'''
Created on 12-Jul-2021

@author: bthakur

TR0 - simplify expression
TR1 - sec-csc to cos-sin
TR2 - tan-cot to sin-cos ratio
TR2i - sin-cos ratio to tan
TR3 - angle canonicalization
TR4 - functions at special angles
TR5 - powers of sin to powers of cos
TR6 - powers of cos to powers of sin
TR7 - reduce cos power (increase angle)
TR8 - expand products of sin-cos to sums
TR9 - contract sums of sin-cos to products
TR10 - separate sin-cos arguments
TR10i - collect sin-cos arguments
TR11 - reduce double angles
TR12 - separate tan arguments
TR12i - collect tan arguments
TR13 - expand product of tan-cot
TRmorrie - prod(cos(x*2**i), (i, 0, k - 1)) -> sin(2**k*x)/(2**k*sin(x))
TR14 - factored powers of sin or cos to cos or sin power
TR15 - negative powers of sin to cot power
TR16 - negative powers of cos to tan power
TR22 - tan-cot powers to negative powers of sec-csc functions
TR111 - negative sin-cos-tan powers to csc-sec-cot

use finite_difference

check https://github.com/sympy/sympy/issues/11155

>>> from sympy import *
>>> y = symbols('y')
>>> U = Function('U')(y)
>>> dU = Function('\delta U')(y)
>>> diff(U + dU,y).expand()
Derivative(U(y), y) + Derivative(\delta U(y), y)

https://stackoverflow.com/questions/46411124/how-to-rewrite-an-abstract-derivative-of-a-sum-as-a-sum-of-derivatives-in-sympy/46411350

'''

import inspect
import traceback
import random
from sympy import (latex, Symbol, symbols,
                   Rational, Eq,
                   )

from sympy import (cos, sin, tan, sec, csc, cot,
                   exp, I, pi,
                   sqrt,
                   simplify, trigsimp, solve,
                   UnevaluatedExpr as ueval)
import sympy

from itertools import product

from sympy.simplify.fu import (TR111,TR22,TR16,TR15,TR14,
        TRmorrie,TR13,TR12i,TR12,TR11,TR10i,TR10,TR9,
        TR8,TR7,TR6,TR5,TR4,TR3,TR2i,TR2,TR1,TR0, 
        TRpower, fu)

# _funct_simp_transform_helper        
# FuncSimpTransformHelper        


from xv.util import UtilClass

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

    def get_simp_transforms(self):
        # https://xaktly.com/ToughIntegrals.html
        
        methods = [
            self.TR_SIMP_1,
            self.TR_SIMP_2,
            self.TR_SIMP_3,
            self.TR_SIMP_4,
            ]
        
        identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}"
        random_method = self.get_no_repeat_random_choice(identifier, methods)
        result = random_method()
        return result
        pass
    
    def TR_SIMP_1(self):
        from sympy.abc import x, A, B
        theta = Symbol(r"\theta")
        y = Symbol(r"y", positive = True)
        
        expr_sqr = 1 - x**2
        expr = sqrt(expr_sqr)
        subs_dict = {x: cos(theta), sin(theta): y}
        subs_dict_reversed = {v:k for k, v in subs_dict.items()}
        tr_func = TR6
        expr_subs = expr.subs(subs_dict)
        expr_transform = tr_func(expr_subs)
        expr_transform_subs = simplify(expr_transform.subs(subs_dict))
        expr_transform_subs = expr_transform_subs.subs(subs_dict_reversed)
        
        
        soln_s = f"""
        $\\displaystyle {latex(expr)} $
        
        Let $\\displaystyle {latex(x)} = {latex(cos(theta))} $
        
        $\\displaystyle = {latex(expr_subs)} $
        
        $\\displaystyle = {latex(expr_transform)} $
        
        $\\displaystyle = {latex(expr_transform_subs)} $
        
        """
        
        
        result = {
            'expr': expr,
            'soln_s': soln_s,
            'expr_transform': expr_transform_subs,
            }
        
        return result
        pass
    
    
    def TR_SIMP_2(self):
        from sympy.abc import x, A, B
        theta = Symbol(r"\theta")
        y = Symbol(r"y", positive = True)

        '''
        '''
        
        expr_sqr = x**2 + 1
        expr = sqrt(expr_sqr)
        subs_dict = {x: tan(theta), sec(theta): y}
        subs_dict_reversed = {v:k for k, v in subs_dict.items()}
        tr_func = TR22
        expr_subs = expr.subs(subs_dict)
        expr_transform = tr_func(expr_subs)
        expr_transform_subs = simplify(expr_transform.subs(subs_dict))
        expr_transform_subs = expr_transform_subs.subs(subs_dict_reversed)
        
        
        soln_s = f"""
        $\\displaystyle {latex(expr)} $
        
        Let $\\displaystyle {latex(x)} = {latex(cos(theta))} $
        
        $\\displaystyle = {latex(expr_subs)} $
        
        $\\displaystyle = {latex(expr_transform)} $
        
        $\\displaystyle = {latex(expr_transform_subs)} $
        
        """
        
        result = {
            'expr': expr,
            'soln_s': soln_s,
            'expr_transform': expr_transform_subs,
            }
        
        return result
        pass
    
    def TR_SIMP_3(self):
        from sympy.abc import x, A, B
        theta = Symbol(r"\theta")
        y = Symbol(r"y", positive = True)

        expr_sqr = x**2 - 1
        expr = sqrt(expr_sqr)
        subs_dict = {x: sec(theta), tan(theta): y}
        subs_dict_reversed = {v:k for k, v in subs_dict.items()}
        tr_func = TR16
        expr_subs = expr.subs(subs_dict)
        expr_transform = tr_func(TR1(expr_subs))
        expr_transform_subs = simplify(expr_transform.subs(subs_dict))
        expr_transform_subs = expr_transform_subs.subs(subs_dict_reversed)
        
        
        soln_s = f"""
        $\\displaystyle {latex(expr)} $
        
        Let $\\displaystyle {latex(x)} = {latex(cos(theta))} $
        
        $\\displaystyle = {latex(expr_subs)} $
        
        $\\displaystyle = {latex(expr_transform)} $
        
        $\\displaystyle = {latex(expr_transform_subs)} $
        
        """
        
        result = {
            'expr': expr,
            'soln_s': soln_s,
            'expr_transform': expr_transform_subs,
            }
        
        return result
        pass
   
   
    def TR_SIMP_4(self):
        from sympy.abc import x, A, B
        theta = Symbol(r"\theta")
        y = Symbol(r"y", positive = True)

        '''
        '''
        
        expr = 2 * x - x**2
        x1_sqr = (x - 1) ** 2
        expr_transform = 1 - x1_sqr
        
        part_result = self.TR_SIMP_1()
        
        
        soln_s = f"""
        $\\displaystyle {latex(expr)} $
        
        $\\displaystyle = - \\left( {latex(0 - expr)} \\right) $
        
        $\\displaystyle = - \\left( {latex(0 - expr)} + 1 - 1\\right)$
        
        $\\displaystyle = - \\left( {latex(x1_sqr)} - 1\\right)$
        
        $\\displaystyle = 1 - {latex(x1_sqr)} $
        
        $\\displaystyle = {latex(expr_transform)} $
        
        {part_result['soln_s']}
        
        """
        
        result = {
            'expr': expr,
            'soln_s': soln_s,
            'expr_transform': expr_transform,
            }
        
        return result
        pass
    
    
    def get_sum_of_terms_transforms(self):
        
        from sympy.abc import x, y, A, B
        from sympy import S
        
        den_factor1 = x ** 2
        den_factor2 = x ** 2 + 25

        coeffs = symbols('a, b, c, d')
        
        term_denoms = [den_factor2, den_factor2, den_factor1, x]
        term_nums = [coeff for coeff in coeffs]
        term_nums[0] = term_nums[0] * x
        
        tr_func = self.TR_SPLIT_1
        
        x0s = [0, 1, -1, 2]
        k = 1
        
        result = tr_func(x, coeffs, term_nums, term_denoms, x0s, k)
        return result
        pass

    def TR_SPLIT_1(self, x, coeffs, term_nums, term_denoms, x0s, k):
        den = sympy.factor(sympy.lcm(term_denoms))
        expr = 1 / den
        
        terms_list = []
        terms_str = []
        x_expr = 0
        for idx, term_denom in enumerate(term_denoms):
            mul_factor = sympy.cancel(den / term_denom)
            item_expr = term_nums[idx] * mul_factor
            x_expr = x_expr + item_expr
            
            term = term_nums[idx] / term_denom
            terms_list.append(term)
            terms_str.append(f"{latex(term)}")
            pass

        from functools import reduce
        terms = reduce(lambda x, y: x + y, terms_list, 1)

        terms_str = " + ".join([term for term in terms_str])
        terms_str = f"$\\displaystyle {terms_str}$"

        eq_gross = Eq(expr, terms)
        eq = Eq(x_expr, k)

        eqs = []
        eqs_str = []
        for x0 in x0s:
            eq_item = eq.subs({x: x0})
            
            eq_item_str = f"""
            $\\displaystyle 
            {latex(eq_item)} 
            \\qquad \\text{{for}} \\; {latex(x)} = {x0} 
            $
            """
            
            eqs.append(eq_item)
            eqs_str.append(eq_item_str.strip())
            pass

        eqs_str = "\n\n".join(eqs_str)

        A, B = sympy.linear_eq_to_matrix(eqs, *coeffs)
        X = sympy.Matrix(coeffs)

        eq_matrix = Eq(ueval(A) * ueval(X), ueval(B))
        
        eqs_soln = solve(eqs, coeffs)
        
        expr_transform = [term.subs(eqs_soln) for term in terms_list]
        
        soln_items = []
        soln_item = f"""
        $\\displaystyle {latex(expr)} $
        
        We want to transform the above expression into the following expression.
        
        {terms_str}
        
        Now:
        
        $\\displaystyle {latex(eq_gross)} $
        
        $\\displaystyle \\implies {latex(eq)} $
        
        Substituting values for x:
        
        {eqs_str}
        
        Converting into matrix:
        
        $\\displaystyle {latex(eq_matrix)} $
        
        Solving the equations:
        {eqs_soln}
        
        Substitute value in expression: 
        
        $\\displaystyle {latex(expr)} $
        
        $\\displaystyle = {latex(expr_transform)} $
        
        
        """
        soln_items.append(soln_item.strip())
        
        soln_s = "\n\n".join(soln_items)

        result = {
            'expr': expr,
            'eq': eq,
            #'eqs': eqs,
            #'eqs_str': eqs_str,
            #'A': A,
            #'X': X,
            #'B': B,
            #'A': A,
            #'eq_matrix': eq_matrix,
            'soln_s': soln_s,
            }
        
        return result
        pass
    
    def _log_DELETED(self, sOut, to_print = None):
        if to_print is None:
            to_print = self.verbose
            
        if to_print:
            print(sOut)
    
            
    
    pass
#class FuncSimpTransformHelper:
