# Copyright (C) 2015 Elf M. Sternberg
# Author: Elf M. Sternberg

from functools import reduce
from collections import namedtuple
import getopt

try:  # noqa: F401
    from typing import Dict, List, Text, Any, Optional, Union, Callable, Tuple  # noqa: F401
except:  # noqa: F401
    pass  # noqa: F401


Option = namedtuple('Option', ['short', 'long', 'takes', 'help', 'conflicts'])  # type: str, str, str, str, List[str]
Arguments = namedtuple('Arguments', ['arguments', 'filenames', 'excluded'])  # type: Dict[str, str], List[str], List[str]

# This was a lot shorter and smarter in Hy...

# A lot of what you see here is separated from git_lint itself, since this will not be
# relevant to the operation of pre-commit.

#   ___                              _   _    _
#  / __|___ _ __  _ __  __ _ _ _  __| | | |  (_)_ _  ___
# | (__/ _ \ '  \| '  \/ _` | ' \/ _` | | |__| | ' \/ -_)
#  \___\___/_|_|_|_|_|_\__,_|_||_\__,_| |____|_|_||_\___|
#


def cleanup_options(options, commandline):
    # type: (List[Option], List[str]) -> Arguments
    """Takes a table of options and the commandline, and returns a
       dictionary of those options that appear on the commandline
       along with any extra arguments.

    :param List(Tuple (string, string, boolean, string, List(string))) options,
        The table of options: One-letter option, long option, takes arguments,
        Help text, list of (long) options superseded by this one.

    : param List(strings) commandline
        The arguments as received by the start-up process

    : returns List(strings), List(strings), List(strings)
        The longopt dictionary of arguments and associated values (if any)
        The list of filenames left after argument processing
        The longopt list of arguments that were excluded by argument precedence
    """

    def make_option_streamliner(options):
        # type: (List[Option]) -> Callable[[Dict[str, str], Option], Dict[str, str]]

        """Takes a list of option tuples, and returns a function that takes
            the output of getopt and reduces it to the longopt key and
            associated values as a dictionary.
        """

        fullset = {}  # type: Dict[str, str]
        for option in options:
            if option.long:
                fullset['--' + option.long] = option.long
                if option.short:
                    fullset['-' + option.short] = option.long

        def streamliner(acc, it):
            # type: (Dict[str, str], Option) -> Dict[str, str]
            acc[fullset[it[0]]] = it[1]
            return acc

        return streamliner

    def remove_conflicted_options(options, request):
        # type: (List[Option], Dict[str, str]) -> Tuple[List[str], List[str]]
        """Takes our list of option tuples, and a cleaned copy of what was
            requested from getopt, and returns a copy of the request
            without any options that are marked as superseded, along with
            the list of superseded options
        """
        def get_excluded_keys(memo, option):
            return memo + option.conflicts

        keys = request.keys()
        marked = [option for option in options if option.long in keys]
        exclude = reduce(get_excluded_keys, marked, [])
        excluded = [key for key in keys if key in exclude]
        cleaned = {key: request[key] for key in keys
                   if key not in excluded}
        return (cleaned, excluded)

    def shortoptstogo(i):
        return i.short + ((i.takes and ':') or '')

    def longoptstogo(i):
        return i.long + ((i.takes and '=') or '')

    optstringsshort = ''.join([shortoptstogo(opt) for opt in options if opt.short])
    optstringslong = [longoptstogo(opt) for opt in options]
    (chosen_options, filenames) = getopt.getopt(commandline[1:],
                                                optstringsshort,
                                                optstringslong)

    # Turns what getopt returns into something more human-readable
    streamline_options = make_option_streamliner(options)

    # Remove any options that are superseded by others.
    (ret, excluded) = remove_conflicted_options(
        options, reduce(streamline_options, chosen_options, {}))

    return Arguments(ret, filenames, excluded)
