'''
Created on 12-Jul-2021

@author: bthakur

'''
import inspect
import traceback

import random
from xv.km.util import latex_fraction, latex_color
from xv.util import toIntOrFloat

from sympy import (Rational, sqrt, srepr, symbols, Symbol, limit, latex, Function,
                   Limit, oo, Derivative, Integral,
                   simplify, solve, expand,
                   exp, log, expand_log,
                   Mul, Add, Pow,
                   Sum, binomial, factorial, 
                   factor, sympify,
                   Eq, srepr
                   )

import sympy as sp
import numpy as np


from sympy import UnevaluatedExpr as ueval

from sympy import (acos, acot, acoth, acsc, asec, asin, 
                   asinh, atan, atan2, atanh, 
                   cos, cosh, cot, coth, csc, sec, 
                   sin, sinc, sinh, tan, tanh,
                   pi,
                   )

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

from ._derivative_solver import DerivativeSolver

from xv.util import run_with_timeout

# _surface_area_of_rotation_helper
# SurfaceAreaOfRotationHelper

from xv.util import UtilClass

class SurfaceAreaOfRotationHelper(UtilClass):
    
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        pass


    def get_random_surface_area_of_rotation(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, 4))
    
            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
        
        
        if random_choice == 1:
            index = self._get_sphere_surface_area()
        
        return None
        pass
        
    def get_random_surface_area_of_rotation_data_set(self, index = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        
        
        #parabola
        expr = sp.sqrt(x)
        x_limits = [1, 2]
        area_expr = None # 2 * sp.pi * x * sp.sqrt(1 + 4 * x**2)
        data_sets.append([expr, x, x_limits, area_expr])
        
        
        
        if index is None:
            indexes = list(range(len(data_sets)))
            identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}.data_sets"
            index = self.get_no_repeat_random_choice(identifier, indexes)
        
        return data_sets[index]
        pass



    def _get_sphere_surface_area(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: 
        """
        self._log(sInfo.strip())
        
        
        x = symbols('x')
        
        data_sets = []
        r = random.randint(1, 5)

        #sphere        
        expr = sp.sqrt(r ** 2 - x ** 2 )
        x_limits = [-r, r]
        area_expr = 2 * sp.pi * r
        data_sets.append([expr, x, x_limits, area_expr])
        
        SA = Symbol('SA')
        f = Function('f')
        f = f(x)
        
        y = Symbol('y')
        dSA = Symbol(f"d{latex(SA)}")
        dx = Symbol(f"d{latex(x)}")
        dy = Symbol(f"d{latex(y)}")
        df = Symbol(f"d{latex(f)}")
        dr = Symbol("dr")
        
        dr_ueval = sp.sqrt(dx ** 2 + dy ** 2)
        dr_ueval_subs = sp.sqrt(dx ** 2 + df ** 2)
        
        problem_items = []
        answer_items = []
        soln_items = []

        x0, x1 = x_limits

        problem_item = f"""
        Find the surface area obtained by rotating the curve $\\displaystyle 
        {latex(f)} = {latex(expr)}$ along x-axis between $\\displaystyle 
        {latex(x)} = {latex(x0)}$ and $\\displaystyle 
        {latex(x)} = {latex(x1)}$.
        """
        problem_items.append(problem_item.strip())

        df_dx = Derivative(f, x)


        dexpr_dx = Derivative(expr, x)
        dexpr_dx_doit = dexpr_dx.doit()

        sqrt_term = sp.sqrt(1 + dexpr_dx_doit ** 2).simplify()
        
        if area_expr is None:
            area_expr = (2 * sp.pi * expr * sqrt_term).simplify()

        soln_item = f"""
        The surface area by rotating curve along x-axis
        
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(dSA)} 
        & = 2 {latex(sp.pi)} {latex(y)} \\cdot  {latex(dr)}   
        \\\\ \\\\
        
        & = 2 {latex(sp.pi)} {latex(y)} \\cdot {latex(dr_ueval)}  
        \\\\ \\\\
        
        & = 2 {latex(sp.pi)} {latex(f)} \\cdot {latex(dr_ueval_subs)}  
        \\\\ \\\\
        
        & = {latex(2 * sp.pi * f)} \\cdot {latex(sp.sqrt(1 + df_dx ** 2))} \\; {latex(dx)}
        \\\\ \\\\
        
        & = {latex(2 * sp.pi)} 
        \\cdot \\left( {latex(expr)} \\right)
        \\cdot   
        {latex(sp.sqrt(1 + dexpr_dx ** 2))} \\; {latex(dx)}
        \\\\ \\\\
        
        & = {latex(2 * sp.pi)} 
        \\cdot \\left( {latex(expr)} \\right)  
        \\cdot 
        {latex(sp.sqrt(1 + dexpr_dx_doit ** 2))} \\; {latex(dx)}
        \\\\ \\\\
        
        & = {latex(2 * sp.pi)} 
        \\cdot \\left( {latex(expr)} \\right)  
        \\cdot 
        {latex(sqrt_term)} \\; {latex(dx)}
        \\\\ \\\\
        
        & = {latex(area_expr)} 
        \\qquad \\cdots \\text{{ (i) }}
        \\\\ \\\\
        
        \\end{{aligned}} 
        $
        """
        soln_items.append(soln_item.strip())


        Intgl_surf_area = Integral(area_expr, x)
        method = Intgl_surf_area.doit
        timeout = 10
        Intgl_surf_area_doit = run_with_timeout(
                          f'Thread_surf_area_intgration_doit', 
                          method, 
                          (), 
                          timeout = timeout, 
                          verbose = self.verbose)
        
        if not Intgl_surf_area_doit:
            sOut = f""" 
            Failed to compute integration in {timeout} seconds 
            area_expr = {area_expr}
            """
            self._log(sOut)
            return None

        
        SA1_ueval = Intgl_surf_area_doit.subs(x, ueval(x1))
        SA0_ueval = Intgl_surf_area_doit.subs(x, ueval(x0))
        
        SA1 = Intgl_surf_area_doit.subs(x, x1).simplify()
        SA0 = Intgl_surf_area_doit.subs(x, x0).simplify()
        
        surf_area = Integral(area_expr, (x, x0, x1)).doit()  #abs(SA1 - SA0)
        abs_surf_area = abs(surf_area)

        if isinstance(SA0, type(sp.nan)) or isinstance(SA1, type(sp.nan)):
            SA0_SA1_stmt = ""
        else:
            SA0_SA1_stmt = f"""
            & = 
            \\left| 
            \\left( {latex(SA1)} \\right) - \\left( {latex(SA0)} \\right)
            \\right|
            \\\\ \\\\
    
            & = 
            \\left| 
            \\left( {latex(SA1 - SA0)} \\right)
            \\right|
            \\\\ \\\\
            """
            pass

        
        soln_item = f"""
        $\\displaystyle 
        \\begin{{aligned}}

        \\implies 
        {latex(SA)} 
        & = \\left| {latex(Integral(area_expr, (x, x0, x1)))} \\right|
        \\\\ \\\\
        
        & = \\left| 
        \\left . {latex( Intgl_surf_area_doit  )} \\right|^{{ {x1} }}_{{ {x0} }}
        \\qquad
        \\right|
        \\\\ \\\\
        
        & = 
        \\left| 
        \\left( {latex(SA1_ueval)} \\right) - \\left( {latex(SA0_ueval)} \\right)
        \\right|
        \\\\ \\\\

        {SA0_SA1_stmt}

        & = {latex(abs_surf_area)} 

        \\end{{aligned}}
        $
        """
        soln_items.append(soln_item.strip())

        
        answer_item = f"""
        $\\displaystyle {latex(abs_surf_area)}$
        """
        answer_items.append(answer_item.strip())


        problem_s = "\n\n".join(problem_items)
        soln_s = "\n\n".join(soln_items)
        answer_s = "\n\n".join(answer_items)


        svg_str = self.plot_expr(expr, x, x_range = [float(x0), float(x1)])

        result = {"problem_s": problem_s,
                  "soln_s": soln_s,
                  "svg_str": svg_str,
                  "answer_s": answer_s,
                  }
        return result
        pass
            
    def plot_expr(self, expr, x, x_range = None):
        verbose = self.verbose
        plt.ioff()
 
        from sympy import symbols, Matrix
        import numpy as np
        
        plotter = Plotter2D(verbose = verbose)
        
        x_range.sort()
        fig, ax, xs, ys = plotter.plot_expr(expr, x_range = x_range, return_xy_data = True )
        
        y_range = self._get_y_range(expr, x, x_range)
        
        #plot horizontal lines
        ax.plot(x_range, (0, 0), color = 'red')
        
        #plot vertical lines
        ax.plot((x_range[0], x_range[0]), (0, y_range[0]), color = 'green')
        ax.plot((x_range[1], x_range[1]), (0, y_range[1]), color = 'green')
        
        
                
        svgHelper = SvgHelper()
        svg_str = svgHelper.plt_to_svg()
        
        plt.clf()
        plt.close()
        
        return svg_str  
    
    
    def _get_y_range(self, expr, x, x_range):  
        try:
            y_range = []
            y_range.append(expr.subs(x, x_range[0]))
            y_range.append(expr.subs(x, x_range[-1]))
            return y_range
        except:
            pass
        
        x_range = np.linspace(*x_range, 1000)
        x_range = [x_range[1], x_range[-1]]

        try:
            y_range = []
            y_range.append(expr.subs(x, x_range[0]))
            y_range.append(expr.subs(x, x_range[-1]))
            return y_range
        except:
            pass
        
        return None
        pass  
    

    pass
#class SurfaceAreaOfRotationHelper:
