'''
Created on 01-Apr-2021
@author: bthakur
'''

import matplotlib.pyplot as plt
from xv.km.ui import SvgHelper

import numpy as np
import random
import inspect
import traceback


from sympy import (simplify, sympify, nsimplify,
                   log, symbols, Symbol,
                    Eq, Matrix, latex,
                   solve, Rational, diag,
                   Add, Mul, Pow,
                   UnevaluatedExpr)



from xv.km.util import (
                     latex_fraction,
                     wrap_latex_block,
                     wrap_latex_statement,
                     )



from ._multi_layered_fraction import MultiLayeredFraction

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

class ArithmeticExpressionManager(BaseManager):
    '''
    Expressions and their simplification


    Minimum Grade: 3
    Maximum Grade: 12


    Examples
    --------
    ke = ArithmeticExpressionManager()
    ke


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

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

    ke.printProblemTypes()
    ke.helpProblemType(0)


    doc_style: xv_doc
    '''


    _OPR_TYPE_COUNT = 10

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

    def __init__(self, *args, **kwargs):
        self.verbose = kwargs.get('verbose', None)
        self.base_helper = BaseHelper(**kwargs)
        self._equation = None
        self.x = symbols('x')
        pass


    def _add_expression(self, expressions, opr_type = None, n = None):
        if n is None:
            n = random.randint(1, 10) * random.choice([1, -1])

        if len(expressions) < 1:
            prev_expr = 1
            prev_latex_expr = f"{prev_expr}"
            pass
        else:
            prev_expr = expressions[-1][1]
            prev_latex_expr = f"\\left({{{latex(prev_expr)}}}\\right)"
            pass

        #prev_expr = UnevaluatedExpr(prev_expr)

        if (isinstance(n, int) or isinstance(n, Symbol)):
            n_expr = f"{n}"
        else:
            n_expr = f"\\left({{{latex(n)}}}\\right)"
            #n_expr = n_expr.replace("\\frac", "\\dfrac")


        if opr_type == 1:
            statement = f"Add $\\displaystyle {n_expr}$ to the expression"
            latex_expr = f"{prev_latex_expr} + {n_expr} "
            expr = Add(prev_expr, n, evaluate = False)

        elif opr_type == 2:
            statement = f"Add expression to $\\displaystyle {n_expr}$"
            latex_expr = f"{n_expr} + {prev_latex_expr}"
            expr = Add(n, prev_expr, evaluate = False)

        #subtract
        elif opr_type == 3:
            statement = f"Subtract $\\displaystyle {n_expr}$ from the expression"
            latex_expr = f"{prev_latex_expr} - {n_expr} "
            expr = Add(prev_expr, -n, evaluate = False)

        elif opr_type == 4:
            statement = f"Subtract the expression from $\\displaystyle {n_expr}$"
            latex_expr = f"{n_expr} - {prev_latex_expr}"
            expr = Add(n, -prev_expr, evaluate = False)

            #Multiply
        elif opr_type == 5:
            statement = f"Multiply $\\displaystyle {n_expr}$ with the expression"
            latex_expr = f"{n_expr} * {prev_latex_expr}"
            expr = Mul(n, prev_expr, evaluate = False)

        elif opr_type == 6:
            statement = f"Multiply the expression with $\\displaystyle {n_expr}$"
            latex_expr = f"{prev_latex_expr} * {n_expr} "
            expr = Mul(n, prev_expr, evaluate = False)

            #Divide
        elif opr_type == 7:
            statement = f"Divide the expression by $\\displaystyle {n_expr}$"
            latex_expr = latex_fraction(f"{latex(prev_expr)}", n_expr)
            try:
                expr = Mul(prev_expr, Rational(1, n), evaluate = False)
            except:
                expr = Mul(prev_expr, 1/n, evaluate = False)

        elif opr_type == 8:
            statement = f"Divide $\\displaystyle {n_expr}$ by the expression"
            latex_expr = latex_fraction(n_expr, f"{latex(prev_expr)}")
            expr = Mul(n, 1 / (prev_expr), evaluate = False)

        elif opr_type == 9:
            statement = f"Raise $\\displaystyle {n_expr}$ to the power of expression"
            latex_expr = f"{{{n_expr}}}^{{{prev_latex_expr}}}"
            expr = Pow(n, prev_expr, evaluate = False)

        elif opr_type == 10:
            statement = f"Raise expression to the power of $\\displaystyle {n_expr}$"
            latex_expr = f"{{{prev_latex_expr}}}^{{{n_expr}}}"
            expr = Pow(prev_expr, n, evaluate = False)
        else:
            raise Exception(f"Invalid opr_type = {opr_type}")

        expressions.append([statement, expr, latex_expr])
        self.expressions = expressions
        pass

    def _get_prob_soln_str(self, expressions):
        problem_arr = []
        solution_arr = []
        for idx, expression in enumerate(expressions):
            latex_s = latex(expression[1])
            latex_expr = expression[2]
            if idx == 0:
                soln_s = f"""
                    <strong>The given expression is:</strong>
                    $\\displaystyle {latex_s}$
                    """
                p_str = f"""
                    The given expression is $\\displaystyle {latex_s}$.
                    Do the followings:
                """
                problem_arr.append(p_str.strip())
            else:
                soln_s = f"""
                    <strong>{expression[0]}:</strong>

                    $\\displaystyle {latex_expr}$

                    = $\\displaystyle {latex_s}$
                    """
                problem_arr.append(expression[0])

            solution_arr.append(soln_s.strip())


            pass
        answer_str = latex(expressions[-1][1])
        answer_str = f"$\\displaystyle {answer_str}$"

        #problem_arr_str = "<ul><li>" + "</li><li>".join(problem_arr) + "</li></ul>"
        problem_arr_str = "</li><li>".join(problem_arr[1:])
        problem_arr_str = f"""
        <strong>{problem_arr[0]}</strong>
        <ol><li>{problem_arr_str}</li></ol>
        """
        solution_arr_str = f"\n\n".join(solution_arr)

        return problem_arr_str, solution_arr_str, answer_str
        pass

    def _get_random_n1_n2(self, loopCount = 1):
        n1 = random.randint(1, 100) * random.choice([1, -1])
        n2 = random.randint(1, 100) * random.choice([1, -1])
        mLoop = loopCount
        while random.choice([True, False]) and mLoop > 0:
            mLoop = mLoop - 1
            if random.choice([True, False]):
                n1 = f"- {n1}"
            if random.choice([True, False]):
                n2 = f"- {n1}"
            if random.choice([True, False]):
                temp = n1
                n1 = n2
                n2 = temp

            m1 = n1
            m2 = n2

            if random.choice([True, False]):
                op = random.choice(['+', '-', '*', '^', 'f'])
                if op == 'f':
                    m1 = latex_fraction(n1,n2)
                else:
                    m1 = f"{n1} {op} {{{n2}}}"

            if random.choice([True, False]):
                op = random.choice(['+', '-', '*', '^', 'f'])
                if op == 'f':
                    m2 = latex_fraction(n1,n2)
                else:
                    m2 = f"{n1} {op} {{{n2}}}"

            n1 = m1
            n2 = m2
            if not isinstance(n1, int):
                n1 = f"({n1})"
            else:
                if n1 < 0:
                    n1 = f"({n1})"

            if not isinstance(n2, int):
                n2 = f"({n2})"
            else:
                if n2 < 0:
                    n2 = f"({n2})"


            pass
        return n1, n2
        pass

    def _simplify_operator(self, exprs, op):
        if not op in exprs:
            return None

        while op in exprs:
            idx = exprs.index(op)

            if op == "**":
                exprs[idx] = exprs[idx-1] ** exprs[idx+1]
            elif op == "/":
                exprs[idx] = nsimplify(exprs[idx-1] / exprs[idx+1])
            elif op == "*":
                exprs[idx] = nsimplify(exprs[idx-1] * exprs[idx+1])
            elif op == "-":
                exprs[idx] = nsimplify(exprs[idx-1] - exprs[idx+1])
            elif op == "+":
                exprs[idx] = nsimplify(exprs[idx-1] + exprs[idx+1])

            del exprs[idx+1]
            del exprs[idx-1]
            pass

        exprs_str = " ".join([str(expr) for expr in exprs])
        return exprs_str
        pass


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_multilayer_fraction',
            '_problem_multilayer_fraction_2',
            '_problem_root_format_to_exp_format',
            '_problem_inv_root_format_to_exp_format',
            '_problem_exp_format_to_root_format',
            '_problem_type_root_exp_1',
            '_problem_type_root_exp_2',
            '_problem_multiple_operators',

            '_problem_statements_to_exprs',
            '_problem_write_expression_simple',
            '_problem_write_expression',
            '_problem_write_expression_advanced',
            '_problem_write_expression_advanced_2',

            '_problem_solve_expression_advanced',
            '_problem_expression_solver',

            ])
        pass

    def _problem_multilayer_fraction(self, *args, **kwargs):
        """
        Simplification of a system of rational numbers


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_multilayer_fraction')


        doc_style: xv_doc
        """

        implies = symbols(u'\U000021D2')
        colors = plt.rcParams["axes.prop_cycle"]()

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

        nCount = random.randint(3, 10)

        mlf = MultiLayeredFraction()
        self.mlf = mlf

        idx = 0
        while idx < nCount:
            mlf.append_fractions()
            idx = idx + 1
            pass

        final_fraction = mlf.get_final_fraction()

        self.final_fraction = final_fraction

        statement = f"""
        Solve:
        $\\displaystyle \\large{{{final_fraction.cum_latex_fraction}}}$
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        result_s, result = self.mlf._repr_html_reverse_order()

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

        num_den_latex, value_latex, value = final_fraction.get_value_details()

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

                $\\displaystyle =  {value_latex} $

                $\\displaystyle = {value}$ """

        problemTemplate.add_solution(f"""

        $\\displaystyle  =$ {answer_s}""", 'text/html')

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

        return problemTemplate

    def _problem_multilayer_fraction_2(self, *args, **kwargs):
        """
        Simplification of a system of rational numbers


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_multilayer_fraction_2')


        doc_style: xv_doc
        """

        implies = symbols(u'\U000021D2')
        colors = plt.rcParams["axes.prop_cycle"]()

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

        nCount = random.randint(3, 10)

        mlf = MultiLayeredFraction()
        self.mlf = mlf

        idx = 0
        while idx < nCount:
            mlf.append_fractions_advanced()
            idx = idx + 1
            pass

        final_fraction = mlf.get_final_fraction()

        statement = f"""
        Solve:
        $\\displaystyle \\large{{{final_fraction.cum_latex_fraction}}}$
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        result_s, result = self.mlf._repr_html_reverse_order()

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

        num_den_latex, value_latex, value = final_fraction.get_value_details()

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

                $\\displaystyle =  {value_latex} $

                $\\displaystyle = {value}$ """

        problemTemplate.add_solution(f"""

        $\\displaystyle  =$ {answer_s}""", 'text/html')

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

        return problemTemplate

    def _problem_multilayer_fraction_NEW_NOT_USED(self, *args, **kwargs):
        """
        Simplification of a system of rational numbers


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_multilayer_fraction')


        doc_style: xv_doc
        """

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

        count = random.randint(3, 5)
        mlf = None
        mlfs = []
        for i in range(count):
            if mlf is None:
                n1 = random.randint(2, 9)
                n2 = random.randint(2, 9)
                p, q = random.sample([n1, n2], 2)
                p_latex = None
                q_latex = None
            else:
                n1 = random.randint(2, 9)
                n2 = random.randint(2, 9)
                pq = []

                pq.append([
                    UnevaluatedExpr(mlf.expr) * UnevaluatedExpr(n2),
                    f"{mlf.expr_latex} * {n1}",
                    ])

                pq.append([
                    UnevaluatedExpr(mlf.p) * UnevaluatedExpr(n2) / UnevaluatedExpr(mlf.q),
                    latex_fraction(f"{mlf.p_latex} * {n2}", mlf.q_latex)
                    ])

                pq.append([
                    UnevaluatedExpr(mlf.expr) * UnevaluatedExpr(n1)  / UnevaluatedExpr(n2),
                    latex_fraction(f"{mlf.expr_latex} * {n1}", n2)
                    ])

                pq.append([
                    UnevaluatedExpr(mlf.p) * UnevaluatedExpr(n1)  / ( UnevaluatedExpr(mlf.q) * UnevaluatedExpr(n2)),
                    latex_fraction(f"{mlf.p_latex} * {n1}", f"{mlf.q_latex} * {n2}")
                    ])

                pq.append([
                    UnevaluatedExpr(mlf.expr) / UnevaluatedExpr(n2),
                    latex_fraction(mlf.expr_latex, n2)
                    ])

                pq.append([
                    UnevaluatedExpr(n2) / UnevaluatedExpr(mlf.expr),
                    latex_fraction(n2, mlf.expr_latex)
                    ])

                pq.append([
                    UnevaluatedExpr(mlf.expr),
                    f"{mlf.expr_latex}",
                    ])

                n3 = random.sample(pq, 2)
                n4 = random.randint(2, 9)
                n3.append([n4, f"{n4}"])

                (p, p_latex), (q, q_latex)  = random.sample(n3, 2)
                pass

            mlf = MultiLayeredFraction(p,
                                       q,
                                       p_latex = p_latex,
                                       q_latex = q_latex,
                                       verbose = self.verbose)
            mlfs.append(mlf)
            pass

        mlfs.reverse()

        self.mlfs = mlfs

        prob_s = f"""
        Simplify:
        <h2>$\\displaystyle {mlfs[0].expr_latex} $</h2>
        """
        problemTemplate.add_problem(prob_s.strip(), "text/html")

        for idx, mlf in enumerate(mlfs):
            if idx == 0:
                soln_s = f"""
                <h2>$\\displaystyle {mlf.expr_latex} $</h2>
                """
            else:
                soln_s = f"""
                <h2>= $\\displaystyle {mlf.expr_latex} $</h2>
                """
            problemTemplate.add_solution(soln_s.strip(), 'text/html')


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

        return problemTemplate


    def _problem_root_format_to_exp_format(self, *args, **kwargs):
        """
        Converting root format as exponential format


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_root_format_to_exp_format')


        doc_style: xv_doc
        """

        nbase = random.randint(2, 5)
        ninv = random.randint(2, 10)

        wlb = wrap_latex_block
        wls = wrap_latex_statement
        wf = latex_fraction

        expr_porb = f"\sqrt[{ninv}]" + wlb(nbase)
        expr_porb = wls(expr_porb)

        expr_soln = wlb(nbase) + "^" + wf(1, ninv , dfrac_latex = False)
        expr_soln = wls(expr_soln)

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

        statement = f"""
        Express as exponent

        {expr_porb}
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        soln_s = f"""
        $\\displaystyle {ninv}$th root same as power $\\displaystyle {wf(1, ninv)}$

        Therefore:

        {expr_porb} \t=\t {expr_soln}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

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

        return problemTemplate
        pass

    def _problem_inv_root_format_to_exp_format(self, *args, **kwargs):
        """
        Converting inverse root format as exp format


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_inv_root_format_to_exp_format')


        doc_style: xv_doc
        """

        nbase = random.randint(2, 5)
        ninv = random.randint(2, 10)

        wlb = wrap_latex_block
        wls = wrap_latex_statement
        wf = latex_fraction

        nexp = wf(1, ninv ).replace("\\dfrac", "\\frac")

        expr_porb = f"\sqrt[{nexp}]" + wlb(nbase)
        expr_porb = wls(expr_porb)

        expr_soln = wlb(nbase) + "^" + wlb(ninv )
        expr_soln = wls(expr_soln)

        expr_1 = wlb(f"\\left({nbase}\\right)") + "^" + wf(1, f"1/{ninv}" )
        expr_1 = wls(expr_1)

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

        statement = f"""
        Express as exponent

        {expr_porb}
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        soln_s = f"""
        $\\displaystyle {wf(1, ninv)}$th root same as power $\\displaystyle {ninv}$

        Therefore:
        {expr_porb} \t=\t {expr_1} \t=\t {expr_soln}
        """

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

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

        return problemTemplate
        pass

    def _problem_exp_format_to_root_format(self, *args, **kwargs):
        """
        Converting root format as exponential format


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_exp_format_to_root_format')


        doc_style: xv_doc
        """

        nbase = random.randint(2, 5)
        ninv = random.randint(2, 10)

        wlb = wrap_latex_block
        wls = wrap_latex_statement
        wf = latex_fraction

        expr_porb = wlb(nbase) + "^" + wf(1, ninv , dfrac_latex = False)
        expr_porb = wls(expr_porb)

        expr_soln = f"\sqrt[{ninv}]" + wlb(nbase)
        expr_soln = wls(expr_soln)

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

        statement = f"""
        Express in root format

        {expr_porb}
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        soln_s = f"""
        power $\\displaystyle {wf(1, ninv)}$ is same as $\\displaystyle {ninv}$th root

        Therefore:

        {expr_porb} \t=\t {expr_soln}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

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

        return problemTemplate
        pass

    def _problem_type_root_exp_1(self, *args, **kwargs):
        """
        Express in exp format


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_type_root_exp_1')


        doc_style: xv_doc
        """

        #expr root(nbase, ninv1) ** nexp
        nbase = random.randint(2, 5)
        ninv = random.randint(2, 10)
        nres = random.randint(2, 10)
        nexp = ninv * nres

        wlb = wrap_latex_block
        wls = wrap_latex_statement
        wf = latex_fraction

        expr_porb = f"\sqrt[{ninv}]" + wlb(wlb(nbase) + "^" + wlb(nexp))

        expr_soln = wlb(nbase) + "^" + wlb(nres)

        expr_1 = wlb(wlb(nbase) + "^" + wlb(nexp))
        expr_1 = f"({expr_1})"  + "^" + wf(1, ninv )

        expr_2 = wlb(wlb(f"\\left({nbase}\\right)") + "^" + wf(nexp, ninv))

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

        statement = f"""
        Simplify

        $\\displaystyle {expr_porb}$
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        soln_s = f"""

        $\\displaystyle {expr_porb}$

        $\\displaystyle = {expr_1}$

        $\\displaystyle = {expr_2}$

        $\\displaystyle = {expr_soln}$

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

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

        return problemTemplate

        pass

    def _problem_type_root_exp_2(self, *args, **kwargs):
        """
        Simplification of a system of rational numbers


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_type_root_exp_2')


        doc_style: xv_doc
        """

        #expr root(nbase, ninv1) ** nexp
        nbase = random.randint(2, 5)
        ninv = random.randint(2, 10)
        nres = random.randint(2, 10)
        nexp = ninv * nres

        wlb = wrap_latex_block
        wls = wrap_latex_statement
        wf = latex_fraction

        expr_porb = wlb(f"\sqrt[{ninv}]" + wlb(nbase))
        expr_porb = wlb(f"({expr_porb})^ " + wlb(nexp))

        expr_soln = wlb(nbase) + "^" + wlb(nres)

        expr_1 = wlb(wlb(nbase) + "^" + wf(1, ninv))
        expr_1 = f"({expr_1})"  + "^" + wlb(nexp)

        expr_2 = wlb(wlb(f"\\left({nbase}\\right)") + "^" + wf(nexp, ninv))

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

        statement = f"""
        Simplify

        $\\displaystyle {expr_porb}$
        """
        problemTemplate.add_problem(statement.strip(), "text/html")

        soln_s = f"""

        $\\displaystyle {expr_porb}$

        $\\displaystyle = {expr_1}$

        $\\displaystyle = {expr_2}$

        $\\displaystyle = {expr_soln}$

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

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

        return problemTemplate
        pass



    def _problem_multiple_operators(self, *args, **kwargs):
        """
        Simplification of expression with multiple operators


        Minimum Grade: 3
        Maximum Grade: 8


        Examples
        --------
        from xv.math.algebra import ArithmeticExpressionManager
        ke = ArithmeticExpressionManager()


        ke.getRandomProblem(problem_type = '_problem_multiple_operators')


        doc_style: xv_doc
        """


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

        exprs = []
        operators = [('+', random.randint(0, 10)),
                     ('-', random.randint(0, 10)),
                     ('*', random.randint(0, 10)),
                     ('/', random.randint(1, 10)),
                     ('**', random.randint(1, 3)),
                     ]
        prev_opr = None
        while len(exprs) < 20:
            if len(exprs) > 4:
                if random.randint(1, 50) < 10:
                    break
            if len(exprs) > 0:
                if prev_opr == "**":
                    random_term = random.choice(operators[:-1])
                else:
                    random_term = random.choice(operators)

                exprs.extend(random_term)
                prev_opr = random_term[0]
            else:
                exprs.append(random.randint(2, 10))
            pass

        exprs_str = " ".join([str(expr) for expr in exprs])
        answer = nsimplify(sympify(exprs_str))

        statement = f"""
        Simplify
        $\\displaystyle {exprs_str}$
        """
        problemTemplate.add_problem(statement, "text/html")

        problemTemplate.add_solution(
            f"$\\displaystyle {exprs_str}$", "text/html")

        exprs_str = exprs_str.replace("**", "^")
        problemTemplate.add_solution(
            f"$\\displaystyle = {exprs_str}$", "text/html")

        exprs_str = self._simplify_operator(exprs, "**")
        if not exprs_str is None:
            problemTemplate.add_solution(
                f"$\\displaystyle = {exprs_str}$ \t\t simplify power", "text/html")
            pass

        exprs_str = self._simplify_operator(exprs, "/")
        if not exprs_str is None:
            problemTemplate.add_solution(
                f"$\\displaystyle = {exprs_str}$ \t\t simplify division", "text/html")
            pass

        exprs_str = self._simplify_operator(exprs, "*")
        if not exprs_str is None:
            problemTemplate.add_solution(
                f"$\\displaystyle = {exprs_str}$ \t\t simplify multiplication", "text/html")
            pass

        exprs_str = self._simplify_operator(exprs, "-")
        if not exprs_str is None:
            problemTemplate.add_solution(
                f"$\\displaystyle = {exprs_str}$ \t\t simplify subtraction", "text/html")
            pass

        exprs_str = self._simplify_operator(exprs, "+")
        if not exprs_str is None:
            problemTemplate.add_solution(
                f"$\\displaystyle = {exprs_str}$ \t\t simplify addition", "text/html")
            pass

        if exprs_str:
            confirm_answer = nsimplify(sympify(exprs_str))
            if confirm_answer != answer:
                soln_s = f"""
                Answer mismatch:
                {confirm_answer} != {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_statements_to_exprs(self, *args, **kwargs):
        n1, n2 = self._get_random_n1_n2(loopCount = 1)
        statements = [
            (f"Deduct $\\displaystyle {n1}$ from $\\displaystyle {n2}$",
             f"We have to deduct $\\displaystyle {n1}$, so we write it with minus sign:\n$\\displaystyle - {n1}$",
             f"Before $\\displaystyle - {n1}$, write $\\displaystyle {n2}$",
             f"So, the answer is:",
             f"$\\displaystyle {n2} - {n1}$"
             ),

            (f"Divide $\\displaystyle {n1}$ by $\\displaystyle {n2}$",
             f"We have to divide $\\displaystyle {n1}$, so we write $\\displaystyle {n1}$ as numerator:\n$\\displaystyle {n1}$",
             f"Put the divider $\\displaystyle {n2}$ as denominator:",
             f"So, the answer is:",
             f"$\\displaystyle {latex_fraction(n1, n2)}$"
             ),

            (f"Add $\\displaystyle {n1}$, \t$\\displaystyle {n2}$ times",
             f"$\\displaystyle {n1}$ + $\\displaystyle {n1}$ + $\\displaystyle {n1}$ + ..... $\\displaystyle {n2}$ times",
             f"In short, the same thing can be written as :",
             f"$\\displaystyle {n1}$ * $\\displaystyle {n2}$"
             ),

            (f"Multiply $\\displaystyle {n1}$, \t$\\displaystyle {n2}$ times",
             f"$\\displaystyle {n1}$ * $\\displaystyle {n1}$ * $\\displaystyle {n1}$ * ..... $\\displaystyle {n2}$ times",
             f"In short, the same thing can be written as :",
             f"$\\displaystyle {n1}^{{{n2}}}$"
             ),

        ]

        statement = random.choice(statements)


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

        problem_s = f"""
        Write mathematical expression for the following:
        {statement[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), "text/html")
        soln_s = "\n\n".join(statement[1:])

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

        answer_str = statement[-1]

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

        return problemTemplate

    def _problem_write_expression_simple(self, *args, **kwargs):
        expressions = []

        item_count = random.randint(3, 10)
        while item_count > 0:
            item_count = item_count - 1
            opr_type = random.choice([1, 2, 3, 4, 7, 8])
            self._add_expression(expressions, opr_type)

        (problem_arr_str,
         solution_arr_str,
         answer_str) = self._get_prob_soln_str(expressions)

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

        statement = problem_arr_str.strip()
        problemTemplate.add_problem(statement, "text/html")

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


        problemTemplate.add_answer(answer_str, 'text/html')

        return problemTemplate

    def _problem_write_expression(self, *args, **kwargs):
        expressions = []

        opr_types = list(range(1, ArithmeticExpressionManager._OPR_TYPE_COUNT + 1))
        opr_types = random.sample(opr_types, random.randint(1, len(opr_types)))
        for opr_type in opr_types:
            self._add_expression(expressions, opr_type)

        (problem_arr_str,
         solution_arr_str,
         answer_str) = self._get_prob_soln_str(expressions)

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

        statement = problem_arr_str.strip()
        problemTemplate.add_problem(statement, "text/html")

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


        problemTemplate.add_answer(answer_str, 'text/html')

        return problemTemplate

    def _problem_write_expression_advanced(self, *args, **kwargs):
        expressions = []

        opr_types = list(range(1, ArithmeticExpressionManager._OPR_TYPE_COUNT + 1))
        random.shuffle(opr_types)
        for opr_type in opr_types:
            self._add_expression(expressions, opr_type)

        startIndex = random.randint(0, len(expressions) - 3)
        expressions = expressions[startIndex:]

        if len(expressions) > 3:
            endIndex = random.randint(3, len(expressions))
            expressions = expressions[:endIndex]


        self.expressions = expressions

        (problem_arr_str,
         solution_arr_str,
         answer_str) = self._get_prob_soln_str(expressions)

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

        statement = problem_arr_str.strip()
        problemTemplate.add_problem(statement, "text/html")

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


        problemTemplate.add_answer(answer_str, 'text/html')

        return problemTemplate


    def _problem_write_expression_advanced_2(self, *args, **kwargs):
        expressions = []

        opr_types = list(range(1, ArithmeticExpressionManager._OPR_TYPE_COUNT + 1))
        opr_types = random.sample(opr_types, 5)
        for opr_type in opr_types:
            self._add_expression(expressions, opr_type)

        temp_expressions = random.sample(expressions, 1)

        n = random.choice(expressions)

        self._add_expression(temp_expressions,
                             opr_type =  random.choice(opr_types),
                             n = n[1]
                             )

        if random.choice([True, False]):
            n = random.choice(expressions)
            self._add_expression(temp_expressions,
                                 opr_type =  random.choice(opr_types),
                                 n = n[1]
                                 )


        (problem_arr_str,
         solution_arr_str,
         answer_str) = self._get_prob_soln_str(temp_expressions)

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

        statement = problem_arr_str.strip()
        problemTemplate.add_problem(statement, "text/html")

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


        problemTemplate.add_answer(answer_str, 'text/html')

        return problemTemplate


    def _problem_solve_expression_advanced(self, *args, **kwargs):
        expressions = []

        opr_types = list(range(1, ArithmeticExpressionManager._OPR_TYPE_COUNT + 1))
        random.shuffle(opr_types)
        for opr_type in opr_types:
            self._add_expression(expressions, opr_type)

        startIndex = random.randint(0, len(expressions) - 3)
        expressions = expressions[startIndex:]

        if len(expressions) > 3:
            endIndex = random.randint(3, len(expressions))
            expressions = expressions[:endIndex]


        self.expressions = expressions
        prob_expr = sympify(expressions[-1][1], evaluate=False)
        self.prob_expr = prob_expr

        ae_helper = self.base_helper.get_arithmetic_helper().get_arithmetic_expression_helper()

        result = ae_helper.get_solution(prob_expr)


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

        problem_s = f"""
        Solve:

        $\\displaystyle {latex(result['prob_expr'])}$
        """
        problemTemplate.add_problem(problem_s, "text/html")

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


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

        return problemTemplate

    def _problem_expression_solver(self, *args, **kwargs):
        ae_solver = self.base_helper.get_arithmetic_helper().get_arithmetic_expression_solver()

        expr_recs = ae_solver.get_random_expr_data()
        result = ae_solver.get_soluton(expr_recs)

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

        prob_s = f"""
        {result['prob_s']}
        """
        problemTemplate.add_problem(prob_s.strip(), "text/html")

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


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

        return problemTemplate

    def _problem_TEMPLATE(self, *args, **kwargs):

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

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

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


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

        return problemTemplate
