'''
Created on 20-Jan-2022

@author: bthakur
'''

import codecs
import inspect
import traceback
import random
import numpy as np
from sympy import (latex, Symbol, symbols,
                    Rational,
                    sin, cos, tan,
                    asin, acos, atan,
                    sinh, cosh, tanh,
                    log, exp, sqrt,
                   diff, factor, 
                   Function, Eq,
                   Add, 
                   simplify, solve, factorial,
                   Sum, binomial
                   )

from sympy import (symbols, Symbol, Function,
                   latex, Eq, Matrix, latex, sympify)

from sympy.core.numbers import One, Integer
from sympy.series import series

from xv.km.util import latex_fraction
from sympy.core.expr import UnevaluatedExpr

from xv.km.ui import SvgHelper
from xv.util.data import CacheUtil

import matplotlib.pyplot as plt


from xv.km.kelements import KPrintElement


# algebric_expression_helper


from xv.util import UtilClass

class AlgebricExpressionHelper(UtilClass):
    
    _DEFAULT_TERM_COUNT = 5
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        pass

    def getRandomVars(self, dim):
        var_names = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.lower())[-1*dim:]
        
        mod_var_names = var_names.copy()
        if random.choice([True, False]):
            for var_name in var_names:
                mod_var_names.append(f"{var_name}**2")
                mod_var_names.append(f"{var_name}**3")
                pass

        if (len(var_names) > 1 and 
            random.choice([True, False])):
            for idx in range(len(var_names) - 1):
                var_name = var_names[idx] 
                next_var_name = var_names[idx + 1] 
                mod_var_names.append(f"{var_name}*{next_var_name}")
                pass

        random.shuffle(mod_var_names)
        var_names = mod_var_names[:dim]
        
        if random.choice([True, False]):
            var_names.sort(key=lambda x: str(x))
        
        #[symbols(v) for v in var_names]
        return Matrix(var_names)
        pass

    
    def getRandomMatrix(self, size):
        signs = np.random.choice([-1,1], size)
        arr = np.random.randint(0, 10, size) * signs
        A = Matrix(arr)
        return A
        pass

    def getEquations(self, A, X, B):
        try:
            eqs = []
            for i in range(A.rows):
                #print(f"""" i = {i}""")
                
                expr = (A[i, :] * X)[0]
                #print(f""""(A[i, :] * X)[0] = {(A[i, :] * X)[0]}""")
                
                eq = Eq(expr , B[i])
                #print(f"""" Eq(expr , B[i]) = {Eq(expr , B[i])} """)

                eqs.append(eq)
                pass
            return eqs
        except Exception as e:
            sError = f"""{inspect.stack()[0][3]} Error: 
            {e}
            {A} 
            {X} 
            {B} 
            """
            print(sError)
            print(traceback.format_exc())
            return None
        pass

    def getExpressions(self, A, X):
        try:
            expressions = []
            for i in range(A.rows):
                expr = (A[i, :] * X)[0]
                expressions.append(expr)
                pass
            return expressions
        except Exception as e:
            sError = f"""{inspect.stack()[0][3]} Error: 
            {e}
            {A} 
            {X} 
            """
            print(sError)
            print(traceback.format_exc())
            return None
        pass
    
    def get_latex_str(self, eqs):
        eqs_str = []
        for eq in eqs:
            #eqs_str.append(KPrintElement(latex(eq), 'latex/html'))
            eqs_str.append(latex(eq))
        return eqs_str
        pass
    
    
    def get_random_matrix_data(self, dimX = None, dimY = None):
        if dimX is None:
            dimX = random.randint(2, 5)
        if dimY is None:
            dimY = random.randint(2, 5)
            
        A = self.getRandomMatrix( (dimX, dimY) )
        X = self.getRandomVars(dimY)
        B = self.getRandomMatrix( (1, dimX) )
        
        eqs = self.getEquations(A, X, B)
        eqs_str = self.get_latex_str(eqs)
        
        A_str = KPrintElement(f"{latex(A)}", 'sympy_latex/html')
        X_str = KPrintElement(f"{latex(X)}", 'sympy_latex/html')
        B_str = KPrintElement(f"{latex(B)}", 'sympy_latex/html')
        
        expression = self.getExpressions(A, X)
        expression_str = self.get_latex_str(expression)
        
        
        self.A = A
        self.X = X
        self.B = B
        self.eqs = eqs
        
        return A, X, B, eqs, expression
            #, A_str, X_str, B_str, eqs_str, expression_str
        pass


    def get_multiply_expr(self, min_rows =  1, max_rows =  4):
        while True:
            X = self.getRandomVars(random.randint(min_rows, max_rows))
            A = self.getRandomMatrix( (X.rows, 1) )
            
            X_arr = X.T.tolist()[0]
            A_arr = A.T.tolist()[0]
            
            if len([item for item in A_arr if item != 0]):
                break
            pass
        
        A_X_expr = [
            X_arr[i] * A_arr[i]
            for i in range(len(X_arr))
            ]
        
        A_X_expr = [item for item in A_X_expr
                    if item != 0]
        
        A_X_expr_str = " + ".join([
            f"${latex(item)}$"
            for item in A_X_expr
            ])
        
        return (A, X, A_arr, X_arr, A_X_expr, A_X_expr_str)
        pass

    
    def get_division_steps(self, divisor, A1_X1_expr):
        divisor_str = f"${latex(divisor)}$".replace("\\frac", "\\dfrac")

        soln_step1 = []
        for item1 in A1_X1_expr:
            div_expr1 = latex_fraction(f"{latex(item1)}", divisor_str)
            soln_step1.append(f"$\\displaystyle {div_expr1}$")
            pass
        
        soln_step1_str = "\t " + "\n\n\t +\t".join(soln_step1)

        soln_step2 = []
        for item1 in A1_X1_expr:
            soln_step2.append(f"${latex(item1/(divisor))}$")
            pass
        
        soln_step2_str = "\t " + "\n\n\t +\t".join(soln_step2)
        soln_step2_str = soln_step2_str.replace("\\frac", "\\dfrac")
        
        soln_step3 = 0
        for item1 in A1_X1_expr:
            soln_step3 = soln_step3 + (item1/(divisor))
            pass
        
        soln_step3_str = f"${latex(soln_step3)}$"
        soln_step3_str = soln_step3_str.replace("\\frac", "\\dfrac")

        return ((soln_step1, soln_step2, soln_step3),
                (soln_step1_str, soln_step2_str, soln_step3_str) )       
        pass

    def get_division_steps_str(self, 
                                A1_X1_expr_str,
                                divisor_str,
                                division_steps_str
                                ):
        soln_s = f"""
        
        \t({A1_X1_expr_str}) \t/\t ({divisor_str})
        
        <div style="float: left; width: 40%; color:red; font-weight:bold;">=\t{division_steps_str[0]}
        </div><div style="float: left; padding-left:3%; width: 40%; color:blue; font-weight:bold; border-left: 1px solid red;">=\t{division_steps_str[1]}
        </div>
        
        <div style="clear:both;"></div>
        =\t{division_steps_str[2]}
        
        """
        return soln_s
        pass

    pass
#class AlgebricExpressionHelper:
