'''
Created on 16-Apr-2021

@author: bthakur
'''
import os 
import numpy as np
import random
import inspect
import traceback

import sympy

from sympy import ( latex,symbols, diff, Symbol, Mul, Matrix, Function,
                    factorial, sympify, Rational, log,
                    Derivative, factor, Eq, simplify, expand,
                    exp, sin, cos, cosh, sinh, pi, sqrt, atan, Limit,
                    expand_trig, trigsimp, srepr, Integer, Pow, Add)

from sympy.core.numbers import One, Integer
from sympy.simplify.fu import TR1, TR2, TR2i, fu, TR16, TR22


from ...helper import BaseHelper, CalculusHelper

from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate

# _differentiation_overview_manager
# DifferentiationOverviewManager
        
class DifferentiationOverviewManager(BaseManager):
    '''
    Differentiation 
    Example:
        ke = DifferentiationOverviewManager()
        ke
        
        ke.printProblemTypes()
        
        ke.getRandomProblem()
        ke.getRandomProblem(problem_type = 0)
        ke.getRandomProblem(problem_type = 1)
        ...
        
        ke.printProblem()
        ke.printAnswer()
        ke.printSolution()
        
        
    '''    
    def __new__(cls, *args, **kwargs):
        instance = super(DifferentiationOverviewManager, cls).__new__(cls, *args, **kwargs)
        for base_class in DifferentiationOverviewManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        
        self._colors = ['indigo', 'blue', 'green', 'red', 'cyan', 'magenta', 
                        'violet', 'black', 'orange', 'black', 'grey']  
        self._markers = ['o', 'v', '^', '<', '>', '1', '2', '3', '4', 
                         '8', 's', 'p', 'P', '*', 'h', 'H', 'X', 'D', 'd', ]
        self._p_labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        self.lastAnnotationX = 0
        self.lastAnnotationY = 0
        self._movements = None
        
        pass


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
                '_problem_delta_of_a_function',
                '_problem_define_limit',
                '_problem_define_continuity',
                '_problem_define_derivative',
                '_problem_derivative_rule_add',
                '_problem_derivative_rule_mul',
                '_problem_compute_derivative_x_power_n',
                '_problem_derivative_finite_difference',
                '_problem_derivative_of_standard_functions',
                '_problem_derivative_sin_cos',
                '_problem_derivative_trig_product_rule',
                '_problem_derivative_product_rule',
                '_problem_derivative_exponent_x_rule',
                '_problem_derivative_p_q',
                '_problem_derivative_custom_function',
                '_problem_intutive_derivative_computation',
                '_problem_derivative_difficult',
                '_problem_bivariate_derivative_computation',
             ])
        pass

    def _problem_delta_of_a_function(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()

        x = dt_helper.x
        exprs = dt_helper.get_standard_exprs(x = x)
        f = random.choice(exprs)
        
        h = dt_helper.h
        x0 = x
        x1 = x0 + h
        
        
        result = dt_helper.get_function_delta(f, x, x0, x1)
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Find the change in $\\displaystyle {latex(f)} \\text{{ when }} {latex(x)}
        \\text{{ changes from }} {latex(x0)} \\text{{ to }}  {latex(x1)} .$
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        The change in $\\displaystyle {latex(f)} \\text{{ when }} {latex(x)}
        \\text{{ changes from }} {latex(x0)} \\text{{ to }}  {latex(x1)} \\text{{ is }}
        $
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_define_limit(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()
        
        x = dt_helper.x
        exprs = dt_helper.get_standard_exprs(x = x)
        f = random.choice(exprs)
        
        x0 = Symbol(rf"{latex(x)}_{{0}}")
        result = dt_helper.define_limit(f, x, x0)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Define limit of $\\displaystyle {latex(f)} \\text{{ at }} {latex(x0)}$.
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        Assuming that $\\displaystyle 
        {latex(f)} \\text{{ exists at }} {latex(x0)}
        $, limit of $\\displaystyle {latex(f)} \\text{{ at }} {latex(x)}$ is
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_define_continuity(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()
        
        x = dt_helper.x
        exprs = dt_helper.get_standard_exprs(x = x)
        f = random.choice(exprs)

        x0 = Symbol(rf"{latex(x)}_{{0}}")
        result = dt_helper.define_continuity(f, x, x0)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Define continuity of $\\displaystyle {latex(f)} \\text{{ at }} {latex(x0)}$.
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        Assuming that limit of $\\displaystyle 
        {latex(f)} \\text{{ exists at }} {latex(x0)}
        $, $\\displaystyle {latex(f)} \\text{{ is continuous at }} {latex(x0)}
        $ if and only if  
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    
    def _problem_define_derivative(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()
        
        x = dt_helper.x
        exprs = dt_helper.get_standard_exprs(x = x)
        f = random.choice(exprs)

        result = dt_helper.define_derivative(f, x)
        xh = result['xh']
        
        df_dx_latex = result['df_dx_latex']
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Define derivative of $\\displaystyle 
        {latex(f)} \\text{{ with respect to }} {latex(x)}$.
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        Assuming that limit of $\\displaystyle 
        {latex(f)} \\text{{ exists at }} {latex(x)} \\text{{ and }} {latex(xh)}
        $, derivative of $\\displaystyle {latex(f)} \\text{{ with respect to }} {latex(x)}$
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    
    def _problem_derivative_rule_add(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()
        
        #f = dt_helper.f
        #g = dt_helper.g
        
        x = dt_helper.x
        exprs = dt_helper.get_standard_exprs(x = x)
        f, g = random.sample(exprs, 2)

        
        x = dt_helper.x
        h = dt_helper.h
        result = dt_helper.get_derivative_rule_add(f, g, x, h)
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Define derivative of $\\displaystyle 
        {latex(f)} + {latex(g)}  \\text{{ with respect to }} {latex(x)}$.
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        Derivative of $\\displaystyle 
        {latex(f)} + {latex(g)}  \\text{{ with respect to }} {latex(x)}$
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    
    def _problem_derivative_rule_mul(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()
        
        #f = dt_helper.f
        #g = dt_helper.g
        
        x = dt_helper.x
        exprs = dt_helper.get_standard_exprs(x = x)
        f, g = random.sample(exprs, 2)

        x = dt_helper.x
        h = dt_helper.h
        result = dt_helper.get_derivative_rule_mul(f, g, x, h)
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Define derivative of $\\displaystyle 
        {latex(f)} + {latex(g)}  \\text{{ with respect to }} {latex(x)}$.
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        Derivative of $\\displaystyle 
        {latex(f)} + {latex(g)}  \\text{{ with respect to }} {latex(x)}$
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    
    def _problem_compute_derivative_x_power_n(self, *args, **kwargs):
        dt_helper = self.base_helper.get_calculus_helper().get_differentiation_theory_helper()

        result = dt_helper.compute_derivative_x_power_n()
        f = result['f']
        x = result['x']
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        problem_s = f"""
        Find derivative of $\\displaystyle 
        {latex(f)} \\text{{ with respect to }} {latex(x)}$.
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        Derivative of $\\displaystyle {latex(f)} \\text{{ with respect to }} {latex(x)}$
        
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    
    def _problem_derivative_finite_difference(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = "finite_difference")
            pass
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        result = adc_helper.get_solution(f, x, x0)
        result_str = adc_helper.solution_to_str(result)
        #print(result)
        
        problem_s = f"""
        Find derivative of the $
        \\displaystyle  {latex(f)}$  with respect to {x}:
         
        $\\displaystyle {result['Derivative_latex']} $
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result_str}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {result['Derivative_latex']} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_derivative_of_standard_functions(self, *args, **kwargs):
        sd_helper = self.base_helper.get_calculus_helper().get_std_differentiation_helper()

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        result = sd_helper.get_standrad_derivative()
        
        prob_s = f"""
        {result['prob_s']}
        """
        
        problemTemplate.add_problem(prob_s.strip(), 'text/html')


        soln_s = f"""
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_derivative_sin_cos(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'sin_cos')
            pass
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()
        result = adc_helper.get_solution(f, x, x0)
        result_str = adc_helper.solution_to_str(result)
        #print(result)
        
        problem_s = f"""
        Find derivative of the $
        \\displaystyle  {latex(f)}$  with respect to {x}:
         
        $\\displaystyle {latex(result['Derivative'])} $
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result_str}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(result['Derivative'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

        
    def _problem_derivative_trig_product_rule(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'trig_product')
            pass
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()
        result = adc_helper.get_solution(f, x, x0)
        result_str = adc_helper.solution_to_str(result)
        #print(result)
        
        problem_s = f"""
        Find derivative of the $
        \\displaystyle  {latex(f)}$  with respect to {x}:
         
        $\\displaystyle {latex(result['Derivative'])} $
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result_str}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(result['Derivative'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass
        
    def _problem_derivative_product_rule(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'product')
            pass
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()
        result = adc_helper.get_solution(f, x, x0)
        result_str = adc_helper.solution_to_str(result)
        #print(result)
        
        problem_s = f"""
        Find derivative of the $
        \\displaystyle  {latex(f)}$  with respect to {x}:
         
        $\\displaystyle {latex(result['Derivative'])} $
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result_str}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(result['Derivative'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass
        
    def _problem_derivative_exponent_x_rule(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'exponent_x')
            pass
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()
        result = adc_helper.get_solution(f, x, x0)
        result_str = adc_helper.solution_to_str(result)
        #print(result)
        
        problem_s = f"""
        Find derivative of the $
        \\displaystyle  {latex(f)}$  with respect to {x}:
         
        $\\displaystyle {latex(result['Derivative'])} $
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result_str}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(result['Derivative'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass
        
    def _fOne(self, x):
        return 1
    
    def _problem_derivative_p_q(self, *args, **kwargs):
        func_p_q = random.choice([
            (sympy.sin, sympy.cos, sympy.tan),
            (self._fOne, sympy.sin, sympy.csc),
            (self._fOne, sympy.cos, sympy.sec),
            (sympy.cos, sympy.sin, sympy.cot),
            ])
        x = symbols('x')
        u = (func_p_q[0])(x)
        v = (func_p_q[1])(x)
        expr = (func_p_q[2])(x)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""Find derivative of the following with respect to {x}"""
        problemTemplate.add_problem(statement)
        problemTemplate.add_problem(expr, 'sympify/html')

        expr_Derivative = Derivative(expr, x)
        answer = TR22(expr_Derivative.doit())
        
        problemTemplate.add_answer(answer, 'sympify/html')
        
        problemTemplate.add_solution(expr_Derivative, 'sympify/html', prefix = "= ")
        
        
        expr_Derivative = Derivative(u / v, x)
        problemTemplate.add_solution(expr_Derivative, 'sympify/html', prefix = "= ")
        
        expr_Derivative = Add(Mul(Derivative(u, x), v), - Mul(u, Derivative(v, x)))/v**2
        problemTemplate.add_solution(expr_Derivative, 'sympify/html', prefix = "= ")
        
        expr_Derivative = Add(Mul(Derivative(u, x).doit(), v), 
                              - Mul(u, Derivative(v, x).doit())
                              )/v**2
                              
        problemTemplate.add_solution(expr_Derivative, 'sympify/html', prefix = "= ")
        
        if (func_p_q[2]) == sympy.tan or (func_p_q[2]) == sympy.cot:
            expr_Derivative = trigsimp(expr_Derivative)
            problemTemplate.add_solution(expr_Derivative, 'sympify/html', prefix = "= ")
            
        problemTemplate.add_solution(answer, 'sympify/html', prefix = "= ")
        
        
        self._expr = expr_Derivative
        return problemTemplate

        pass

    def _problem_derivative_custom_function(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'custom')
            print(f"""
            f = {f}
            """)
            pass
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()
        result = adc_helper.get_solution(f, x, x0)
        result_str = adc_helper.solution_to_str(result)
        #print(result)
        
        problem_s = f"""
        Find derivative of the $
        \\displaystyle  {latex(f)}$  with respect to {x}:
         
        $\\displaystyle {latex(result['Derivative'])} $
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result_str}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(result['Derivative'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass
        

    def _problem_intutive_derivative_computation(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'custom')
            pass
        

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        result = adc_helper.get_intutive_derivative_computation(f, x, x0)
        
        self.result = result
        
        problem_s = f"""
        {result['problem_s']}
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    
    def _problem_derivative_difficult(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)

        if (f is None or 
            x is None or  
            x0 is None):
            f, x, x0 = adc_helper.get_random_function(category = 'difficult')
            pass
        

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        result = adc_helper.get_intutive_derivative_computation(f, x, x0)
        
        self.result = result
        
        problem_s = f"""
        {result['problem_s']}
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_bivariate_derivative_computation(self, *args, **kwargs):
        adc_helper = self.base_helper.get_calculus_helper().get_auto_derivation_calc_helper()

        f = kwargs.get('f', None)
        x = kwargs.get('x', None)

        if (f is None or 
            x is None ):
            f, x = adc_helper.get_random_bivariate_function(category = None)
            pass
        

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        result = adc_helper.get_intutive_derivative_computation(f, x, x)
        
        self.result = result
        
        problem_s = f"""
        {result['problem_s']}
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result['soln_s']}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_limit_advanced_questions(self, *args, **kwargs):
        """
        
        """
        
        folder_path = "question_bank/math/calculus"
        search_file_paths = [
                "derivative_advanced_questions.txt"
            ]
        
        file_paths = self.get_q_bank_factory().search_question_bank_files(folder_path, search_file_paths)
        return self.get_q_bank_factory().PROBLEM_TEMPLATE_Q_BANK_RANDOM_QUESTION(file_paths, *args, **kwargs)
