'''
Created on 01-Apr-2021
@author: bthakur
'''
import os 
#import decimal
from functools import reduce

import numpy as np
import random
import inspect
import traceback
from sympy import (simplify, sympify, log, symbols, Eq, Matrix, latex, 
                   solve, Rational, diag)

from xv.km.util import latex_fraction
from xv.util import toIntOrFloat


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


# _long_arithmetic_process_manager
# LongArithmeticProcessManager
        
class LongArithmeticProcessManager(BaseManager):
    '''
    Simple arithmetic operations


    Minimum Grade: 3
    Maximum Grade: 7


    Examples
    --------
    ke = LongArithmeticProcessManager()
    ke 
    
    ke.printProblemTypes()
    ke.helpProblemType(0)
    
    ke.getRandomProblem()
    ke.getRandomProblem(problem_type = 0)


    ke.printProblem()
    ke.printAnswer()
    ke.printSolution()


    doc_style: xv_doc
    ''' 

    
    def __new__(cls, *args, **kwargs):
        instance = super(LongArithmeticProcessManager, cls).__new__(cls, *args, **kwargs)
        for base_class in LongArithmeticProcessManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        self._equation = None
        pass
    


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_add_integers',
            ]) 
        pass

    def _problem_add_integers(self, *args, **kwargs):
        """
        Add two or more integers 

        doc_style: xv_doc
        """

        lap_helper = self.base_helper.get_arithmetic_helper().get_long_arithmetic_process_helper()
        
        result = lap_helper.get_problem_add_integers(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        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['prob_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_add_multiple_numbers(self, *args, **kwargs):
        """
        Add multiple integers 


        Minimum Grade: 3
        Maximum Grade: 4

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_add_multiple_numbers')


        doc_style: xv_doc
        """

        
        nums = []
        while len(nums) < 20:
            nums.append(random.randint(0, 9))
            pass
        
        n1s = random.sample(nums, random.randint(2, 4))
        n2s = random.sample(nums, random.randint(2, 4))
        n3s = random.sample(nums, random.randint(2, 4))
        n4s = random.sample(nums, random.randint(2, 4))

        ns = ["(" + " + ".join([str(x) for x in n1s]) + ")",
              "(" + " + ".join([str(x) for x in n2s]) + ")",
              " + ".join([str(x) for x in n3s]),
              " + ".join([str(x) for x in n4s])] 
        
        ns_list = random.sample(ns, random.randint(2, 4))
        ns_list_str = " + ".join(ns_list)
        
        ns_vals = []
        for i, ns_item in enumerate(ns_list):
            if i == 0:
                ns_vals.append(sympify(ns_item))
                continue
            if not "(" in ns_list[i-1] and not "(" in ns_list[i]:
                ns_vals[-1] = ns_vals[-1] + sympify(ns_item)
            else:
                ns_vals.append(sympify(ns_item))
            pass
        
        answer = sum(ns_vals)
        
        ns_val_str = " + ".join([str(x) for x in ns_vals])
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Solve:
        $\\displaystyle {ns_list_str}$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        $\\displaystyle {ns_list_str}$

        $\\displaystyle = {ns_val_str}$ \t\t Add numbers in brackets together
        
        $\\displaystyle = {answer}$
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer, 'text/html')
        
        return problemTemplate

    def _get_random_number_arr(self, size = 5):
        n_arr = []
        while len(n_arr) < size:
            if random.choice([True, False]):
                n_arr.append(0)
                continue
            n_arr.append(random.randint(1, 9))
            pass
        return n_arr
        pass

    def _convert_number_arr_to_str(self, n_arr):
        
        n_arr_str = []
        for x in n_arr:
            if x == 0 and len(n_arr_str) == 0:
                continue
            n_arr_str.append(f"{x}")
            pass
        
        n_arr_str = "".join(n_arr_str)
        return n_arr_str
        pass

    def _convert_number_arr_to_expanded_str(self, n_arr):
        n_arr.reverse()
        
        n_arr_str = []
        for i, x in enumerate(n_arr):
            if x == 0:
                continue
            n_arr_str.append(f"{toIntOrFloat(x * 10 ** i)}")
            pass
        
        n_arr_str.reverse()
        
        n_arr_str = " + ".join(n_arr_str)
        return f"$\\displaystyle {n_arr_str} $"
        pass

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


        Minimum Grade: 3
        Maximum Grade: 4


        doc_style: xv_doc
        """

        n_size = random.randint(2, 5)
        size = random.randint(3, 6)
        n_cum_arr = np.zeros(size)
        n_arrs = []
        while len(n_arrs) < n_size:
            num = self._get_random_number_arr(size)
            if sum(num) == 0:
                continue
            n_arrs.append(num)
            n_cum_arr = n_cum_arr + np.array(num)
            pass
        
        
        n_arrs_strs = [self._convert_number_arr_to_str(n_arr) 
                       for n_arr in n_arrs]
        n_arrs_strs = " + ".join(n_arrs_strs)
        
        n_arrs_expanded_strs = [self._convert_number_arr_to_expanded_str(n_arr) 
                       for n_arr in n_arrs]
        n_arrs_expanded_strs = "\n + ".join(n_arrs_expanded_strs)
        
        
        n_cum_arr_str = self._convert_number_arr_to_expanded_str(list(n_cum_arr)) 
        
        n_answer = sympify(n_arrs_strs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Add:
        {n_arrs_strs}
        
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        {n_arrs_strs}

        = {n_arrs_expanded_strs}
        
        = {n_cum_arr_str}
        
        = {n_answer}
        """
        
        answer_s = f"""
        {n_answer}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    
    def _problem_subtract(self, *args, **kwargs):
        """
        Subtraction of integers 


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_subtract')


        doc_style: xv_doc
        """

        n2  = random.randint(1, 6)
        n1  = n2 + random.randint(1, 6)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_subtract_question(n1, n2)
        q2 = self._get_subtract_question(n2, n1)

        
        explanations = ['', '']
        if n1 < 10 and n2 < 10:
            explanations[0] = f"""
            Suppose there is a match between positive and negative teams. 
            Positive team scores {n1} and negative team {n2}. 
            The positive team wins by {abs(n1-n2)}. Hence the answer is +{n1-n2}
            """

            explanations[1] = f"""
            Suppose there is a match between positive and negative teams. 
            Positive team scores {n2} and negative team {n1}. 
            The negative team wins by {abs(n1-n2)}. Hence the answer is {n2-n1}
            """

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[1]}
        
        {q2[0]} = {q2[1]}
        """
        
        soln_s = f"""
        {explanations[0]}        
        {q1[0]} = {q1[1]}
        
        {explanations[1]}        
        {q2[0]} = {q2[1]}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


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


        Minimum Grade: 3
        Maximum Grade: 4


        doc_style: xv_doc
        """

        n_size = 2
        size = random.randint(3, 5)
        n_cum_arr = np.zeros(size)
        n_arrs = []
        while len(n_arrs) < n_size:
            num = self._get_random_number_arr(size)
            if sum(num) == 0:
                continue
            n_arrs.append(num)
            if len(n_arrs) == 1:
                n_cum_arr = n_cum_arr + np.array(num)
            else:
                n_cum_arr = n_cum_arr - np.array(num)
            pass
        
        
        n_arrs_strs = [self._convert_number_arr_to_str(n_arr) 
                       for n_arr in n_arrs]
        n_arrs_strs = f"{n_arrs_strs[0]} - {n_arrs_strs[1]}"
        
        n_arrs_expanded_strs = [self._convert_number_arr_to_expanded_str(n_arr) 
                       for n_arr in n_arrs]
        n_arrs_expanded_strs[1] = n_arrs_expanded_strs[1].replace("+", "-")

        n_arrs_expanded_strs = f"""
        {n_arrs_expanded_strs[0]} 
        - {n_arrs_expanded_strs[1]}
        """.strip()
        
        
        n_cum_arr_str = self._convert_number_arr_to_expanded_str(list(n_cum_arr)) 
        
        n_answer = sympify(n_arrs_strs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')


        problem_s = f"""
        Add:
        {n_arrs_strs}
        
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        {n_arrs_strs}

        = {n_arrs_expanded_strs}
        
        = {n_cum_arr_str}
        
        = {n_answer}
        """
        
        answer_s = f"""
        {n_answer}
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_subtract_multiple_numbers(self, *args, **kwargs):
        """
        Subtraction of multiple integers 


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_subtract_multiple_numbers')


        doc_style: xv_doc
        """

        nums = []
        while len(nums) < 20:
            nums.append(random.randint(0, 9))
            pass
        
        n1s = random.sample(nums, random.randint(2, 4))
        n2s = random.sample(nums, random.randint(2, 4))
        n3s = random.sample(nums, random.randint(2, 4))
        n4s = random.sample(nums, random.randint(2, 4))
        
        num_signs = random.sample(["+", "-", "-", "+"], 4)
        
        ns = [f"{num_signs[0]} (" + " + ".join([str(x) for x in n1s]) + ")",
              f"{num_signs[1]} (" + " + ".join([str(x) for x in n2s]) + ")",
              f"{num_signs[2]} (" + " + ".join([str(x) for x in n3s]) + ")",
              f"{num_signs[3]} (" + " + ".join([str(x) for x in n4s]) + ")"]
        
        ns_list = random.sample(ns, random.randint(2, 4))
        if ns_list[0][0] == '+':
            ns_list[0] = ns_list[0][1:]

        ns_list_str = " ".join(ns_list)
        
        ns_vals = []
        for i, ns_item in enumerate(ns_list):
            if i == 0:
                ns_vals.append(sympify(ns_item))
                continue
            if not "(" in ns_list[i-1] and not "(" in ns_list[i]:
                ns_vals[-1] = ns_vals[-1] + sympify(ns_item)
            else:
                ns_vals.append(sympify(ns_item))
            pass
        
        answer = sum(ns_vals)
        if answer != sympify(ns_list_str):
            print(f"Error in answer {answer} != {sympify(ns_list_str)}")

        ns_val_str = []
        for i, ns_item in enumerate(ns_vals):
            if i == 0:
                ns_val_str.append(f"{ns_item}")
            else:
                if ns_item < 0:
                    ns_val_str.append(f"- {abs(ns_item)}")
                else:
                    ns_val_str.append(f"+ {abs(ns_item)}")
            pass
        
        ns_val_str = " ".join(ns_val_str)
        
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Solve:
        
        $\\displaystyle {ns_list_str}$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        $\\displaystyle {ns_list_str}$

        $\\displaystyle = {ns_val_str}$ \t\t Add numbers in brackets together
        
        $\\displaystyle = {answer}$
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer, 'text/html')
        
        return problemTemplate
    

    def _problem_add_numbers_with_same_trailing_zeroes(self, *args, **kwargs):
        """
        Add numbers with same trailing zeroes 


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_add_numbers_with_same_trailing_zeroes')


        doc_style: xv_doc
        """

        p = random.randint(1, 10)
        n1, n2  = random.sample(list(range(1, 10)), 2)
        n1 = n1 * 10 ** p
        n2 = n2 * 10 ** p
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_add_question(n1, n2, trailing_zeroes = p)
        q2 = self._get_add_question(n2, n1, trailing_zeroes = p)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        {q2[0]} = {q2[-1]}

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        {'-'*70}
        
        
        {self._get_solution_str(q2)}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_subtract_numbers_with_same_trailing_zeroes(self, *args, **kwargs):
        """
        subtract numbers with same trailing zeroes


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_subtract_numbers_with_same_trailing_zeroes')


        doc_style: xv_doc
        """

        p = random.randint(1, 10)
        n1, n2  = random.sample(list(range(1, 10)), 2)
        n1 = n1 * 10 ** p
        n2 = n2 * 10 ** p
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_subtract_question(n1, n2, trailing_zeroes = p)
        q2 = self._get_subtract_question(n2, n1, trailing_zeroes = p)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        {q2[0]} = {q2[-1]}

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        {'-'*70}
        
        
        {self._get_solution_str(q2)}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_add_numbers_with_trailing_zeroes(self, *args, **kwargs):
        """
        add numbers with trailing zeroes


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem type = '_problem_add_numbers_with_trailing_zeroes')


        doc_style: xv_doc
        """

        p1, p2  = random.sample(list(range(1, 10)), 2)
        n1, n2  = random.sample(list(range(1, 10)), 2)
        
        n1p1 = n1 * 10 ** p1
        n2p1p2 = n2 * 10 ** (p1 + p2)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_add_question(n1p1, n2p1p2, trailing_zeroes = p1)
        q2 = self._get_add_question(n2p1p2, n1p1, trailing_zeroes = p1)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        {q2[0]} = {q2[-1]}

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        {'-'*70}
        
        
        {self._get_solution_str(q2)}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_subtract_numbers_with_zero_with_one_without(self, *args, **kwargs):
        """
        subtract numbers with zero with one without


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_subtract_numbers_with_zero_with_one_without')


        doc_style: xv_doc
        """

        p1 = 0
        p2  = random.randint(2, 10)
        n1, n2  = random.sample(list(range(1, 10)), 2)
        
        n1p1 = n1 * 10 ** p1
        n2p1p2 = n2 * 10 ** (p1 + p2)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_subtract_numbers_with_zero_with_one_without(
            n2p1p2, n1p1, trailing_zeroes = p1)

        q2 = self._get_subtract_numbers_with_zero_with_one_without(
            n1p1, n2p1p2, trailing_zeroes = p1)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        {q2[0]} = {q2[-1]}

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        {'-'*70}
        
        
        {self._get_solution_str(q2)}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_subtract_numbers_with_trailing_zeroes(self, *args, **kwargs):
        """
        subtract numbers with trailing zeroes


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem type = '_problem_subtract_numbers_with_trailing_zeroes')


        doc_style: xv_doc
        """

        p1, p2  = random.sample(list(range(1, 5)), 2)
        n1, n2  = random.sample(list(range(1, 10)), 2)
        
        n1p1 = n1 * 10 ** p1
        n2p1p2 = n2 * 10 ** (p1 + p2)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_subtract_numbers_with_zero_with_one_without(
            n2p1p2, n1p1, trailing_zeroes = p1)
        q2 = self._get_subtract_numbers_with_zero_with_one_without(
            n1p1, n2p1p2, trailing_zeroes = p1)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        {q2[0]} = {q2[-1]}

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        {'-'*70}
        
        {self._get_solution_str(q2)}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_multiply(self, *args, **kwargs):
        """
        multiply


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_multiply')


        doc_style: xv_doc
        """

        n1, n2  = random.sample(list(range(1, 10)), 2)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_multiply_question(n1, n2)
        q2 = self._get_multiply_question(n2, n1)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[1]}

        {q2[0]} = {q2[1]}

        """
        
        soln_s = answer_s
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_multiply_numbers_with_trailing_zeroes(self, *args, **kwargs):
        """
        multiply numbers with trailing zeroes


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem type = '_problem_multiply_numbers_with_trailing_zeroes')


        doc_style: xv_doc
        """

        e1, e2  = random.sample(list(range(1, 5)), 2)
        n1, n2  = random.sample(list(range(1, 10)), 2)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_multiply_question(n1, n2, e1, e2)
        q2 = self._get_multiply_question(n1, n2, e2, e1)

        problem_s = f"""
        Solve:

        {q1[0]}

        {q2[0]}

        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        {q2[0]} = {q2[-1]}

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        
        
        {self._get_solution_str(q2)}
        
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_divide(self, *args, **kwargs):
        """
        divide


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_divide')


        doc_style: xv_doc
        """

        n2  = random.randint(1, 6)
        n1  = n2 * random.randint(1, 11)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_divide_question(n1, n2)

        problem_s = f"""
        Solve:

        {q1[0]}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[1]}

        """
        
        soln_s = answer_s
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_divide_numbers_with_trailing_zeroes(self, *args, **kwargs):
        """
        divide numbers with trailing zeroes


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_divide_numbers_with_trailing_zeroes')


        doc_style: xv_doc
        """

        n2  = random.randint(1, 6)
        n1  = n2 * random.randint(1, 11)

        e2  = random.randint(1, 5)
        e1  = e2 + random.randint(1, 5)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        q1 = self._get_divide_question(n1, n2, e1, e2)
        
        problem_s = f"""
        Solve:

        {q1[0]}

        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        answer_s = f"""
        {q1[0]} = {q1[-1]}  

        """
        
        soln_s = f"""
        {self._get_solution_str(q1)}
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate



    def _problem_divide_in_parts(self, *args, **kwargs):
        """
        divide numbers in parts


        Minimum Grade: 3
        Maximum Grade: 6

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_divide_in_parts')


        doc_style: xv_doc

        """

        while True:
            qn = random.randint(1, 9) ** random.randint(1, 2) 
            qe = 10 ** random.randint(0, 3)
            q = qn * qe
            if q != 1:
                break

        p_str = []
        result = []
        for i in range(random.randint(2, 5)):
            temp_n = q * random.randint(1, 9) 
            temp_e = 10 ** random.randint(0, 3) 
            temp = temp_n * temp_e
            p_str.append(str(temp))
            
            temp_result = f"{temp//q}".zfill(len(str(temp)))
            result.append(temp_result)

        p = sympify("".join(p_str))
        result_str = ", ".join(result)
        result_str2 = "".join(result)
        
        expanded_expr = [
            f"$\\displaystyle {latex_fraction(p_str[i], q)}$"
            for i in range(len(p_str))
            ]
        
        expanded_expr_str = ", ".join(expanded_expr)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Solve:
        
        $\\displaystyle {latex_fraction(p, q)}$
        
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        
        $\\displaystyle {latex_fraction(p, q)}$
        
        Divide in parts:
        
        = {expanded_expr_str}
        
        Keep the number of digits same as numerator by adding leading zeroes
        
        = $\\displaystyle {result_str}$
        
        = $\\displaystyle {result_str2}$
        
        Ignore leading zeroes
        
        = $\\displaystyle {p // q}$
        
        
        """
        
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {p // q}$
        """
        
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_fast_addition(self, *args, **kwargs):
        """
        fast addition


        Minimum Grade: 3
        Maximum Grade: 8

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_fast_addition')


        doc_style: xv_doc
        """

        n00 = random.randint(1, 10 ** random.randint(2, 4) - 1 ) 
        n01 = random.randint(1, 10 ** random.randint(2, 4) - 1 ) 
        ns = np.array([n00, n01])
        ns_s = " + ".join(map(str, ns))
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        
        problem_s = f"""
        Solve ${ns_s}$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        We have to calculate ${ns_s}$
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        divisors = {1000: ('thousand', 1),
                    100: ('hundred', 1),
                    10: ('', 10),
                    1: ('', 1),
                    }
                
        
        prev_cum_sum = 0
        cum_sum = 0
        for idx, k in enumerate(divisors.keys()):
            v, m = divisors[k]
            ns_temp = ((ns % (k * 10)) // k) * m
            ns_temp_str = " + ".join(map(str, ns_temp))
            ns_temp_sum = sum(ns_temp)
            
            ns_temp_val = (ns_temp * k) // m
            
            cum_sum = prev_cum_sum + sum(ns_temp_val)
            
            if ns_temp_sum == 0:
                continue
            
            soln_s = f"""
            $\\left({ns_temp_str}\\right)$ {v} $= {ns_temp_sum}$ {v}
            """
            problemTemplate.add_solution(soln_s.strip(), 'text/html')
            
            if prev_cum_sum != 0:
                soln_s = f"""<h6>Sum $= {prev_cum_sum} + {sum(ns_temp_val)} = {cum_sum}$</h6>
                """
                problemTemplate.add_solution(soln_s.strip(), 'text/html')
            
            prev_cum_sum = cum_sum
            pass
        
        
        answer_s = f"""
        ${sum(ns)}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    def _problem_fast_multiply(self, *args, **kwargs):
        """
        fast multiply


        Minimum Grade: 3
        Maximum Grade: 8

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_fast_multiply')


        doc_style: xv_doc
        """

        n1 = random.randint(11, 10 ** random.randint(2, 4) - 1 ) 
        n2 = random.randint(2, 9)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        problem_s = f"""
        Solve $\\displaystyle {n1} * {n2}$
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        divisors = {1000: ('thousand', 1),
                    100: ('hundred', 1),
                    10: ('', 10),
                    1: ('', 1),
                    }
        
        expr_expanded = []
        prev_cum_sum = 0
        cum_sum = 0
        soln_s_arr = []
        for idx, k in enumerate(divisors.keys()):
            if n1 < k:
                continue
            
            v, m = divisors[k]
            n1_temp = ((n1 % (k * 10)) // k) * m 
            
            n1_temp_val = (n1_temp * k) // m
            cum_sum = cum_sum + n1_temp_val * n2
            
            expr_expanded.append(f"$\\displaystyle {n1_temp}$ {v}")
            
            
            soln_s = f"""
            $\\displaystyle {n1_temp}
            $ {v} * $\\displaystyle {n2}
            $  $\\displaystyle = {n1_temp * n2}
            $ {v}
            """
            soln_s_arr.append(soln_s)
            
            #problemTemplate.add_solution(soln_s.strip(), 'text/html')
            
            if prev_cum_sum != 0:
                soln_s = f"""<h6>Sum $\\displaystyle 
                = {prev_cum_sum} + {n1_temp_val * n2} = {cum_sum}$</h6>
                """
                soln_s_arr.append(soln_s)
                #problemTemplate.add_solution(soln_s.strip(), 'text/html')
            
            prev_cum_sum = cum_sum
            pass
        
        expr_expanded_str = " + ".join(expr_expanded)
        soln_s = f"""
        $\\displaystyle 
        {n1} * {n2}
        $ = ( {expr_expanded_str} ) * $\\displaystyle 
        {n2}$
        
        """
        soln_s_arr.insert(0, soln_s)
          
        soln_s_arr_str = "\n".join(soln_s_arr)  
        
        problemTemplate.add_solution(soln_s_arr_str, 'text/html')
        
        answer_s = f"""
        $\\displaystyle {n1 * n2}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_fast_multiply_2(self, *args, **kwargs):
        """
        fast multiply double digit numbers


        Minimum Grade: 3
        Maximum Grade: 8

    
        Examples
        --------
        from xv.math.basicmaths import LongArithmeticProcessManager
        ke = LongArithmeticProcessManager()


        ke.getRandomProblem(problem_type = '_problem_fast_multiply')


        doc_style: xv_doc
        """

        n1 = random.randint(11, 10 ** random.randint(2, 4) - 1 ) 
        n2 = random.randint(11, 99)
        
        n2_arr = np.array([(n2//10) * 10, n2 - (n2//10) * 10])
        n2_arr_str = " + ".join(map(str, n2_arr))

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        problem_s = f"""
        Solve $\\displaystyle {n1} * {n2}$
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        divisors = {1000: ('thousand', 1),
                    100: ('hundred', 1),
                    10: ('', 10),
                    1: ('', 1),
                    }
        
        expr_expanded = []
        prev_cum_sum = 0
        cum_sum = 0
        soln_s_arr = []
        for idx, k in enumerate(divisors.keys()):
            if n1 < k:
                continue
            
            v, m = divisors[k]
            n1_temp = ((n1 % (k * 10)) // k) * m 
            
            n1_temp_val = (n1_temp * k) // m
            cum_sum = cum_sum + n1_temp_val * n2
            
            expr_expanded.append(f"${n1_temp}$ {v}")
            
            
            soln_s = f"""
            $\\displaystyle {n1_temp} \\;{v} * {n2} 
            = {n1_temp} \\;{v} * ({n2_arr_str}) 
            = ({n1_temp * n2_arr[0]} + {n1_temp * n2_arr[1]}) \\;{v} 
            = {n1_temp * n2} \\;{v}$
            """
            soln_s_arr.append(soln_s)
            
            #problemTemplate.add_solution(soln_s.strip(), 'text/html')
            
            if prev_cum_sum != 0:
                soln_s = f"""<h6>Sum $\\displaystyle 
                = {prev_cum_sum} + {n1_temp_val * n2} = {cum_sum}$</h6>
                """
                soln_s_arr.append(soln_s)
                #problemTemplate.add_solution(soln_s.strip(), 'text/html')
            
            prev_cum_sum = cum_sum
            pass
        
        expr_expanded_str = " + ".join(expr_expanded)
        soln_s = f"""
        $\\displaystyle {n1} * {n2}$ = ( {expr_expanded_str} ) * $\\displaystyle 
        {n2}$
        
        """
        soln_s_arr.insert(0, soln_s)
          
        soln_s_arr_str = "\n".join(soln_s_arr)  
        
        problemTemplate.add_solution(soln_s_arr_str, 'text/html')
        
        answer_s = f"""
        $\\displaystyle {n1 * n2}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def convert_numeric_to_words(self, n1):
        divisors = {1000: ('thousand', 1),
                    100: ('hundred', 1),
                    10: ('', 10),
                    1: ('', 1),
                    }
        
        expr_expanded = []
        for idx, k in enumerate(divisors.keys()):
            if n1 < k:
                continue
            
            v, m = divisors[k]
            n1_temp = ((n1 % (k * 10)) // k) * m 
            expr_expanded.append(f"$\\displaystyle {n1_temp}$ {v}")
            pass
        return " + ".join(expr_expanded)
        pass

    def _sample(self, *args, **kwargs):
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
        