"""
Argument parser modules for the TMTC commander core
"""
import argparse
import sys

from tmtccmd.config.definitions import CoreModeList, ServiceOpCodeDictT
from tmtccmd.utility.logger import get_console_logger


LOGGER = get_console_logger()


def parse_input_arguments(
        print_known_args: bool = False, print_unknown_args: bool = False
) -> argparse.Namespace:
    try:
        from tmtccmd.config.hook import get_global_hook_obj
        hook_obj = get_global_hook_obj()
        args = hook_obj.custom_args_parsing()
        if args is None:
            return parse_default_input_arguments(print_known_args, print_unknown_args)
    except ImportError:
        return parse_default_input_arguments(print_known_args, print_unknown_args)


def parse_default_input_arguments(print_known_args: bool = False, print_unknown_args: bool = False):
    """Parses all input arguments
    :return: Input arguments contained in a special namespace and accessable by args.<variable>
    """
    from tmtccmd.utility.conf_util import AnsiColors
    descrip_text = \
        f"{AnsiColors.GREEN}TMTC Client Command Line Interface\n" \
        f"{AnsiColors.RESET}This application provides generic components to execute " \
        f"TMTC commanding.\n" \
        f"The developer is expected to specify the packaged telecommands for a given\n" \
        "service and operation code combination. The developer is also expected\n" \
        "to implement the handling of telemetry. All these tasks can be done by implementing\n" \
        "a hook object and passing it to the core."
    arg_parser = argparse.ArgumentParser(
        description=descrip_text,
        formatter_class=argparse.RawTextHelpFormatter
    )

    add_default_mode_arguments(arg_parser)
    add_default_com_if_arguments(arg_parser)
    add_generic_arguments(arg_parser)

    # TODO: Only add this if TMTC commander is configured for Ethernet?
    add_ethernet_arguments(arg_parser)

    arg_parser.add_argument(
        '--tc_timeout_factor', type=float, help='TC Timeout Factor. Multiplied with '
        'TM Timeout, TC sent again after this time period. Default: 3.5', default=3.5
    )
    arg_parser.add_argument(
        '-r', '--raw_data_print', help='Supply -r to print all raw TM data directly',
        action='store_true'
    )
    arg_parser.add_argument(
        '-d', '--short_display_mode', help='Supply -d to print short output', action='store_true'
    )
    arg_parser.add_argument(
        '--hk', dest='print_hk', help='Supply -k or --hk to print HK data', action='store_true'
    )
    arg_parser.add_argument(
        '--rs', dest="resend_tc", help='Specify whether TCs are sent again after timeout',
        action='store_true'
    )

    if len(sys.argv) == 1:
        LOGGER.info("No input arguments specified. Run with -h to get list of arguments")

    args, unknown = arg_parser.parse_known_args()

    if print_known_args:
        LOGGER.info("Printing known arguments:")
        for argument in vars(args):
            LOGGER.debug(argument + ": " + str(getattr(args, argument)))
    if print_unknown_args:
        LOGGER.info("Printing unknown arguments:")
        for argument in unknown:
            LOGGER.info(argument)

    args_post_processing(args, unknown)
    return args


def add_generic_arguments(arg_parser: argparse.ArgumentParser):
    arg_parser.add_argument('-s', '--service', type=str, help='Service to test', default=None)
    arg_parser.add_argument(
        '-o', '--op_code', help='Operation code, which is passed to the TC packer functions',
        default=None
    )
    arg_parser.add_argument(
        '-l', '--listener',
        help='Determine whether the listener mode will be active after performing the operation',
        action='store_false'
    )
    arg_parser.add_argument(
        '-t', '--tm_timeout', type=float, help='TM Timeout when listening to verification sequence.'
        ' Default: 5 seconds', default=5.0
    )
    arg_parser.add_argument(
        '--nl', dest='print_log', help='Supply --nl to suppress print output to log files.',
        action='store_false'
    )
    arg_parser.add_argument(
        '--np', dest='print_tm', help='Supply --np to suppress print output to console.',
        action='store_false'
    )


def add_default_mode_arguments(arg_parser: argparse.ArgumentParser):
    from tmtccmd.config.definitions import CoreModeList, CoreModeStrings
    help_text = 'Core Modes. Default: seqcmd\n'
    seq_help = \
        f"{CoreModeList.SEQUENTIAL_CMD_MODE} or " \
        f"{CoreModeStrings[CoreModeList.SEQUENTIAL_CMD_MODE]}: " \
        f"Sequential Command Mode\n"
    listener_help = \
        f"{CoreModeList.LISTENER_MODE} or {CoreModeStrings[CoreModeList.LISTENER_MODE]}: " \
        f"Listener Mode\n"
    gui_help = \
        f"{CoreModeList.GUI_MODE} or " \
        f"{CoreModeStrings[CoreModeList.GUI_MODE]}: " \
        f"GUI mode\n"
    help_text += seq_help + listener_help + gui_help
    arg_parser.add_argument(
        '-m', '--mode', type=str, help=help_text,
        default=CoreModeStrings[CoreModeList.SEQUENTIAL_CMD_MODE]
    )


def add_default_com_if_arguments(arg_parser: argparse.ArgumentParser):
    from tmtccmd.config.definitions import CoreComInterfacesDict, CoreComInterfaces
    help_text = "Core Communication Interface. If this is not specified, the commander core\n" \
                "will try to extract it from the JSON or prompt it from the user.\n"
    dummy_line = \
        f"{CoreComInterfacesDict[CoreComInterfaces.DUMMY.value]}: Dummy Interface\n"
    udp_line = \
        f"{CoreComInterfacesDict[CoreComInterfaces.TCPIP_UDP.value]}: " \
        f"UDP client\n"
    ser_dle_line = \
        f"{CoreComInterfacesDict[CoreComInterfaces.SERIAL_DLE.value]}: " \
        f"Serial with DLE transport layer\n"
    ser_fixed_line = \
        f"{CoreComInterfacesDict[CoreComInterfaces.SERIAL_FIXED_FRAME.value]}: " \
        f"Serial with fixed frames\n"
    ser_qemu_line = \
        f"{CoreComInterfacesDict[CoreComInterfaces.SERIAL_QEMU.value]}: " \
        f"QEMU serial interface\n"
    help_text += dummy_line + ser_dle_line + udp_line + ser_fixed_line + ser_qemu_line
    arg_parser.add_argument(
        '-c', '--com_if', type=str, help=help_text, default=CoreComInterfaces.UNSPECIFIED.value
    )


def add_ethernet_arguments(arg_parser: argparse.ArgumentParser):
    arg_parser.add_argument('--clientIP', help='Client(Computer) IP. Default:\'\'', default='')
    arg_parser.add_argument(
        '--boardIP', help='Board IP. Default: Localhost 127.0.0.1', default="127.0.0.1")


def args_post_processing(args, unknown: list) -> None:
    """Handles the parsed arguments.
    :param args: Namespace objects (see https://docs.python.org/dev/library/argparse.html#argparse.Namespace)
    :param unknown: List of unknown parameters.
    :return: None
    """
    if len(unknown) > 0:
        print("Unknown arguments detected: " + str(unknown))
    if len(sys.argv) > 1:
        handle_unspecified_args(args)
    if len(sys.argv) == 1:
        handle_empty_args(args)


def handle_unspecified_args(args) -> None:
    """If some arguments are unspecified, they are set here with (variable) default values.
    :param args:
    :return: None
    """
    from tmtccmd.config.hook import get_global_hook_obj
    from tmtccmd.config.definitions import CoreModeStrings
    if args.tm_timeout is None:
        args.tm_timeout = 5.0
    if args.mode is None:
        args.mode = CoreModeStrings[CoreModeList.SEQUENTIAL_CMD_MODE]
    service_op_code_dict = dict()
    if args.service is None or args.op_code is None:
        hook_obj = get_global_hook_obj()
        service_op_code_dict = hook_obj.get_service_op_code_dictionary()
    if args.service is None:
        if args.mode == CoreModeStrings[CoreModeList.SEQUENTIAL_CMD_MODE]:
            LOGGER.info("No service argument (-s) specified, prompting from user..")
            # Try to get the service list from the hook base and prompt service from user
            args.service = prompt_service(service_op_code_dict)
            if args.op_code is None:
                current_service = args.service
                args.op_code = prompt_op_code(
                    service_op_code_dict=service_op_code_dict, service=current_service
                )
    elif args.op_code is None:
        current_service = args.service
        args.op_code = prompt_op_code(service_op_code_dict=service_op_code_dict, service=current_service)


def handle_empty_args(args) -> None:
    """If no args were supplied, request input from user directly.
    :param args:
    :return:
    """
    LOGGER.info("No arguments specified..")
    handle_unspecified_args(args=args)


def prompt_service(service_op_code_dict: ServiceOpCodeDictT) -> str:
    service_adjustment = 20
    info_adjustment = 30
    horiz_line_num = service_adjustment + info_adjustment + 3
    horiz_line = horiz_line_num * "-"
    service_string = "Service".ljust(service_adjustment)
    info_string = "Information".ljust(info_adjustment)
    while True:
        LOGGER.info(f"{service_string} | {info_string}")
        LOGGER.info(horiz_line)
        for service_entry in service_op_code_dict.items():
            adjusted_service_entry = service_entry[0].ljust(service_adjustment)
            adjusted_service_info = service_entry[1][0].ljust(info_adjustment)
            LOGGER.info(f"{adjusted_service_entry} | {adjusted_service_info}")
        service_string = input("Please select a service by specifying the key: ")
        if service_string in service_op_code_dict:
            LOGGER.info(f"Selected service: {service_string}")
            return service_string
        else:
            LOGGER.warning("Invalid key, try again")


def prompt_op_code(service_op_code_dict: ServiceOpCodeDictT, service: str) -> str:
    op_code_adjustment = 16
    info_adjustment = 34
    horz_line_num = op_code_adjustment + info_adjustment + 3
    horiz_line = horz_line_num * "-"
    op_code_info_str = "Operation Code".ljust(op_code_adjustment)
    info_string = "Information".ljust(info_adjustment)
    while True:
        LOGGER.info(f"{op_code_info_str} | {info_string}")
        LOGGER.info(horiz_line)
        if service in service_op_code_dict:
            op_code_dict = service_op_code_dict[service][1]
            for op_code_entry in op_code_dict.items():
                adjusted_op_code_entry = op_code_entry[0].ljust(op_code_adjustment)
                adjusted_op_code_info = op_code_entry[1][0].ljust(info_adjustment)
                LOGGER.info(f"{adjusted_op_code_entry} | {adjusted_op_code_info}")
            op_code_string = input("Please select an operation code by specifying the key: ")
            if op_code_string in op_code_dict:
                LOGGER.info(f"Selected op code: {op_code_string}")
                return op_code_string
            else:
                LOGGER.warning("Invalid key, try again")
        else:
            LOGGER.warning("Service not in dictionary. Setting default operation code 0")
            return "0"
