'''
Created on 12-Jul-2021

@author: bthakur
'''
import random
import inspect
import traceback

import numpy as np

from sympy import (symbols, Symbol, pi, latex, sympify,
                   Rational, Derivative, simplify,
                   UnevaluatedExpr as ueval
                   )
from xv.km.util import latex_fraction, wrap_in_latex
from xv.util import number_to_nth, list_to_str

from ._prime_number_helper import PrimeNumberHelper


class PowerHelper:
    def __init__(self, **kwargs):
        self.verbose = kwargs.get('verbose', False)
        pass

    def express_multiple_of_a_sum_series(self, a = None, b = None, c = None):
        if a is None:
            a = random.randint(2, 10)
        if b is None:
            b = random.randint(1, 10)
        if c is None:
            c = random.randint(2, 7)

        result1 = self.get_sum_series(a, b)
        result = self.get_sum_series(a = a, b = b * c)

        exprs_sum = [f""" \\left({result1['expr_sum']}\\right) """
                        for x in range(c)]
        exprs_sum = " + ".join(exprs_sum)

        exprs_mul_latex = [f""" \\left({result1['expr_mul_latex']}\\right) """
                        for x in range(c)]

        exprs_mul_latex = " + ".join(exprs_mul_latex)

        expr_mul = f"{c} * {b} * {a}"

        data = {
            'a': a,
            'b': b,
            'c': c,
            'exprs_sum': exprs_sum,
            'exprs_mul_latex': exprs_mul_latex,
            'expr_power': f"{result1['expr_mul']} * {c}",
            'expr_mul': expr_mul,
            'expr_value': result['expr_value'],
            }

        return data
        pass

    def express_power_of_a_sum_series(self, a = None, b = None, c = None):
        if a is None:
            a = random.randint(2, 10)
        if b is None:
            b = random.randint(1, 10)
        if c is None:
            c = random.randint(2, 7)

        result1 = self.get_sum_series(a, b)

        exprs_mul = [f""" \\left({result1['expr_sum']}\\right) """
                        for x in range(c)]
        exprs_mul = " * ".join(exprs_mul)

        exprs_power_latex = [f""" \\left({result1['expr_mul_latex']}\\right) """
                        for x in range(c)]

        exprs_power_latex = " * ".join(exprs_power_latex)

        expr_power_latex = f"{result1['expr_mul']} ^ {c}"

        expr_power = f"\\left({b} * {a}\\right)^{{{c}}}"

        expr_dist_power = f"{{{b}}}^{{{c}}} * {{{a}}}^{{{c}}}"

        expr_dist_value = f"{b ** c} * {a ** c}"

        expr_value = (b * a) ** c

        data = {
            'a': a,
            'b': b,
            'c': c,
            'exprs_mul': exprs_mul,
            'exprs_power_latex': exprs_power_latex,
            'expr_power_latex': expr_power_latex,
            'expr_power': expr_power,
            'expr_dist_power': expr_dist_power,
            'expr_dist_value': expr_dist_value,
            'expr_value': expr_value,
            }

        return data
        pass

    def get_sum_series(self, a = None, b = None):
        if a is None:
            a = random.randint(2, 10)
        if b is None:
            b = random.randint(1, 10)

        expr_mul = ueval(ueval(b) * ueval(a))
        expr_mul_latex = f"{{{b}}} * {{{a}}}"

        expr_value = a * b

        if isinstance(b, int):
            expr_sum = [f"{a}" for i in range(b)]
            expr_sum = " + ".join(expr_sum)
        else:
            expr_sum = None

        large_series = self._express_as_large_series(a, b, "+")

        data = {
            'a': a,
            'b': b,
            'expr_mul': expr_mul,
            'expr_mul_latex': expr_mul_latex,
            'expr_sum': expr_sum,
            'expr_value': expr_value,
            'large_series': large_series
            }

        return data
        pass


    def get_mul_series(self, a = None, b = None):
        if a is None:
            a = random.randint(2, 10)
        if b is None:
            b = random.randint(1, 10)

        expr_power = ueval(a) ** ueval(b)
        expr_value = a ** b

        if isinstance(b, int):
            expr_mul = [f"{a}" for i in range(b)]
            expr_mul = " * ".join(expr_mul)
        else:
            expr_mul = None

        large_series = self._express_as_large_series(a, b, "*")

        data = {
            'a': a,
            'b': b,
            'expr_power': expr_power,
            'expr_mul': expr_mul,
            'expr_value': expr_value,
            'large_series': large_series,
            }

        return data
        pass


    def distribute_power_of_product_expr(self, nums = None, e = None):
        if nums is None:
            nums = random.sample(list(range(2, 10)),
                                 random.randint(2, 5))
            #nums = list(np.random.randint(2, 5, size = random.randint(2, 5) ))

        if e is None:
            e = random.randint(2, 10)

        expr_base = 1
        expr_dist_power = 1
        for x in nums:
            expr_base = expr_base * ueval(x)
            expr_dist_power = expr_dist_power * ueval(x) **  ueval(e)

        expr_power = ueval(expr_base) **  ueval(e)

        data = {
            'nums': nums,
            'e': e,
            'expr_base': expr_base,
            'expr_power': expr_power,
            'expr_dist_power': expr_dist_power,
            }

        return data
        pass

    def _express_as_large_series(self, a, b, op):
        if not isinstance(b, int):
            return None

        n = min(b, 3)
        if n <= 0:
            n = 3

        terms = [f"{a}" for i in range(n)]
        terms_str = f" {op} ".join(terms)

        if b not in [1, 2, 3]:
            if b < 0:
                b_expr = f"\\left({b}\\right)"
            else:
                b_expr = f"{b}"
            terms_str = f"{terms_str} \\cdots {b_expr}\\text{{ times}}"

        return terms_str
        pass

    def get_soln_exponent_to_root_form(self, num, root_base):

        pn_helper = PrimeNumberHelper(verbose = self.verbose)

        if isinstance(root_base, Rational):
            inv_root_base = Rational(root_base.q, root_base.p)
        else:
            inv_root_base = 1 / root_base

        power_frac = latex_fraction(1, latex(inv_root_base))

        soln_items = []
        soln_item = f"""
        $\\displaystyle {self.get_exp_latex(num, inv_root_base)}$
        """
        soln_items.append(soln_item.strip())

        soln_item = f"""
        $\\displaystyle = {self.get_nth_root_latex(root_base, num)}
        \\qquad \\text{{ Root is inverse of power }}
        \\quad {power_frac} = {latex(root_base)}
        $

        """
        soln_items.append(soln_item.strip())


        soln_s = "\n\n".join(soln_items)

        answer_s = f"""
        $\\displaystyle {self.get_nth_root_latex(root_base, num)}$
        """

        return soln_s.strip(), answer_s.strip()

        pass

    def get_soln_root_to_exponent_form(self, num, root_base):

        pn_helper = PrimeNumberHelper(verbose = self.verbose)

        if isinstance(root_base, Rational):
            inv_root_base = Rational(root_base.q, root_base.p)
        else:
            inv_root_base = 1 / root_base

        inv_root_base_frac = latex_fraction(1, latex(root_base))

        soln_items = []
        soln_item = f"""
        $\\displaystyle {self.get_nth_root_latex(root_base, num)}$
        """
        soln_items.append(soln_item.strip())

        prime_factors = pn_helper.getPrimeFactors(num)
        prime_factors_latex = pn_helper.getPrimeFactors_as_str(num, as_latex = True)

        prime_factors_mod_ueval = {k:Symbol(latex(ueval(v) * ueval(inv_root_base)))
                                   for k,v in prime_factors.items()}

        prime_factors_mod_ueval_latex = pn_helper.convertPrimeFactors_dict_to_str(
                                        prime_factors_mod_ueval, as_latex = True)

        prime_factors_mod = {k:Symbol(latex(v * inv_root_base))
                             for k,v in prime_factors.items()}

        prime_factors_mod_latex = pn_helper.convertPrimeFactors_dict_to_str(
                                        prime_factors_mod, as_latex = True)

        soln_item = f"""
        $\\displaystyle
        = {{\\left( {prime_factors_latex} \\right) }}^{{{latex(inv_root_base)}}}
        \\qquad \\text{{ Power is inverse of root }}
        \\quad {inv_root_base_frac} = {latex(inv_root_base)}
        $

        $\\displaystyle = {prime_factors_mod_ueval_latex}
        \\qquad \\text{{ Power to a power are multiplied together. }}
        $

        $\\displaystyle = {prime_factors_mod_latex}$


        """
        soln_items.append(soln_item.strip())


        soln_s = "\n\n".join(soln_items)

        answer_s = f"""
        $\\displaystyle {prime_factors_mod_latex}$
        """

        return soln_s.strip(), answer_s.strip()

        pass


    def get_nth_root_latex(self, root_base, num):
        s = f""" \\sqrt[{latex(root_base)}]{{{latex(num)}}} """
        return s.strip()

    def get_exp_latex(self, num_base, num_exp):
        try:
            if int(num_base) == num_base  and num_base >= 0:
                parenthesis = False
            elif isinstance(num_base, Symbol) and len(f"{num_base}") == 1:
                parenthesis = False
            else:
                parenthesis = True
        except:
            parenthesis = True
            pass

        base_s = f""" {latex(num_base)} """
        if parenthesis:
            base_s = f""" \\left( {base_s} \\right) """
            pass

        s = f""" {{{base_s}}}^{{{latex(num_exp)}}} """
        return s.strip()

    def get_unit_digit_of_an_exp_number(self, base_num = None, exp_num = None):
        if base_num is None:
            base_num = random.randint(10, 1000)

        if exp_num is None:
            exp_num = random.randint(10, 1000)

        base_unit = sympify(base_num % 10)

        num = base_num ** ueval(exp_num)



        soln_stmts = []
        u = Symbol('x')

        soln_stmt = f"""
        <h4>Step 1: Which units digits will appear in any power of {wrap_in_latex(base_num)}?</h4>
        The unit digit of an exponential expression depends only on the last digit of
        the base number.
        <br/>
        In this case,
        the unit digit of {wrap_in_latex(base_num)} is {wrap_in_latex(base_unit)}.
        <br/>
        Let $\\displaystyle
        {latex(u)} = {latex(base_unit)}
        $
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_stmt = f"""
        Let us write a power table of {wrap_in_latex(u)} but keep only unit digit.
        <br/>
        Every time, we will multiply {wrap_in_latex(u)} with last unit digit of previous step.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        temp_units = []
        temp_unit = base_unit

        for i in range(0, 10):

            if  i == 0:
                soln_stmt = f"""
                $\\displaystyle
                \\text{{ Unit digit of }}
                {latex(u)}

                = {latex(base_unit)}
                $
                """
                soln_stmts.append(soln_stmt.strip())

                temp_units.append(base_unit)
                continue

            new_temp_unit = (base_unit * temp_unit) % 10

            soln_stmt = f"""
            $\\displaystyle

            \\text{{ Unit digit of }} {latex(u)} \\cdot  \\text{{ last unit digit }}

            = {latex(base_unit)} \\cdot  {latex(temp_unit)}

            =  {latex(base_unit * temp_unit)}

            = {latex(new_temp_unit)}
            $
            """
            soln_stmts.append(soln_stmt.strip())

            if new_temp_unit in temp_units:
                soln_stmt = f"""
                As unit digit {wrap_in_latex(new_temp_unit)} is repeated,
                we will stop continuing further.
                """
                soln_stmts.append(soln_stmt.replace("\n", " ").strip())
                break
                pass

            temp_units.append(new_temp_unit)

            temp_unit = new_temp_unit
            pass

        answer, soln_temp_units = self._get_soln_temp_units(
            num, exp_num, base_num, base_unit, temp_units)

        soln_stmts.extend(soln_temp_units)

        soln_s = "\n\n".join(soln_stmts)

        answer_s = f"""
        {wrap_in_latex(answer)}
        """
        result = {
            'base_num': base_num,
            'exp_num': exp_num,
            'num': num,
            'soln_s': soln_s.strip(),
            'unit_digit': answer,
            'answer_s': answer_s.strip(),
            }

        return result

    def _get_soln_temp_units(self, num, exp_num, base_num,
                             base_unit, temp_units):
        temp_units_count = len(temp_units)
        soln_stmts = []

        if temp_units_count == 1:
            soln_stmt = f"""
            As there is only one possible unit digit, ${temp_units[0]}$,
            for any power of {wrap_in_latex(num)}, therefore,
            <br/>
            last digit = ${temp_units[0]}$
            """
            soln_stmts.append(soln_stmt.replace("\n", " ").strip())
            return temp_units[0], soln_stmts

        soln_stmt = f"""
        As we see, possible unit digits of any power of {wrap_in_latex(base_unit)}
        are {temp_units}.
        <br/>
        So, any power of {wrap_in_latex(base_num)} will
        have only these ${temp_units_count}$ digits appearing in a cycle.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_stmt = f"""
        <h4>Step 2: Which one of the above digits will be unit digit
        of {wrap_in_latex(num)}?</h4>
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        count_remainder = exp_num % temp_units_count

        soln_stmt = f"""
        As only ${temp_units_count}$ digits repeat in units place in a loop,
        we can ignore the completed loops
        and take into consideration the remainder.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_stmt = f"""
        $\\displaystyle
        \\text{{ Remainder of }}

        \\frac{{ {exp_num} }}{{ {temp_units_count} }}

        = {latex(count_remainder)}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        if count_remainder != 0:
            answer = temp_units[count_remainder - 1]
            soln_stmt = f"""
            As remainder is ${count_remainder}$,
            the unit digit will be
            {number_to_nth(count_remainder)} position
            in {temp_units}. Therefore,
            <br/>
            last digit = ${answer}$

            """
            soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        else:
            answer = temp_units[-1]
            soln_stmt = f"""
            As remainder is ${count_remainder}$, the loop is completed
            and we are at the end of the loop.
            Therefore, the unit digit will be last
            digit of {temp_units}. Hence,
            <br/>
            last digit = ${answer}$
            """
            soln_stmts.append(soln_stmt.replace("\n", " ").strip())
            pass

        num_str = str(simplify(num))

        real_last_digit = int(num_str[-1])

        if len(num_str) > 10:
            num_str = f"{num_str[:3]} \\cdots {num_str[-3:]}"


        if real_last_digit != answer:
            soln_stmt = f"""
            <h4>Error: The correct unit digit should be:</h4>
            """
            soln_stmts.append(soln_stmt.strip())
        else:

            soln_stmt = f"""
            <h4>Verification of result:</h4>
            """
            soln_stmts.append(soln_stmt.strip())
            pass

        soln_stmt = f"""
        $\\displaystyle
        \\text{{The unit digit of }}
        \\left(
        {latex(num)}
        \\right)
        $

        $\\displaystyle
        = \\text{{The unit digit of }}
        \\left(
        {num_str}
        \\right)
        $

        $\\displaystyle
        = {latex(real_last_digit)}
        $

        """
        soln_stmts.append(soln_stmt.strip())



        return answer, soln_stmts
        pass

    pass
#class PowerHelper:
