'''
Created on 19-Apr-2024

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

from sympy import (symbols, Symbol, limit, latex, Function,
                   Limit, oo, Derivative, simplify,
                   Eq,
                   Derivative, Integral,
                   exp, log, expand_log,
                   Mul, Add, Pow, UnevaluatedExpr,
                   Sum, binomial, factorial, Rational, 
                   UnevaluatedExpr as ueval
                   )

from sympy import (sin, cos, tan, cot, sec, csc,
                   asin, acos, atan, acot, asec, acsc,
                   sinh, cosh, tanh, coth, sech, csch,
                   asinh, acosh, atanh, acoth, asech, acsch,
                   )

import sympy as sp        

from xv.km.util import latex_fraction
from xv.util import UtilClass

# _std_integration_by_reversal_helper
# StdIntegrationByReversalHelper

from ._std_integration_base_helper import StdIntegrationBaseHelper

class StdIntegrationByReversalHelper(StdIntegrationBaseHelper):
    '''
    integration of standard functions by reversing the result of differntiation
    '''    

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        pass

    
    def _get_std_solution(self, F, x, x_func, C):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        if x == x_func:
            return self._reverse_diff_with_x_as_integration(F, x, C)
        
        return self._reverse_diff_with_x_func_as_integration(F, x, x_func, C)
        pass

    def _reverse_diff_with_x_as_integration(self, F, x, C):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        try:
            DF = Derivative(F, x)
            dFdx = DF.doit().simplify()
            
            IdFdx = Integral(dFdx, x) 
            
            IdFdx = Integral(dFdx, x) 
            IdFdx_doit = IdFdx.doit()
            
            
            prob_s = f"""
            Find integral of $\\displaystyle \\Large {{ {latex(dFdx)} }}
            $ with respect to $\\displaystyle {latex(x)}$.
            """
            
            soln_stmts = []
            
    
            soln_stmt = f"""
            $\\displaystyle
            \\begin{{aligned}}
            
            & {latex(DF)} = {latex(dFdx)}
            \\\\ \\\\
            
            \\implies  
            & {latex(IdFdx)} = {latex(F)} + {latex(C)}
            
            \\end{{aligned}}
            $
            
            """
            soln_stmts.append(soln_stmt.strip())
            
            
            if simplify(F) != simplify(IdFdx_doit):
                soln_stmt = f"""
                Warning: 
                ${latex(IdFdx)} = {latex(IdFdx_doit)} != {latex(F)}$
                """
                soln_stmts.append(soln_stmt.strip())
                pass
            
            
            answer_s = f"""
            $\\displaystyle {latex(IdFdx_doit)} + {latex(C)}$
            """
    
            soln_s = "\n\n".join(soln_stmts)
    
            result = {
                'prob_s': prob_s.strip(),
                'soln_s': soln_s.strip(),
                'answer_s': answer_s.strip(),
                }
            
            return result
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            pass
   
        return None
        pass


    def _reverse_diff_with_x_func_as_integration(self, F, x, x_func, C):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        try:
            z = Symbol('z')
            dz = Symbol(f"d{latex(z)}")
            dx = Symbol(f"d{latex(x)}")

            z_eq = Eq(z, x_func)
            
            dzdx = Derivative(x_func, x).doit()
            
            dzdx_eq = Eq(Derivative(z, x), dzdx)
            dx_eq = Eq(1 / dzdx * dz, dx)
            dz_eq = Eq(dzdx * dx, dz)

            DF = Derivative(F  / dzdx, x)
            dFdx = DF.doit().simplify()
            
            IdFdx = Integral(dFdx, x) 
            IdFdx_doit = IdFdx.doit()

            G = F.subs(x_func, z)
            
            
            DG = Derivative(G, z)
            dGdz = DG.doit().simplify()
            
            IdGdz = Integral(dGdz, z) 
            
            IdGdz_by_dzdx = Integral(dGdz / dzdx, z) 
            
            prob_s = f"""
            Find integral of $\\displaystyle \\Large {{ {latex(dFdx)} }}
            $ with respect to $\\displaystyle {latex(x)}$.
            """
            
            
            soln_stmts = []
            
            soln_stmt = f"""
            $\\displaystyle
            \\begin{{aligned}}
            
            \\text{{Let }} 
            & {latex(z_eq)}
            \\qquad \\cdots \\text{{ (i) }} 
            \\\\ \\\\
            
            \\implies  
            & {latex(dzdx_eq)}
            \\\\ \\\\
            
            \\implies  
            & {latex(dx_eq)}
            \\qquad \\cdots \\text{{ (ii) }} 
            
            \\end{{aligned}}
            $
            
            """
            soln_stmts.append(soln_stmt.strip())
            
    
            soln_stmt = f"""
            We know
            
            $\\displaystyle
            \\begin{{aligned}}
            
            & {latex(DG)} = {latex(dGdz)}
            \\\\ \\\\
            
            & \\implies  
            {latex(IdGdz)} = {latex(G)} 
            \\\\ \\\\
            
            & \\implies  
            {latex(IdGdz_by_dzdx)} = {latex(1 / dzdx)} \\cdot {latex(G)} 
            \\qquad \\text{{ multiply both sides by }} {latex(1/dzdx)}
            
            \\end{{aligned}}
            $
            
            """
            soln_stmts.append(soln_stmt.strip())
            
            
            soln_stmt = f"""
            Substituting values of ${latex(z)}$ and ${latex(dz)}$ from (i) and (ii)
            
            $\\displaystyle
            \\begin{{aligned}}
            
            {latex(IdFdx)} 
            & = {latex(1 / dzdx)} \\cdot {latex(F)} 
            \\\\ \\\\
            
            & = {latex(F / dzdx)} + {latex(C)}
            \\qquad \\text{{ added integration constant }} {latex(C)}
            \\\\ \\\\
            
            \\end{{aligned}}
            $
            
            """
            soln_stmts.append(soln_stmt.strip())
            
            if simplify(F / dzdx) != simplify(IdFdx_doit):
                soln_stmt = f"""
                Warning: 
                ${latex(IdFdx)} = {latex(IdFdx_doit)} != {latex(F / dzdx)}$
                """
                soln_stmts.append(soln_stmt.strip())
                pass
            
            answer_s = f"""
            $\\displaystyle {latex(IdFdx_doit)} + {latex(C)}$
            """
    
            soln_s = "\n\n".join(soln_stmts)
    
            result = {
                'prob_s': prob_s.strip(),
                'soln_s': soln_s.strip(),
                'answer_s': answer_s.strip(),
                }
            
            return result
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            pass
   
        return None
        pass

    

    def get_standrad_integral_by_reversal(self, index = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        if index is None:
            random_choices = list(range(1, 8))
    
            identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}.random_choices"
            random_choice = self.get_no_repeat_random_choice(identifier, random_choices)
        else:
            random_choice = index
            
        x = self.x
        
        
        if random_choice == 1:
            return self._get_xn(x = x)
        
        if random_choice == 2:
            return self._get_log(x = x)
        
        if random_choice == 3:
            return self._get_exp(x = x)
        
        if random_choice == 4:
            return self._get_asin(x = x)
        
        if random_choice == 5:
            return self._get_acos(x = x)
        
        if random_choice == 6:
            return self._get_atan(x = x)
        
        if random_choice == 7:
            return self._get_a_pow_x(x = x)
        
        pass
    
    
    def _get_xn(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        if x is None:
            x = self.x

        try:
            x_func = self._get_linear_x_func(x)    
            if x == x_func:
                return self._get_xn_wrt_x(x)
                
            C = self.C
            F = 1 / x_func
            return self._get_std_solution(F, x, x_func, C)
    
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            pass
   
        return None
        pass
 
    def _get_xn_wrt_x(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        if x is None:
            x = self.x

        x_func = x 
            
        C = self.C
        n = self.n
        
        F = x_func ** n
        DF = Derivative(F, x)
        dFdx = DF.doit().simplify()
        
        IdFdx = Integral(dFdx, x) 
        
        F_mod = F.subs(n, n + 1)
        IdFdx_mod = Integral(dFdx.subs(n, n + 1), x) 
        
        F = x_func ** n
        Intg_F = Integral(F, x) 
        Intg_F_doit = x_func ** (n + 1) / (n+ 1)
        
        prob_s = f"""
        Find integral of $\\displaystyle {latex(dFdx)}
        $ with respect to $\\displaystyle {latex(x)}$.
        """
        
        soln_stmts = []
        

        soln_stmt = f"""
        $\\displaystyle
        \\begin{{aligned}}
        
        & {latex(DF)} = {latex(dFdx)}
        \\\\ \\\\
        
        \\implies  
        & {latex(IdFdx)} = {latex(F)}
        \\\\ \\\\
        
        \\implies  
        & {latex(IdFdx_mod)} = {latex(F_mod)}
        \\qquad \\text{{ substitute }} {latex(n)} \\text{{ with }} {latex(n + 1)} 
        \\\\ \\\\
        
        \\implies  
        & {latex(Intg_F)} = {latex(Intg_F_doit)} 
        \\qquad \\text{{ divide both sides with }} {latex(n + 1)} 
        \\\\ \\\\
        
        \\implies  
        & {latex(Intg_F)} = {latex(Intg_F_doit)} + {latex(C)}
        \\qquad \\text{{ added integration constant }} {latex(C)}
        
        \\end{{aligned}}
        $
        
        """
        soln_stmts.append(soln_stmt.strip())
        
        
        
        answer_s = f"""
        $\\displaystyle {latex(Intg_F_doit)} + {latex(C)}$
        """

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

        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
    

    def _get_log(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        if x is None:
            x = self.x

        x_func = self._get_linear_x_func(x)    
            
        C = self.C
        F = log(x_func)
        return self._get_std_solution(F, x, x_func, C)
    
    def _get_exp(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        x_func = self._get_linear_x_func(x)    
            
        C = self.C
        F = sp.exp(x_func)
        return self._get_std_solution(F, x, x_func, C)
    
    def _get_asin(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        x_func = self._get_linear_x_func(x)    
            
        C = self.C
        F = asin(x_func)
        return self._get_std_solution(F, x, x_func, C)
        
    def _get_acos(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        x_func = self._get_linear_x_func(x)    
            
        C = self.C
        
        #for default function being asin
        #F = acos(x_func)
        F = asin(x_func)
        
        return self._get_std_solution(F, x, x_func, C)
        
    def _get_atan(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self

        x_func = self._get_linear_x_func(x)    
            
        C = self.C
        F = atan(x_func)
        return self._get_std_solution(F, x, x_func, C)

    def _get_a_pow_x(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        if x is None:
            x = self.x

        x_func = self._get_linear_x_func(x)    
            
        a = self.a
            
        C = self.C
        F = a ** x_func 
        return self._get_std_solution(F, x, x_func, C)
    
    
        
    pass
#class StdIntegrationByReversalHelper:
