'''
Created on 12-Jul-2021

@author: bthakur
'''
import random
import inspect
import traceback
from sympy import (Symbol, latex, Function, Eq,
                   solve,
                   Derivative,
                   sqrt,
                   Rational,
                   sin, cos, tan, exp, log,
                   )

from xv.util import UtilClass

class MaxMaxHelper(UtilClass):

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

    def get_random_applied_problem(self):
        nSum = random.randint(6, 20)
        problem_s = f"""
        Find two nonnegative numbers whose sum is {nSum} and 
        so that the product of one number and 
        the square of the other number is a maximum.
        """.replace("\n", "")
        
        x = Symbol('x')
        expr = x**2 * (nSum - x)
        
        pre_solution = f"""
        Let first number is ${latex(x)}$. 
        Then the second number will be ${latex(nSum)} - {latex(x)}$.
        We want to maximize $\\displaystyle {latex(expr)} $ 
        """
        
        post_solution = f"""
        """
        
        soln_result = self.get_extrema_solution(expr, x, 
                                                max_val = True, 
                                                min_val =False)
        
        result = {
            'x': x,
            'expr': expr,
            'problem_s': problem_s,
            'pre_solution': pre_solution,
            'post_solution': post_solution,
            }
        
        result.update(soln_result)
        return result
        pass


    def get_random_expr(self, x = None):
        if x is None:
            x = Symbol('x')
        
        exprs = self.get_exprs()
        return random.choice(exprs)
        pass
    
    def get_exprs(self, x = None):
        if x is None:
            x = Symbol('x')
        
        exprs =  [
            sqrt((x-4)**2 + x),
            x * (9-x)**2 ,
            sin(x),
            cos(x),
            tan(x),
            sqrt(x**2 + (3 * x / (x - Rational(8, 9)))**2)
            ]
        return exprs
        

    def get_extrema_solution(self, expr, x, max_val = True, min_val = True):
        soln_s = []
        min_values = {}
        max_values = {}
        
        f = Function('f')
        f_d = Derivative(f(x), x)
        f_d2 = Derivative(f(x), x, 2)
        eq = Eq(Derivative(expr, x), 0)

        
        stmt = f"""
        $\\displaystyle {f(x)} = {latex(expr)}$
        """
        soln_s.append(stmt.strip())
        
        if max_val:
            stmt = f"""
            <h3>For maximum value of ${f(x)}$:</h3>
            \t\t$\\displaystyle {latex(f_d)} = 0$ 
            
            \t\t$\\displaystyle {latex(f_d2)} < 0$
            """
            soln_s.append(stmt.strip())
        
        if min_val:
            stmt = f"""
            <h3>For minimum value of ${f(x)}$:</h3>
            \t\t$\\displaystyle {latex(f_d)} = 0$ 
            
            \t\t$\\displaystyle {latex(f_d2)} > 0$
            """
            soln_s.append(stmt.strip())
            pass


        stmt = f"""
        Let us solve ${latex(f_d)} = 0$:
        
        $\\displaystyle \\implies {latex(eq)}$
        """
        soln_s.append(stmt.strip())


        # Step 1
        ########################################
        eq = Eq(eq.lhs.doit(), eq.rhs)
        stmt = f"""$\\displaystyle \\implies {latex(eq)}$"""
        soln_s.append(stmt.strip())
        
        # Step 2
        ########################################
        soln_x = solve(eq, x, dict = False)
        if soln_x:
            soln_x_l = [latex(xroot) for xroot in soln_x]
            soln_x_s = ",\\quad".join(soln_x_l)
            stmt = f"""$\\displaystyle \\implies {x} = {soln_x_s}$"""
            soln_s.append(stmt.strip())

            expr_dx_2 = Derivative(expr, x, 2)
            expr_dx_2_doit = expr_dx_2.doit()
            
            for idx, xroot in enumerate(soln_x):
                stmt = f"""<h3>Checking min and max at $\\displaystyle 
                 {latex(x)} = {latex(soln_x[idx])}$ </h3>"""
                soln_s.append(stmt.strip())

                stmts, x0, x0min, x0max = self._get_max_min_statement(expr, 
                                            x, 
                                            soln_x, 
                                            soln_x_l, 
                                            idx, 
                                            expr_dx_2, 
                                            expr_dx_2_doit,
                                            max_val = max_val, min_val = min_val)
                
                if stmts:
                    soln_s.extend(stmts)

                if x0min:
                    min_values[x0] = x0min
                    
                if x0max:
                    max_values[x0] = x0max
                
                pass

            pass

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

        
        max_values_str = [f"""$\\displaystyle 
                          f({x0}) = {latex(x0max)}
                          $
                          """.strip() 
                          for x0, x0max in max_values.items()]
        
        max_values_str = "\n\n".join(max_values_str)
        
        min_values_str = [f"""$\\displaystyle 
                          f({x0}) = {latex(x0min)}
                          $
                          """.strip() 
                          for x0, x0min in min_values.items()]
        
        min_values_str = "\n\n".join(min_values_str)
        
        result = {
            'soln_s': soln_s,
            'min_values': min_values,
            'max_values': max_values,
            'min_values_str': min_values_str,
            'max_values_str': max_values_str,
            }
        
        return result
        pass


    def _get_max_min_statement(self, 
                               expr,
                               x,
                               soln_x,
                               soln_x_l, idx, 
                               expr_dx_2,
                               expr_dx_2_doit, 
                               max_val = True, min_val = True
                               ):

        x0 = soln_x_l[idx]
        x0min = None
        x0max = None
        
        soln_s = []
        
        right_sub_val = ' \\left. \\right|_{\\substack{ ' + f"{x}={x0}" + ' }}'
        #soln_dx_2 = f"""$\\displaystyle {{{latex(expr_dx_2)}}} {{{right_sub_val}}}$"""
        soln_dx_2 = self._get_substack_latex(expr_dx_2, x, x0)
        soln_dx_2 = f"""$\\displaystyle {soln_dx_2}$"""
        
        #soln_dx_2_doit = f"""$\\displaystyle {{{latex(expr_dx_2_doit)}}} {{{right_sub_val}}}$"""
        soln_dx_2_doit = self._get_substack_latex(expr_dx_2_doit, x, x0)
        soln_dx_2_doit = f"""$\\displaystyle {soln_dx_2_doit}$"""
        
        
        expr_dx_2_computed = expr_dx_2_doit.subs(x, soln_x[idx])
        soln_dx_2_computed = f"""$\\displaystyle {latex(expr_dx_2_computed)}$"""
        
        stmt = f"""
        Second derivative of ${latex(expr)}$ with resprct to ${x}$ at ${x}={x0}$:
        
        {soln_dx_2} = {soln_dx_2_doit} = {soln_dx_2_computed}
        """
        soln_s.append(stmt.strip())

        if expr_dx_2_computed == 0:
            stmt = f"""
            As the value of Second derivative is zero, there is no max and min value at {x} = {x0}
            """
            soln_s.append(stmt.strip())
            return soln_s, x0, x0min, x0max
            pass
        
        if expr_dx_2_computed > 0:
            if min_val:
                x0min = expr.subs(x, soln_x[idx])
    
                #soln_min_latex = f"""$\\displaystyle {{{latex(expr)}}} {{{right_sub_val}}}$"""
                soln_min_latex = self._get_substack_latex(expr, x, x0)
                #soln_min_latex = f"""$\\displaystyle {soln_min_latex}$"""
                
                stmt = f"""
                As the value of Second derivative is greater than zero, 
                
                $\\displaystyle \\text{{Minimum}} \\left( {latex(expr)} \\right) 
                 = {soln_min_latex} = {latex(x0min)}
                 $
                """
                soln_s.append(stmt.strip())
            return soln_s, x0, x0min, x0max
            
        if expr_dx_2_computed < 0:
            if max_val:
                x0max = expr.subs(x, soln_x[idx])
                #soln_max_latex = f"""$\\displaystyle {{{latex(expr)}}} {{{right_sub_val}}}$"""
                soln_max_latex = self._get_substack_latex(expr, x, x0)
                #soln_max_latex = f"""$\\displaystyle {soln_max_latex}$"""
    
                stmt = f"""
                As the value of Second derivative is less than zero, 
                
                $\\displaystyle \\text{{Maximum}} \\left( {latex(expr)} \\right) 
                 = {soln_max_latex} = {latex(x0max)}
                $
                """
                soln_s.append(stmt.strip())
            return soln_s, x0, x0min, x0max
            

        stmt = f"""
        Unable to proceed for {x} = {x0}
        """
        soln_s.append(stmt.strip())
        return soln_s, x0, x0min, x0max
        pass


    def _get_substack_latex(self, expr, x, x0):
        substack_latex = ' \\left. {' + latex(expr)  + '} \\right|_{\\substack{ ' + f"{x}={x0}" + ' }}'
        return substack_latex
        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 MaxMaxHelper:
