'''
Created on 16-Apr-2021

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

import sympy
import numpy as np

from sympy import (symbols, Function, Derivative, diff,
                   simplify, latex,
                   Symbol, Sum, binomial, factorial, UnevaluatedExpr
                   )
from ...util import MathUtil
from xv.util import list_to_str


'''
, diff, Symbol, Mul, Matrix,
factorial, sympify, Rational,
Derivative, factor, Eq, simplify, expand,
exp, sin, cos, cosh, sinh, pi, sqrt, atan, Limit,
expand_trig, trigsimp, srepr, Integer, Pow, Add)
'''

from xv.km.kelements import KPrintElement

from xv.km.util import latex_fraction


from ...helper import BaseHelper
from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate

# _xn_manager
# XnManager

class XnManager(BaseManager):
    '''
    Differentiation of X**n


    Examples
    --------

    from xv.math.calculus.differentiation import XnManager
    ke = XnManager()
    ke

    ke.printProblemTypes()

    ke.getRandomProblem()
    ke.getRandomProblem(problem_type = 0)
    ke.getRandomProblem(problem_type = 1)
    ...

    ke.printProblem()
    ke.printAnswer()
    ke.printSolution()


    doc_style: xv_doc
    '''

    def __new__(cls, *args, **kwargs):
        instance = super(XnManager, cls).__new__(cls, *args, **kwargs)
        for base_class in XnManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance

    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        pass


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
                '_problem_calculate_diff_fx_n',
                '_problem_calculate_diff_x_n',
                '_problem_binomial_series',
                '_problem_binomial_series_applied',
                '_problem_binomial_term',
                '_problem_selection_of_items',
                '_problem_factorial_0',
                '_problem_permutation_term',
                '_problem_factorial_expression',
             ])
        pass

    def _problem_calculate_diff_fx_n(self, *args, **kwargs):
        """
        Differentiation of f(x) as nth power


        doc_style: xv_doc
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        x, n = symbols('x, n')
        a = Symbol('a', constant = True)

        if random.choice([True, False]):
            n = random.randint(2, 10)
            pass

        f = Function('f')
        f = f(x)
        f = (x - a) ** n


        df_dx = Derivative(f, x)
        df_dx_doit = simplify(df_dx.doit())

        problem_s = f"""
        Find differentiation of ${latex(f)}$ with respect to ${latex(x)}$
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        differentiation of ${latex(f)}$ with respect to ${latex(x)}$

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

        $\\displaystyle = {latex(df_dx_doit)} $
        """

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        $\\displaystyle {latex(df_dx_doit)} $
        """

        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass

    def _problem_calculate_diff_x_n(self, *args, **kwargs):
        """
        Differentiation of X**n


        Minimum Grade: 6
        Maximum Grade: 12


        Examples
        --------
        from xv.math.calculus.differentiation import XnManager
        ke = XnManager()


        ke.getRandomProblem(problem_type = '_problem_calculate_diff_x_n')


        doc_style: xv_doc
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        x, n = symbols('x, n')

        if random.choice([True, False]):
            n = random.randint(2, 10)
            pass

        f = Function('f')
        f = f(x)
        f = x ** n

        df_dx = Derivative(f, x)
        df_dx_doit = simplify(df_dx.doit())

        problem_s = f"""
        Find differentiation of ${latex(f)}$ with respect to ${latex(x)}$
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        differentiation of ${latex(f)}$ with respect to ${latex(x)}$

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

        $\\displaystyle = {latex(df_dx_doit)} $
        """

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        $\\displaystyle {latex(df_dx_doit)} $
        """

        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass

    def _problem_binomial_series(self, *args, **kwargs):
        """
        _problem_binomial_series


        Minimum Grade: 6
        Maximum Grade: 12
        doc_style: xv_doc
        """

        binomial_helper = self.base_helper.get_algebra_helper().get_binomial_helper()


        bs_expr = binomial_helper.get_expr()
        bs_sum = binomial_helper.get_series_sum()


        bs_eval_0 = binomial_helper.get_series_latex(evaluate_level = 0)
        bs_eval_1 = binomial_helper.get_series_latex(evaluate_level = 1)
        bs_eval_2 = binomial_helper.get_series_latex(evaluate_level = 2)
        bs_eval_3 = binomial_helper.get_series_latex(evaluate_level = 3)



        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Expand:

        $\\displaystyle {latex(bs_expr)}$
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        $\\displaystyle {latex(bs_expr)}$

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

        $\\displaystyle = {bs_eval_0}$

        $\\displaystyle = {bs_eval_1}$

        $\\displaystyle = {bs_eval_2}$

        $\\displaystyle = {bs_eval_3}$

        """

        #$\\displaystyle = {latex(sum(bs))}$

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        """

        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass


    def _problem_binomial_series_applied(self, *args, **kwargs):
        """

        Minimum Grade: 6
        Maximum Grade: 12

        doc_style: xv_doc
        """


        binomial_helper = self.base_helper.get_algebra_helper().get_binomial_helper()

        x, y, n = binomial_helper.get_random_data()

        bs_expr = binomial_helper.get_expr(x = x, y = y, n = n)
        bs_sum = binomial_helper.get_series_sum(x = x, y = y, n = n)


        bs_eval_0 = binomial_helper.get_series_latex(x = x, y = y, n = n,
                                                     evaluate_level = 0)

        bs_eval_1 = binomial_helper.get_series_latex(x = x, y = y, n = n,
                                                     evaluate_level = 1)

        bs_eval_2 = binomial_helper.get_series_latex(x = x, y = y, n = n,
                                                     evaluate_level = 2)

        bs_eval_3 = binomial_helper.get_series_latex(x = x, y = y, n = n,
                                                     evaluate_level = 3)



        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Expand:

        $\\displaystyle {latex(bs_expr)}$
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        $\\displaystyle {latex(bs_expr)}$

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

        $\\displaystyle = {bs_eval_0}$

        $\\displaystyle = {bs_eval_1}$

        $\\displaystyle = {bs_eval_2}$

        $\\displaystyle = {bs_eval_3}$

        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

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

        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass


    def _problem_binomial_term(self, *args, **kwargs):
        """
        _problem_binomial_term


        Minimum Grade: 6
        Maximum Grade: 12

        doc_style: xv_doc
        """

        binomial_helper = self.base_helper.get_algebra_helper().get_binomial_helper()

        x, y, n = binomial_helper.get_random_data()
        bs_expr = binomial_helper.get_expr(x = x, y = y, n = n)

        i = random.randint(0, n)
        term_0 = binomial_helper.get_ith_term(i, x, y, n, evaluate_level = 0)
        term_1 = binomial_helper.get_ith_term(i, x, y, n, evaluate_level = 1)
        term_2 = binomial_helper.get_ith_term(i, x, y, n, evaluate_level = 2)


        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Find $\\displaystyle t_{{{i}}}$ in the expansion of the following. The term count starts from zero.

        $\\displaystyle {latex(bs_expr)}$
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        $\\displaystyle t_{{{i}}}$

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

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

        $\\displaystyle = {latex(term_2)}$
        """

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        $\\displaystyle {latex(term_2)}$
        """

        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass



    def _problem_selection_of_items(self, *args, **kwargs):
        """
        Minimum Grade: 6
        Maximum Grade: 12

        doc_style: xv_doc
        """
        binomial_helper = self.base_helper.get_algebra_helper().get_binomial_helper()
        n = binomial_helper.n
        k = binomial_helper.k
        nCk = binomial_helper.get_combination_latex(n , k)
        exprC = binomial_helper.get_combination_expr(n , k)

        combination_formula = binomial_helper.get_combination_formula_latex(n, k)
        combination_formula = list_to_str(combination_formula, sep = "\n\n")

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Write expression for selecting ${latex(k)}$ items from a collection of ${latex(n)}$ items

        $\\displaystyle {nCk}$

        Note: $\\displaystyle {nCk}
        $ is read as ${latex(n)}$ combination ${latex(k)}$.
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        {combination_formula}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        $\\displaystyle
        {latex(exprC)}
        $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass


    def _problem_factorial_0(self, *args, **kwargs):
        """
        _problem_factorial_0


        Minimum Grade: 6
        Maximum Grade: 12


        Examples
        --------
        from xv.math.calculus.differentiation import XnManager
        ke = XnManager()


        ke.getRandomProblem(problem_type = '_problem_factorial_0')


        doc_style: xv_doc
        """

        n = Symbol('n', integer=True, nonnegative=True)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        statement = f""""""
        expr = f""
        problemTemplate.add_problem(statement)
        problemTemplate.add_problem(expr, 'text/html')

        zero_permutation = f"P_{{0}}^{{0}}"

        expr_permutation = f"P_{{{n}}}^{{{n}}}"
        expr_expanded1 = latex_fraction(f"{n}!", f"({n}-{n})!")

        expr_expanded2 = latex_fraction(f"{n}!", "0!")
        expr_expanded3 = latex_fraction("1", "0!")
        answer = 1
        n_factorial_expanded = f"""
        {n} * ({n-1}) * ({n-2}) \\cdots * 3 * 2 * 1
        """.strip()


        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Find value of factorial of zero: $\\displaystyle 0!$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        <h2>First proof:</h2>
        If we have zero items, there is one way of arranging it - by doing nothing.
        Hence,

        $\\displaystyle {zero_permutation} =  0! = 1 $

        <h2>Alternate proof (a weak one):</h2>
        Let {n} is a positive integer.

        $\\displaystyle {expr_permutation} = {expr_expanded1} $

        $\\displaystyle \\implies {n_factorial_expanded} = {expr_expanded2} $

        $\\displaystyle \\implies {n}! = {expr_expanded2} $

        Dividing both sides with $\\displaystyle {n}!$,

        $\\displaystyle \\implies 1 = {expr_expanded3} $

        $\\displaystyle \\implies 0! = {answer}$
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {answer}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

        pass



    def _problem_permutation_term(self, *args, **kwargs):
        """
        Minimum Grade: 6
        Maximum Grade: 12

        doc_style: xv_doc
        """
        binomial_helper = self.base_helper.get_algebra_helper().get_binomial_helper()
        n = binomial_helper.n
        k = binomial_helper.k
        nPk = binomial_helper.get_permutation_latex(n, k)
        exprP = binomial_helper.get_permutation_expr(n , k)

        permutation_formula = binomial_helper.get_permutation_formula_latex(n, k)
        permutation_formula = list_to_str(permutation_formula, sep = "\n\n")

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Write expression for arranging ${latex(k)}$ items from a collection of ${latex(n)}$ items

        $\\displaystyle {nPk}$

        Note: $\\displaystyle {nPk}
        $ is read as ${latex(n)}$ permutation ${latex(k)}$.
        """
        problemTemplate.add_problem(problem_s, 'text/html')

        soln_s = f"""
        {permutation_formula}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        $\\displaystyle
        {latex(exprP)}
        $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')


        return problemTemplate
        pass


    def _problem_factorial_expression(self, *args, **kwargs):
        """
        Minimum Grade: 6
        Maximum Grade: 12

        doc_style: xv_doc
        """
        binomial_helper = self.base_helper.get_algebra_helper().get_binomial_helper()
        n = binomial_helper.n
        result = binomial_helper.get_factorial_statements(n)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problemTemplate.add_problem(result['prob_s'], 'text/html')
        problemTemplate.add_solution(result['soln_s'], 'text/html')
        problemTemplate.add_answer(result['answer_s'], 'text/html')

        return problemTemplate
        pass


