from typing import List, Optional
from dataclasses import dataclass
import argparse
from pathlib import Path

@dataclass
class Args():
    logging_no_color: bool
    logging_verbosity_verbose: int
    logging_verbosity_quiet: int

    dry_run: bool
    copy_links: bool
    exclude: List[str]
    exclude_from: List[Path]
    delete: bool
    delete_excluded: bool
    force: bool
    show_progress: bool
    adb_encoding: str

    adb_bin: str
    adb_flags: List[str]
    adb_options: List[List[str]]

    direction: str

    direction_push_local: Optional[str]
    direction_push_android: Optional[str]

    direction_pull_android: Optional[str]
    direction_pull_local: Optional[str]

def get_cli_args(docstring: str, version: str) -> Args:
    parser = argparse.ArgumentParser(description = docstring)
    parser.add_argument("--version",
        action = "version",
        version = version
    )

    parser_logging = parser.add_argument_group(title = "logging")
    parser_logging.add_argument("--no-color",
        help = "Disable colored logging (Linux only)",
        action = "store_true",
        dest = "logging_no_color"
    )
    parser_logging_verbosity = parser_logging.add_mutually_exclusive_group(required = False)
    parser_logging_verbosity.add_argument("-v", "--verbose",
        help = "Increase logging verbosity: -v for debug",
        action = "count",
        dest = "logging_verbosity_verbose",
        default = 0
    )
    parser_logging_verbosity.add_argument("-q", "--quiet",
        help = "Decrease logging verbosity: -q for warning, -qq for error, -qqq for critical, -qqqq for no logging messages",
        action = "count",
        dest = "logging_verbosity_quiet",
        default = 0
    )

    parser.add_argument("-n", "--dry-run",
        help = "Perform a dry run; do not actually copy and delete etc",
        action = "store_true",
        dest = "dry_run"
    )
    parser.add_argument("-L", "--copy-links",
        help = "Follow symlinks and copy their referent file / directory",
        action = "store_true",
        dest = "copy_links"
    )
    parser.add_argument("--exclude",
        help = "fnmatch pattern to ignore relative to source (reusable)",
        action = "append",
        dest = "exclude",
        default = []
    )
    parser.add_argument("--exclude-from",
        help = "Filename of file containing fnmatch patterns to ignore relative to source (reusable)",
        metavar = "EXCLUDE_FROM",
        type = Path,
        action = "append",
        dest = "exclude_from",
        default = []
    )
    parser.add_argument("--del",
        help = "Delete files at the destination that are not in the source",
        action = "store_true",
        dest = "delete"
    )
    parser.add_argument("--delete-excluded",
        help = "Delete files at the destination that are excluded",
        action = "store_true",
        dest = "delete_excluded"
    )
    parser.add_argument("--force",
        help = "Allows files to overwrite folders and folders to overwrite files. This is false by default to prevent large scale accidents",
        action = "store_true",
        dest = "force"
    )
    parser.add_argument("--show-progress",
        help = "Show progress from 'adb push' and 'adb pull' commands",
        action = "store_true",
        dest = "show_progress"
    )
    parser.add_argument("--adb-encoding",
        help = "Which encoding to use when talking to adb. Defaults to UTF-8. Relevant to GitHub issue #22",
        dest = "adb_encoding",
        default = "UTF-8"
    )

    parser_adb = parser.add_argument_group(title = "ADB arguments",
        description = "By default ADB works for me without touching any of these, but if you have any specific demands then go ahead. See 'adb --help' for a full list of adb flags and options"
    )
    parser_adb.add_argument("--adb-bin",
        help = "Use the given adb binary. Defaults to 'adb' ie whatever is on path",
        dest = "adb_bin",
        default = "adb")
    parser_adb.add_argument("--adb-flag",
        help = "Add a flag to call adb with, eg '--adb-flag d' for adb -d, that is return an error if more than one device is connected",
        metavar = "ADB_FLAG",
        action = "append",
        dest = "adb_flags",
        default = []
    )
    parser_adb.add_argument("--adb-option",
        help = "Add an option to call adb with, eg '--adb-option P 5037' for adb -P 5037, that is use port 5037 for the adb server",
        metavar = ("OPTION", "VALUE"),
        nargs = 2,
        action = "append",
        dest = "adb_options",
        default = []
    )

    parser_direction = parser.add_subparsers(title = "direction",
        dest = "direction",
        required = True
    )

    parser_direction_push = parser_direction.add_parser("push",
        help = "Push from computer to phone"
    )
    parser_direction_push.add_argument("direction_push_local",
        metavar = "LOCAL",
        help = "Local path"
    )
    parser_direction_push.add_argument("direction_push_android",
        metavar = "ANDROID",
        help = "Android path"
    )

    parser_direction_pull = parser_direction.add_parser("pull",
        help = "Pull from phone to computer"
    )
    parser_direction_pull.add_argument("direction_pull_android",
        metavar = "ANDROID",
        help = "Android path"
    )
    parser_direction_pull.add_argument("direction_pull_local",
        metavar = "LOCAL",
        help = "Local path"
    )

    args = parser.parse_args()

    if args.direction == "push":
        args_direction_ = (
            args.direction_push_local,
            args.direction_push_android,
            None,
            None
        )
    else:
        args_direction_ = (
            None,
            None,
            args.direction_pull_android,
            args.direction_pull_local
        )

    args = Args(
        args.logging_no_color,
        args.logging_verbosity_verbose,
        args.logging_verbosity_quiet,

        args.dry_run,
        args.copy_links,
        args.exclude,
        args.exclude_from,
        args.delete,
        args.delete_excluded,
        args.force,
        args.show_progress,
        args.adb_encoding,

        args.adb_bin,
        args.adb_flags,
        args.adb_options,

        args.direction,
        *args_direction_
    )

    return args
