#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = "Christian Heider Nielsen"
__doc__ = r"""
          Automatically generates a look up data

           Created on 06/03/2020
           """

from time import sleep, time
from typing import Iterable, Mapping, Set, Tuple

from warg.decorators.hashing import make_hash

global_table = {}

__all__ = ["add_lut", "look_up", "look_up_args", "look_up_kws"]


def add_lut(f: callable):
    """

    :param f:
    :type f:
    :return:
    :rtype:"""
    global_table[f] = {}
    return f


def look_up(f, *args, **kwargs):
    """

    :param f:
    :type f:
    :param args:
    :type args:
    :param kwargs:
    :type kwargs:
    :return:
    :rtype:"""
    ag_hash = hash(args) + make_hash(kwargs)
    if f in global_table:
        if ag_hash in global_table[f]:
            return global_table[f][ag_hash]

        res = global_table[f][ag_hash] = f(*args, **kwargs)
        return res

    global_table[f] = {}
    res = global_table[f][ag_hash] = f(*args, **kwargs)
    return res


def look_up_args(f, *args):
    """

    :param f:
    :type f:
    :param args:
    :type args:
    :return:
    :rtype:"""
    if f in global_table:
        if args in global_table[f]:
            return global_table[f][args]

        res = global_table[f][args] = f(*args)
        return res

    global_table[f] = {}
    res = global_table[f][args] = f(*args)
    return res


def look_up_kws(f, **kws):
    """

    :param f:
    :type f:
    :param kws:
    :type kws:
    :return:
    :rtype:"""
    kw_hash = make_hash(kws)
    if f in global_table:
        if kw_hash in global_table[f]:

            return global_table[f][kw_hash]

        res = global_table[f][kw_hash] = f(**kws)
        return res

    global_table[f] = {}
    res = global_table[f][kw_hash] = f(**kws)
    return res


def precompute_lut(
    f: callable, arg_sets: Set[Tuple[Iterable, Mapping]], *, verbose=False
) -> callable:
    """

    :param f:
    :type f:
    :param arg_sets:
    :type arg_sets:
    :param verbose:
    :type verbose:
    :return:
    :rtype:"""
    for arg_set, kws_set in arg_sets:
        res = look_up(f, *arg_set, **kws_set)
        if verbose:
            print(f"precompute {f},{arg_set},{kws_set}->{res}")
    return f


def precompute_lut_args(
    f: callable, arg_sets: Set[Iterable], *, verbose=False
) -> callable:
    """

    :param f:
    :type f:
    :param arg_sets:
    :type arg_sets:
    :param verbose:
    :type verbose:
    :return:
    :rtype:"""
    for arg_set in arg_sets:
        res = look_up_args(f, *arg_set)
        if verbose:
            print(f"precompute {f},{arg_sets}->{res}")
    return f


def precompute_lut_kws(
    f: callable, arg_sets: Set[Mapping], *, verbose=False
) -> callable:
    """

    :param f:
    :type f:
    :param arg_sets:
    :type arg_sets:
    :param verbose:
    :type verbose:
    :return:
    :rtype:"""
    for arg_set in arg_sets:
        res = look_up_kws(f, **arg_set)
        if verbose:
            print(f"precompute {f},{arg_sets}->{res}")
    return f


def precompute_lut_dec(arg_sets, *, verbose=False):
    """

    :param arg_sets:
    :type arg_sets:
    :param verbose:
    :type verbose:
    :return:
    :rtype:"""

    def rdec(f):
        """

        :param f:
        :type f:
        :return:
        :rtype:"""
        precompute_lut(f, arg_sets, verbose=verbose)
        return f

    return rdec


def precompute_lut_args_dec(arg_sets, *, verbose: bool = False):
    """

    :param arg_sets:
    :type arg_sets:
    :param verbose:
    :type verbose:
    :return:
    :rtype:"""

    def rdec(f):
        """

        :param f:
        :type f:
        :return:
        :rtype:"""
        precompute_lut_args(f, arg_sets, verbose=verbose)
        return f

    return rdec


if __name__ == "__main__":

    @add_lut
    def my_function(foo, bar):
        """

        :param foo:
        :type foo:
        :param bar:
        :type bar:
        :return:
        :rtype:"""
        return foo + bar

    result = look_up_args(my_function, 0, 8)
    print(result)
    result = look_up_args(my_function, 8, 0)
    print(result)

    @precompute_lut_args_dec(list(zip(range(9), reversed(range(9)))))
    def my_function2(foo, bar):
        """

        :param foo:
        :type foo:
        :param bar:
        :type bar:
        :return:
        :rtype:"""
        sleep(0.1)
        return bar * foo

    a = time()
    look_up_args(my_function2, 0, 8)
    print(time() - a)
    a = time()
    look_up_args(my_function2, 8, 0)
    print(time() - a)

    result = look_up(my_function2, 14, bar=21)
    print(result)

    result = look_up(my_function2, 14, bar=21)
    print(result)

    result = look_up(my_function2, 104, bar=2)
    print(result)

    result = look_up_args(my_function, 1, 2)
    print(result)

    result = look_up_args(my_function, 1, 2)
    print(result)

    result = look_up_args(my_function, 12, 2)
    print(result)

    result = look_up_args(my_function2, 1, 2)
    print(result)

    result = look_up_kws(my_function2, foo=1, bar=2)
    print(result)
