'''
Created on 16-Apr-2021

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

import sympy

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

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


from xv.util import number_to_nth, toIntOrFloat

from ...helper import BaseHelper, CalculusHelper

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

        
class LimitManager(BaseManager):
    '''
    Basic Differentiation Rules
    Example:
        ke = LimitManager()
        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(LimitManager, cls).__new__(cls, *args, **kwargs)
        for base_class in LimitManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        pass


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
                '_problem_intro_to_limit',
                '_problem_intro_to_continuity',
                '_problem_algebra_of_limits',
                '_problem_l_hopitals_rule',
                '_problem_application_of_l_hopitals_rule',
                '_problem_advanced_application_of_l_hopitals_rule',
                '_problem_limit_of_custom_l_hopitals_expressions',
                '_problem_limit_of_custom_expressions',
                '_problem_limit_advanced_questions',
                
             ])
        pass

    def _problem_intro_to_limit(self, *args, **kwargs):
        """
        _problem_intro_to_limit 

        doc_style: xv_doc
        """
        
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        
        result = lmt_helper.define_limit_at_a_point()
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        
        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')

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

    def _problem_intro_to_continuity(self, *args, **kwargs):
        """
        _problem_intro_to_continuity 

        doc_style: xv_doc
        """
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        
        result = lmt_helper.define_limit_at_a_continuity()
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        
        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')

        svg_str = result['svg_str']
        problemTemplate.add_solution(svg_str, 'text/svg')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    def _problem_algebra_of_limits(self, *args, **kwargs):
        """
        _problem_algebra_of_limits 

        doc_style: xv_doc
        """
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        
        result = lmt_helper.define_algebra_of_limits()
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        
        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
    
    def _problem_l_hopitals_rule(self, *args, **kwargs):
        """
        _problem_l_hopitals_rule 

        doc_style: xv_doc
        """
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        l_hopitals_rule_def = r"""
        If two functions $f$ and $g$ are differentiable on an open interval $I$, 
        with an exception made for $a$, 
        and the limit of their quotient takes 
        an indeterminate form such as 
        $\frac{0}{0}$ or $\frac{\infty}{\infty}$, 
        the limit of their quotient can be expressed as
        $$
        \lim _{x \rightarrow a} \frac{f(x)}{g(x)} 
        =\lim _{x \rightarrow a} \frac{f^{\prime}(x)}{g^{\prime}(x)}
        $$
        Given that $g^{\prime}(x) \neq 0$ and the RHS exists.
        """

        l_hopitals_rule_expl = r"""
        """
        
        problem_s = r"""
        Define L’Hopital’s Rule.
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        <b>L’Hopital’s Rule</b>
        
        {l_hopitals_rule_def}
        {l_hopitals_rule_expl}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {l_hopitals_rule_def}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    def _problem_application_of_l_hopitals_rule(self, *args, **kwargs):
        """
        _problem_application_of_l_hopitals_rule 

        doc_style: xv_doc
        """
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        lmt_helper = self.base_helper.get_calculus_helper().get_lhopitals_helper()
        
        g, h, x, x0 = lmt_helper.get_random_expr(advanced = False)
        
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        result = lmt_helper.get_solution(g/h, x, x0)
        result_str = lmt_helper.solution_to_str(result)
        
        problem_s = f"""
        Solve:
         
        $\\displaystyle {latex(result['Limit'])} $
        """
        
        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['Limit'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    
    
    def _problem_advanced_application_of_l_hopitals_rule(self, *args, **kwargs):
        """
        _problem_advanced_application_of_l_hopitals_rule 

        doc_style: xv_doc
        """
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        lHopitalsHelper = self.base_helper.get_calculus_helper().get_lhopitals_helper()
        g, h, x, x0 = lHopitalsHelper.get_random_expr(advanced = True)
        
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        result = lmt_helper.get_solution(g/h, x, x0)
        result_str = lmt_helper.solution_to_str(result)
        
        problem_s = f"""
        Solve:
         
        $\\displaystyle {latex(result['Limit'])} $
        """
        
        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['Limit'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    
    def _problem_limit_of_custom_l_hopitals_expressions(self, *args, **kwargs):
        """
        _problem_limit_of_custom_l_hopitals_expressions 

        doc_style: xv_doc
        """
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        g = kwargs.get('g', None)
        h = kwargs.get('h', None)
        x = kwargs.get('x', None)
        x0 = kwargs.get('x0', None)
        
        if None in (g, h, x, x0):
            problem_s = f"""
            Mandatory parameters:
            x: a variable
            g: a function of x
            h: a function of x
            x0: value of x to compute limit at
            """
            
            problemTemplate.add_problem(problem_s.strip(), 'text/html')
            return problemTemplate
            pass
        
        
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        result = lmt_helper.get_solution(g/h, x, x0)
        result_str = lmt_helper.solution_to_str(result)
        
        problem_s = f"""
        Solve:
         
        $\\displaystyle {latex(result['Limit'])} $
        """
        
        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['Limit'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    
    def _problem_limit_of_custom_expressions(self, *args, **kwargs):
        """
        _problem_limit_of_custom_expressions 

        doc_style: xv_doc
        """
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        x = kwargs.get('x', None)
        f = kwargs.get('f', None)
        x0 = kwargs.get('x0', None)
        
    
        
        lmt_helper = self.base_helper.get_calculus_helper().get_limit_helper()
        
        help_s = ""
        if None in (f, x, x0):
            help_s = f"""
            Note: You can pass the following parameters:
            x: a variable
            f: a function of x
            x0: value of x to compute limit at
            """
            f, x, x0 = lmt_helper.get_random_limit_expr()
            pass
        
        
        result = lmt_helper.get_solution(f, x, x0)
        result_str = lmt_helper.solution_to_str(result)
        
        problem_s = f"""
        Solve:
         
        $\\displaystyle {latex(result['Limit'])} $
        
        {help_s}
        """
        
        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['Limit'])} 
        
        = {latex(result['answer'])} $
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    
    def _problem_limit_advanced_questions(self, *args, **kwargs):
        """
        
        """
        
        folder_path = "question_bank/math/calculus"
        search_file_paths = [
                "limit_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)



