'''
Created on 02-Mar-2021

@author: bthakur
'''
'''
Created on 07-Feb-2021

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

from sympy import (simplify, sympify, log, latex, Symbol, symbols, 
                   UnevaluatedExpr as ueval)

import sympy
from ...helper import BaseHelper
from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate

from xv.util import list_to_str

# _matrix_manager
# MatrixManager
        
class MatrixManager(BaseManager):
    '''
    Matrix in Linear Algebra
    Example:
        ke = MatrixManager()
        ke.getRandomProblem()
        ke.getRandomProblem(problem_type = 0)
        ke.getRandomProblem(problem_type = 1)
        ...
        ke.printProblem()
        ke.printAnswer()
        ke.printSolution()

        ke.printProblemTypes()
    '''    
    def __new__(cls, *args, **kwargs):
        instance = super(MatrixManager, cls).__new__(cls, *args, **kwargs)
        for base_class in MatrixManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        pass

    def _TEMPLATE_CUSTOM_QUESTIONS(self, keywords, **kwargs):
        
        from ...storeddata import MatrixStoredQuestions
        
        lsqs = MatrixStoredQuestions(verbose = self.verbose)
        result = lsqs.get_random_question(keywords)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            keywords = {keywords}
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate
            
        
        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 _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_foundation_to_concept_of_matrix',
            '_problem_foundation_to_concept_of_matrix_adv',
            '_problem_interpret_a_matrix',
            '_problem_matrix_to_equations',
            '_problem_equations_to_matrix',
            '_problem_matrix_inverse',
            '_problem_solve_equations_by_inverse_matrix',
            '_problem_solve_equations_by_row_operation',
            '_problem_solve_a_determinant',
            '_problem_solve_le_by_crammer_rule',
            '_problem_solve_by_plot_equations',
            '_problem_matrix_notation',
            '_problem_matrix_operations',
            '_problem_vector_space_notation',
            '_problem_vector_operations',
            '_problem_discrete_fast_fourier_transform',
            
         ])
        pass

    def _problem_foundation_to_concept_of_matrix(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(shop_count = 1)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        human_count = result['human_count']
        friend_names_str = result['friend_names_str']
        food_count = result['food_count']
        food_names_str = result['food_names_str']
        A = result['A']
        B = result['B']
        C = result['C']
        Adf = result['Adf']
        Bdf = result['Bdf']
        Cdf = result['Cdf']
        Cdf_latex = result['Cdf_latex']
        line_break = result['line_break']
    
        prob_s = f"""
        {human_count} friends, {friend_names_str} are having a party. 
        They order {food_count} items: {food_names_str}.
        <br />
        {Adf.to_html().strip()}
        <br />
        They prices of items they ordered are as follows:
        <br />
        {Bdf.to_html().strip()}
        <br />
        Calculate the cost for each friend.
        """
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = []
        
        soln_item = f"""
        Ordered quantities:
        <br />
        {Adf.to_html().strip()}
        <br />
        Prices of ordered items:
        <br />
        {Bdf.to_html().strip()}
        Let us calculate the cost:
        <br />
        {Cdf_latex.to_html().strip()}
        <br />
        <br />
        So, the cost is:
        <br />
        {Cdf.to_html().strip()}
        
        <br />
        <br />
        In short form, we write:
        <br />
        <br />
        $\\displaystyle 
        {latex(A)} {latex(B)} = {latex(C)}
        $

        <br />
        <br />
        In mathematical format:
        <br />
        $\\displaystyle 
        AB = C
        $
        """
        soln_item = soln_item.replace("\n", "").replace(line_break, "<br/>")
        soln_s.append(soln_item.strip())
        
        soln_s = "\n\n".join(soln_s)
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        The cost for each friend.
        <br/>
        {Cdf.to_html().strip()}
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass

    def _problem_foundation_to_concept_of_matrix_adv(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        shop_count = random.randint(2, 4)
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                                shop_count = shop_count)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        human_count = result['human_count']
        friend_names_str = result['friend_names_str']
        food_count = result['food_count']
        food_names_str = result['food_names_str']
        A = result['A']
        B = result['B']
        C = result['C']
        Adf = result['Adf']
        Bdf = result['Bdf']
        Cdf = result['Cdf']
        Cdf_latex = result['Cdf_latex']
        line_break = result['line_break']
    
        prob_s = f"""
        {human_count} friends, {friend_names_str} are having a party. 
        They order {food_count} items: {food_names_str}.
        <br />
        {Adf.to_html().strip()}
        <br />
        They prices of items they ordered are different in various shops. They are as follows:
        <br />
        {Bdf.to_html().strip()}
        <br />
        Calculate the cost for each friend at each shop.
        """
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = []
        
        soln_item = f"""
        Ordered quantities:
        <br />
        {Adf.to_html().strip()}
        <br />
        Prices of ordered items:
        <br />
        {Bdf.to_html().strip()}
        Let us calculate the cost:
        <br />
        {Cdf_latex.to_html().strip()}
        <br />
        <br />
        So, the cost is:
        <br />
        {Cdf.to_html().strip()}
        
        <br />
        <br />
        In short form, we write:
        <br />
        <br />
        $\\displaystyle 
        {latex(A)} {latex(B)} = {latex(C)}
        $

        <br />
        <br />
        In mathematical format:
        <br />
        $\\displaystyle 
        AB = C
        $
        """
        soln_item = soln_item.replace("\n", "").replace(line_break, "<br/>")
        soln_s.append(soln_item.strip())
        
        soln_s = "\n\n".join(soln_s)
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        The cost for each friend.
        <br/>
        {Cdf.to_html().strip()}
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass

    def _problem_interpret_a_matrix(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        interpretations = True)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        human_count = result['human_count']
        friend_names = result['friend_names']
        friend_names_str = result['friend_names_str']
        food_count = result['food_count']
        food_names = result['food_names']
        food_names_str = result['food_names_str']
        A = result['A']
        B = result['B']
        C = result['C']
        Adf = result['Adf']
        Bdf = result['Bdf']
        Cdf = result['Cdf']
        Cdf_latex = result['Cdf_latex']
        q_str = result['q_str']
        qa_str = result['qa_str']
        line_break = result['line_break']
    
        prob_s = f"""
        {human_count} friends, {friend_names_str} are having a party. 
        They order {food_count} items: {food_names_str}.
        The order, price and cost of their party is as follows:
        <br />
        <br />
        $\\displaystyle 
        {latex(A)} {latex(B)} = {latex(C)}
        $
        <br/>
        <br/>
        Answer the followings:
        <br/>
        {q_str}
        """
        prob_s = prob_s.replace("\n", "")
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = []
        
        soln_item = f"""
        {Adf.to_html().strip()}
        <br />
        They price of items they ordered is as follows:
        <br />
        {Bdf.to_html().strip()}
        <br />
        Ordered quantities:
        <br />
        {Adf.to_html().strip()}
        <br />
        Price of ordered items:
        <br />
        {Bdf.to_html().strip()}
        Let us calculate the cost:
        <br />
        {Cdf_latex.to_html().strip()}
        <br />
        <br />
        So, the cost is:
        <br />
        {Cdf.to_html().strip()}
        
        <br />
        <br />
        In short form, we write:
        <br />
        $\\displaystyle 
        {latex(A)} {latex(B)} = {latex(C)}
        $

        <br />
        <br />
        <b>The answers are as follows:</b>
        <br />
        <br />
        {qa_str}
        """
        soln_item = soln_item.replace("\n", "").replace(line_break, "<br/>")
        soln_s.append(soln_item.strip())
        
        soln_s = "\n\n".join(soln_s)
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {qa_str}
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass

    def _problem_matrix_to_equations(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        equations = True)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        A = result['A']
        X = result['X']
        C = result['C']
        eqs = result['eqs']
        eqs_latex = result['eqs_latex']
        
        
        
        line_break = result['line_break']
    
        prob_s = f"""
        Convert the followings into equations:
        
        $\\displaystyle 
        {latex(A)} {latex(X)} = {latex(C)}
        $
        """
        prob_s = prob_s.replace("\n", "")
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = []
        
        soln_item = f"""
        {eqs_latex}
        """
        soln_item = soln_item.replace("\n", "").replace(line_break, "<br/>")
        soln_s.append(soln_item.strip())
        
        soln_s = "\n\n".join(soln_s)
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {eqs_latex}
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass


    def _problem_equations_to_matrix(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        equations = True)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        A = result['A']
        X = result['X']
        C = result['C']
        #eqs = result['eqs']
        eqs_latex = result['eqs_latex']
        
        line_break = result['line_break']
    
        prob_s = f"""
        Convert the followings into matrix equation:
        {eqs_latex}
        
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = []
        
        soln_item = f"""
        $\\displaystyle 
        {latex(A)} {latex(X)} = {latex(C)}
        $
        """
        soln_item = soln_item.replace("\n", "").replace(line_break, "<br/>")
        soln_s.append(soln_item.strip())
        
        soln_s = "\n\n".join(soln_s)
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle 
        {latex(A)} {latex(X)} = {latex(C)}
        $
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass
    
    def _problem_matrix_inverse(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        inverse = True)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        A = result['A']
        #B = result['B']
        #X = result['X']
        #C = result['C']
        #eqs = result['eqs']
        #eqs_latex = result['eqs_latex']
        line_break = result['line_break']
        A_inv = result['A_inv']    
        A_inv_soln_s = result['A_inv_soln_s']    
        
    
        prob_s = f"""
        Find inverse of matrix:
        $\\displaystyle 
        {latex(A)}
        $
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {A_inv_soln_s}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle 
        {latex(A_inv)}
        $
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass
    
    
    def _problem_solve_equations_by_inverse_matrix(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        equations = True, 
                        solutions = True,
                        inverse = True)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        A = result['A']
        B = result['B']
        X = result['X']
        C = result['C']
        #eqs = result['eqs']
        eqs_latex = result['eqs_latex']
        soln_s = result['soln_s']    
        line_break = result['line_break']
        A_inv = result['A_inv']    
        A_inv_soln_s = result['A_inv_soln_s']    
    
        prob_s = f"""
        Solve:
        <br/>
        {eqs_latex}
        
        """
        prob_s = prob_s.replace("\n", "")
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = soln_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        soln_s = f"""
        <br/>
        <h3>Note: Get inverse matrix:</h3>
        {A_inv_soln_s}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        
        answer_s = f"""
        $\\displaystyle 
        {latex(X)} = {latex(B)}
        $
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass
    
    
    def _problem_solve_equations_by_row_operation(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        equations = True, 
                        solutions = True,
                        inverse = True,
                        row_operation = True,
                        )

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        A = result['A']
        B = result['B']
        X = result['X']
        C = result['C']
        #eqs = result['eqs']
        eqs_latex = result['eqs_latex']
        #soln_s = result['soln_s']    
        line_break = result['line_break']
        #A_inv = result['A_inv']    
        #A_inv_soln_s = result['A_inv_soln_s']    
        row_operations = result['row_operations']    
        
    
        prob_s = f"""
        Solve:
        <br/>
        {eqs_latex}
        
        """
        prob_s = prob_s.replace("\n", "")
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        #soln_s = soln_s.replace("\n", "").replace(line_break, "<br/>")
        #problemTemplate.add_solution(soln_s.strip(), 'text/html')

        soln_s = f"""
        We have 
        $\\displaystyle AX = C  \\implies {latex(A)} {latex(X)} = {latex(C)}$
        
        {row_operations}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        
        answer_s = f"""
        $\\displaystyle 
        {latex(X)} = {latex(B)}
        $
        """
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
    
        return problemTemplate
        pass

    def _problem_solve_a_determinant(self, *args, **kwargs):
        """
        how to calculate value of a determinant


        doc_style: xv_doc
        """

        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_problem_solve_a_determinant()

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate
    
        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_solve_le_by_crammer_rule(self, *args, **kwargs):
        """
        Solve linear equations by crammer rule


        doc_style: xv_doc
        """
        from xv.math.algebra import LinearEquationManager
        ke = LinearEquationManager(verbose = self.verbose)
        problem_type = '_problem_solve_le_by_crammer_rule'
        
        kwargs['problem_type'] = problem_type
        problemTemplate = ke.getProblemTemplate(*args, **kwargs)
        
        return problemTemplate


    def _problem_solve_by_plot_equations(self, *args, **kwargs):
        
        matrix_helper = self.base_helper.get_algebra_helper().get_matrix_helper()
        result = matrix_helper.get_foundation_to_concept_of_matrix(
                        #equations = True, 
                        #solutions = True,
                        plot_equations = True,
                        )

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        if result is None:
            prob_s = f"""
            Failed to get questions
            """
            problemTemplate.add_problem(prob_s.strip(), 'text/html')
            return problemTemplate

        A = result['A']
        B = result['B']
        X = result['X']
        C = result['C']
        #eqs = result['eqs']
        #eqs_latex = result['eqs_latex']
        line_break = result['line_break']
        svg_str = result['svg_str']    
        
        X_str = list_to_str(list(X), last_sep = ' and ')
    
        prob_s = f"""
        Draw graph of the following matrix relation and find the values of {X_str}
        <br/>
        <br/>
        $\\displaystyle {latex(A)} {latex(X)} = {latex(C)}$
        """
        prob_s = prob_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        Graph of:
        <br/>
        <br/>
        $\\displaystyle {latex(A)} {latex(X)} = {latex(C)}$
        <br/>
        <br/>
        $\\displaystyle \\implies {latex(X)} = {latex(B)}$
        """
        soln_s = soln_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_solution(svg_str, 'text/svg')
        
        
        answer_s = soln_s
        answer_s = answer_s.replace("\n", "").replace(line_break, "<br/>")
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        problemTemplate.add_answer(svg_str, 'text/svg')
    
        return problemTemplate
        pass
    
    
    def _problem_matrix_notation(self, *args, **kwargs):
        keywords = kwargs.get('keywords', 'matrix notation')
        return self._TEMPLATE_CUSTOM_QUESTIONS(keywords, **kwargs)
        pass

    def _problem_matrix_operations(self, *args, **kwargs):
        keywords = kwargs.get('keywords', 'matrix operations')
        return self._TEMPLATE_CUSTOM_QUESTIONS(keywords, **kwargs)
        pass

    def _problem_vector_space_notation(self, *args, **kwargs):
        keywords = kwargs.get('keywords', 'vector space')
        return self._TEMPLATE_CUSTOM_QUESTIONS(keywords, **kwargs)
        pass

    def _problem_vector_operations(self, *args, **kwargs):
        keywords = kwargs.get('keywords', 'vector operations')
        return self._TEMPLATE_CUSTOM_QUESTIONS(keywords, **kwargs)
        pass

    def _problem_discrete_fast_fourier_transform(self, *args, **kwargs):
        keywords = kwargs.get('keywords', 'discrete fast fourier transform')
        return self._TEMPLATE_CUSTOM_QUESTIONS(keywords, **kwargs)
        pass


