from argparse import ArgumentParser, ArgumentTypeError
from locale import getdefaultlocale
from multiprocessing import Pool
from contextlib import redirect_stdout
from io import StringIO

from zdict import constants, utils, easter_eggs
from zdict.api import dump
from zdict.completer import DictCompleter
from zdict.loader import get_dictionary_map
from zdict.utils import readline, check_zdict_dir_and_db


def user_set_encoding_and_is_utf8():
    # Check user's encoding settings
    try:
        (lang, enc) = getdefaultlocale()
    except ValueError:
        print("Didn't detect your LC_ALL environment variable.")
        print("Please export LC_ALL with some UTF-8 encoding.")
        print("For example: `export LC_ALL=en_US.UTF-8`")
        return False
    else:
        if enc != "UTF-8":
            print("zdict only works with encoding=UTF-8, ")
            print("but your encoding is: {} {}".format(lang, enc))
            print("Please export LC_ALL with some UTF-8 encoding.")
            print("For example: `export LC_ALL=en_US.UTF-8`")
            return False
    return True


def get_args():
    # parse args
    parser = ArgumentParser(prog='zdict')

    parser.add_argument(
        'words',
        metavar='word',
        type=str,
        nargs='*',
        help='Words for searching its translation'
    )

    parser.add_argument(
        "-v", "--version",
        action="version",
        version='%(prog)s-' + constants.VERSION
    )

    parser.add_argument(
        "-d", "--disable-db-cache",
        default=False,
        action="store_true",
        help="Temporarily not using the result from db cache.\
              (still save the result into db)"
    )

    parser.add_argument(
        "-t", "--query-timeout",
        type=float,
        default=5.0,
        action="store",
        help="Set timeout for every query. default is 5 seconds."
    )

    def positive_int_only(value):
        ivalue = int(value)
        if ivalue <= 0:
            raise ArgumentTypeError(
                "%s is an invalid positive int value" % value
            )
        return ivalue

    parser.add_argument(
        "-j", "--jobs",
        type=positive_int_only,
        nargs="?",
        default=0,     # 0: not using, None: auto, N (1, 2, ...): N jobs
        action="store",
        help="""
            Allow N jobs at once.
            Do not pass any argument to use the number of CPUs in the system.
        """
    )

    parser.add_argument(
        "-sp", "--show-provider",
        default=False,
        action="store_true",
        help="Show the dictionary provider of the queried word"
    )

    parser.add_argument(
        "-su", "--show-url",
        default=False,
        action="store_true",
        help="Show the url of the queried word"
    )

    available_dictionaries = list(dictionary_map.keys())
    available_dictionaries.append('all')
    parser.add_argument(
        "-dt", "--dict",
        default="yahoo",
        action="store",
        choices=available_dictionaries,
        metavar=','.join(available_dictionaries),
        help="""
            Must be seperated by comma and no spaces after each comma.
            Choose the dictionary you want. (default: yahoo)
            Use 'all' for qureying all dictionaries.
            If 'all' or more than 1 dictionaries been chosen,
            --show-provider will be set to True in order to
            provide more understandable output.
        """
    )

    parser.add_argument(
        "-ld", "--list-dicts",
        default=False,
        action="store_true",
        help="Show currently supported dictionaries."
    )

    parser.add_argument(
        "-V", "--verbose",
        default=False,
        action="store_true",
        help="Show more information for the queried word.\
        (If the chosen dictionary have implemented verbose related functions)"
    )

    parser.add_argument(
        "-c", "--force-color",
        default=False,
        action="store_true",
        help="Force color printing (zdict automatically disable color printing \
        when output is not a tty, use this option to force color printing)"
    )

    parser.add_argument(
        '--dump', dest='pattern',
        nargs='?',
        default=None, const=r'^.*$',
        help='Dump the querying history, can be filtered with regex'
    )

    parser.add_argument(
        "-D", "--debug",
        default=False,
        action="store_true",
        help="Print raw html prettified by BeautifulSoup for debugging."
    )

    return parser.parse_args()


def set_args(args):
    if args.force_color:
        utils.Color.set_force_color()

    args.dict = args.dict.split(',')

    if 'all' in args.dict:
        args.dict = tuple(dictionary_map.keys())
    else:
        # Uniq and Filter the dict not in supported dictionary list then sort.
        args.dict = sorted(set(d for d in args.dict if d in dictionary_map))

    if len(args.dict) > 1:
        args.show_provider = True

    return args


def lookup_string_wrapper(dict_class, word, args):
    import sys
    if args.force_color:
        utils.Color.set_force_color()
    else:
        utils.Color.set_force_color(sys.stdout.isatty())

    dictionary = dict_class(args)
    f = StringIO()
    with redirect_stdout(f):
        dictionary.lookup(word)
    return f.getvalue()


def init_worker():
    # When -j been used, make subprocesses ignore KeyboardInterrupt
    # for not showing KeyboardInterrupt traceback error message.
    import signal
    signal.signal(signal.SIGINT, signal.SIG_IGN)


def normal_mode(args):
    if args.jobs == 0:
        # user didn't use `-j`
        for word in args.words:
            for d in args.dict:
                zdict = dictionary_map[d](args)
                zdict.lookup(word)
    else:
        # user did use `-j`
        # If processes is None, os.cpu_count() is used.
        pool = Pool(args.jobs, init_worker)

        for word in args.words:
            futures = [
                pool.apply_async(lookup_string_wrapper,
                                 (dictionary_map[d], word, args))
                for d in args.dict
            ]
            results = [i.get() for i in futures]
            print(''.join(results))

    easter_eggs.lookup_pyjokes(word)


class MetaInteractivePrompt():
    def __init__(self, args):
        self.args = args
        self.dicts = tuple(
            dictionary_map[d](self.args) for d in self.args.dict
        )
        self.dict_classes = tuple(dictionary_map[d] for d in self.args.dict)

        if self.args.jobs == 0:
            # user didn't use `-j`
            self.pool = None
        else:
            # user did use `-j`
            # If processes is None, os.cpu_count() is used.
            self.pool = Pool(self.args.jobs, init_worker)

    def __del__(self):
        del self.dicts

    def prompt(self):
        user_input = input('[zDict]: ').strip()

        if user_input:
            if self.pool:
                futures = [
                    self.pool.apply_async(lookup_string_wrapper,
                                          (d, user_input, self.args))
                    for d in self.dict_classes
                ]
                results = [i.get() for i in futures]
                print(''.join(results))
            else:
                for dictionary_instance in self.dicts:
                    dictionary_instance.lookup(user_input)
        else:
            return

    def loop_prompt(self):
        while True:
            self.prompt()


def interactive_mode(args):
    # configure readline and completer
    readline.parse_and_bind("tab: complete")
    readline.set_completer(DictCompleter().complete)

    zdict = MetaInteractivePrompt(args)
    zdict.loop_prompt()


def execute_zdict(args):
    if args.list_dicts:
        for provider in sorted(
            dictionary_map,
            key=lambda x: {'yahoo': 0, 'pyjokes': 2}.get(x, 1)
        ):
            print(
                '{}: {}'.format(
                    provider,
                    dictionary_map[provider](args).title
                )
            )
        exit()

    if args.pattern:
        for word in dump(pattern=args.pattern):
            print(word)
        exit()

    try:
        if args.words:
            normal_mode(args)
        else:
            interactive_mode(args)
    except (KeyboardInterrupt, EOFError):
        print()
        return


def main():
    if user_set_encoding_and_is_utf8():
        check_zdict_dir_and_db()

        global dictionary_map
        dictionary_map = get_dictionary_map()

        args = get_args()
        args = set_args(args)

        execute_zdict(args)
    else:
        exit()