'''
Created on 01-Apr-2021
@author: bthakur
'''
import os 
#import decimal
from functools import reduce
from xv.km.ui import SvgHelper 

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

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


from xv.km.util import PintUnitSystem
from xv.km import SympyUnitSystem


from ..helper import BaseHelper
from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate
from xv.util import html_table
        
class NumberUnitManager(BaseManager):
    '''
    Units in decimal and other number systems


    Minimum Grade: 4
    Maximum Grade: 12


    Examples
    --------
    ke = NumberUnitManager()
    ke 
    
    ke.printProblemTypes()
    ke.helpProblemType(0)
    
    ke.getRandomProblem()
    ke.getRandomProblem(problem_type = 0)
    ke.getRandomProblem(problem_type = 0, dim = 2) 



    doc_style: xv_doc
    ''' 

    
    def __new__(cls, *args, **kwargs):
        instance = super(NumberUnitManager, cls).__new__(cls, *args, **kwargs)
        for base_class in NumberUnitManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        self._equation = None
        self.nuh = self.base_helper.get_common_helper().get_number_unit_helper()
        self.ps = PintUnitSystem(verbose = self.verbose)
        self.ss = SympyUnitSystem(verbose = self.verbose)
        pass
    



    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_large_number_system',
            '_problem_small_number_system',
            '_problem_convert_number_system',
            '_problem_single_unit_conversion',
            '_problem_convert_units',
            '_problem_iupac_number_prefixes',
            '_problem_calculate_dimension',
            '_problem_dimension',
            '_problem_physical_constants',
            '_problem_dimension_of_constant',
            '_problem_compatible_units',
            '_problem_temperature_mp_bp',
            '_problem_temperature_conversion_equation',
            '_problem_temperature_conversion',
            ])
        pass


    def _problem_large_number_system(self, *args, **kwargs):
        """
        Large number system in decimal system 


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_large_number_system')
        


        doc_style: xv_doc
        """

        
        implies = symbols(u'\U000021D2')
        nuh = self.nuh
        base =  10  
        large_number_system = nuh.get_number_system(base = base,
                                                   set_type = 'large')
        self.large_number_system = large_number_system

        number_system_str = nuh.get_number_system_str_expr(
                                large_number_system, base = base)
        
        identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}{base}"
        m = self.get_no_repeat_random_choice(identifier, list(large_number_system.keys()))

        #m = random.choice(list(large_number_system.keys()))
        
        n_prefix = large_number_system.get(m)
        n = base ** m
        n_expr = f"{{{base}}}^{{{m}}}"
                
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
        What name for is ${n_expr}$?
        """
        problemTemplate.add_problem(statement, "text/html")
        
        soln_s = f"""
        ${n_expr}$ = {n_prefix}
        
        To know all names of large number system, type
        ke.large_number_system
        
        {number_system_str}
    
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')


        answer_s = f"""{n_prefix}"""
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate




    def _problem_small_number_system(self, *args, **kwargs):
        """
        Small number system in decimal system 


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_small_number_system')
        


        doc_style: xv_doc
        """

        
        implies = symbols(u'\U000021D2')
        
        nuh = self.nuh
        base =  10
        small_number_system = nuh.get_number_system(base = base,
                                                   set_type = 'small')
        self.small_number_system = small_number_system

        number_system_str = nuh.get_number_system_str_expr(
                                small_number_system, base = base)
        
        identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}{base}"
        m = self.get_no_repeat_random_choice(identifier, list(small_number_system.keys()))
        
        #m = random.choice(list(small_number_system.keys()))
        n_prefix = small_number_system.get(m)
        n = base ** m
        n_expr = f"{{{base}}}^{{{m}}}"
                
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
        What name for is ${n_expr}$?
        """
        problemTemplate.add_problem(statement, "text/html")
        
        soln_s = f"""
        ${n_expr}$ = {n_prefix}
        
        To know all names of number system, type
        ke.small_number_system

        {number_system_str}
    
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')


        answer_s = f"""{n_prefix}"""
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_convert_number_system(self, *args, **kwargs):
        """
        Conversion in decimal system 


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_convert_number_system')
        


        doc_style: xv_doc
        """

        
        implies = symbols(u'\U000021D2')
        
        nuh = self.nuh
        base =  10
        self.number_system = nuh.get_number_system(base = base)
        number_system = nuh.get_number_system(base = base, 
                                             set_type = 'small')
        
        number_system_str = nuh.get_number_system_str_expr(
                                number_system, base = base)
        
        #m1, m2 = random.sample(list(number_system.keys()), 2)
        
        identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}{base}"
        m1, m2 = self.get_no_repeat_random_sample(identifier, 
                                                  list(number_system.keys()), 
                                                  2)
        
        
        qty_unit = random.choice(['meter', 'gram'])
        qty =  random.randint(1, 10)
        
        names1 = nuh.get_unit_names(number_system.get(m1)) 
        names2 = nuh.get_unit_names(number_system.get(m2)) 
        
        n1_prefix = random.choice(names1).replace(" ", "\\;")
        n2_prefix = random.choice(names2).replace(" ", "\\;")

        n1 = base ** m1
        n2 = base ** m2
        n1_expr = f"{{{base}}}^{{{m1}}}"
        n2_expr = f"{{{base}}}^{{{m2}}}"

        result_expr = f"{{{base}}}^{{{m1-m2}}}"
        
        n1_frac = latex_fraction(n1_expr, n1_prefix)
        n2_frac = latex_fraction(n2_prefix, n2_expr)
        
        result_frac = latex_fraction(n1_expr, n2_expr)
                
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
        Convert {qty} ${n1_prefix}$ {qty_unit} to ${n2_prefix}$ {qty_unit}.
        
        Note: You may use the following table:
        {number_system_str} 
        """
        problemTemplate.add_problem(statement, "text/html")
        
        soln_s = f"""
        We know:
        \t ${n1_prefix}$ = ${n1_expr}$
        \t ${n2_prefix}$ = ${n2_expr}$
        
        {qty} ${n1_prefix}$ {qty_unit} 
        
        = ${n2_frac}$ * ${n1_frac}$ * {qty} ${n1_prefix}$ {qty_unit} 
        
        = ${result_frac}$ * {qty} ${n2_prefix}$ {qty_unit}   \t\t\t Note: Cancel ${n1_prefix}$
        
        = {qty} * ${result_expr}$ ${n2_prefix}$ {qty_unit} 
        
        To know all names of number system, type
        ke.number_system
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')


        answer_s = f"""{qty} * ${result_expr}$ ${n2_prefix}$ {qty_unit} """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

    def _problem_single_unit_conversion(self, *args, **kwargs):
        """
        Convert values from one unit to another unit a single conversion


        Minimum Grade: 4
        Maximum Grade: 12


        doc_style: xv_doc
        """
        nuh = self.nuh
        result = nuh.get_problem_to_convert_units()

        conversion_category = nuh.get_random_conversion_category()
        conversion_set = nuh.get_conversion_set(conversion_category)
        
        while True:
            conversion_formula = random.choice(conversion_set)
            unit1, unit_val1 = conversion_formula[0]
            unit2, unit_val2 = conversion_formula[1]
            if unit1.startswith('cube') and unit2.startswith('cube'):
                continue
            if unit1.startswith('square') and unit2.startswith('square'):
                continue
            break
            pass
        
        qty =  random.randint(2, 10)
        
        converted_qty = qty * unit_val2 / unit_val1
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        prob_s = f"""
        convert {qty} {unit1} to {unit2}.

        Use $\\displaystyle 
        {latex(unit_val1)}  \\text {{ {unit1} }} = 
        {latex(unit_val2)}  \\text {{ {unit2} }}
        $
        """
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        
        soln_s = f"""
        We know:
        $\\displaystyle 
        {latex(unit_val1)}  \\text {{ {unit1} }} = 
        {latex(unit_val2)}  \\text {{ {unit2} }}
        $
        
        Therefore, 
        $\\displaystyle 
        {latex(qty)}  \\text {{ {unit1} }}
        $
        
        $\\displaystyle 
        = {latex(qty)}  \\text {{ {unit1} }} \\cdot
        \\frac 
        {{ {latex(unit_val2)}  \\text {{ {unit2} }} }} 
        {{ {latex(unit_val1)}  \\text {{ {unit1} }} }}
        $
        
        $\\displaystyle 
        = {latex(qty)} \\cdot
        \\frac 
        {{ {latex(unit_val2)}  }} 
        {{ {latex(unit_val1)}  }} 
        \\cdot  \\text {{ {unit2} }}
        $
        
        $\\displaystyle 
        = {latex(converted_qty)}  \\text {{ {unit2} }}
        $
        """
        problemTemplate.add_solution(soln_s.strip(), "text/html")
        
        answer_s = f"""
        $\\displaystyle 
        {latex(converted_qty)}  \\text {{ {unit2} }}
        $
        """
        problemTemplate.add_answer(answer_s.strip(), "text/html")
        return problemTemplate



    def _problem_convert_units(self, *args, **kwargs):
        """
        Convert values from one unit to another unit


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_convert_units')
        
        doc_style: xv_doc
        """
        nuh = self.nuh
        result = nuh.get_problem_to_convert_units()

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        problemTemplate.add_problem(result['prob_s'], "text/html")
        
        problemTemplate.add_solution(result['soln_s_part1'], 'text/html')

        problemTemplate.add_solution(result['svg_str'], 'text/svg')
        
        if result['conv_path'] is None:
            problemTemplate.add_solution(result['soln_exception'], 'text/html')
            problemTemplate.add_answer(result['soln_exception'], 'text/html')
            return problemTemplate
        
        problemTemplate.add_solution(result['soln_s_part2'], 'text/html')
        problemTemplate.add_answer(result['answer_s'], 'text/html')
        
        return problemTemplate

    def _problem_iupac_number_prefixes(self, *args, **kwargs):
        """
        IUPAC number system

        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_iupac_number_prefixes')
        


        doc_style: xv_doc
        """

        
        implies = symbols(u'\U000021D2')
        
        iupac_number_prefixes = self.nuh.get_iupac_number_prefixes()
        self.iupac_number_prefixes = iupac_number_prefixes
        
        n = random.choice(list(iupac_number_prefixes.keys()))
        n_prefix = iupac_number_prefixes.get(n)
                
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
        What is IUPAC number prefix for {n}?
        """
        problemTemplate.add_problem(statement, "text/html")
        
        prefixes_table = html_table(self.iupac_number_prefixes, header = ["Number", "IUPAC Prefix"])
        
        soln_s = f"""
        {n_prefix}
        {prefixes_table}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')


        answer_s = f"""{n_prefix}"""
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        


    def _problem_calculate_dimension(self, *args, **kwargs):
        """
        Calculate dimension of a physical quantity

        Minimum Grade: 4
        Maximum Grade: 12

        doc_style: xv_doc
        """
        nu_helper = self.base_helper.get_common_helper().get_number_unit_helper()
        
        result = nu_helper.calculate_random_dim()

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s']}
        """
        problemTemplate.add_problem(prob_s, "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_dimension(self, *args, **kwargs):
        """
        Dimensions in unit systems


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_dimension')
        


        doc_style: xv_doc
        """

        
        implies = symbols(u'\U000021D2')
        dot = symbols(u'\U00002022')
        
        ps = self.ps
        
        units = ps.get_units()
        random_unit = random.choice(units)
        dimension = ps.get_dimension(random_unit)

        units_of_dimension = ps.get_units_of_dimension(random_unit,
                                                       flatten =  True)
        units_of_dimension.remove(random_unit)
        
        if len(units_of_dimension) > 5:
            units_of_dimension = random.sample(units_of_dimension, 5)
        
        units_of_dimension_str = f"\n\t{dot} ".join(units_of_dimension)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
            What is dimension of {random_unit}?
            
            Hint: The following have also same dimensions:
            \t{dot} {units_of_dimension_str}
            """
        problemTemplate.add_problem(statement, "text/html")
        
        soln_s = f"""
            Dimensions of {random_unit} = {dimension}
            """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
            {dimension}
            """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate

    def _problem_physical_constants(self, *args, **kwargs):
        """
        Physical constants used in maths and science


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_physical_constants')
        


        doc_style: xv_doc
        """

        ps = self.ps
        physical_constants_str = ps.get_physical_constants_str(html = True)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
            Write some of important physical constants used in maths and science.
            """
        problemTemplate.add_problem(statement.strip(), "text/html")

        answer_s = f"""
            {physical_constants_str}
            """
        
        problemTemplate.add_solution(answer_s.strip(), 'text/html')

        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate


    def _problem_dimension_of_constant(self, *args, **kwargs):
        """
        Dimensions of constants used in maths and science


        Minimum Grade: 4
        Maximum Grade: 12


    
        Examples
        --------
        from xv.math.algebra import NumberUnitManager
        ke = NumberUnitManager()


        ke.getRandomProblem(problem_type = '_problem_dimension_of_constant')
        


        doc_style: xv_doc
        """

        
        implies = symbols(u'\U000021D2')
        dot = symbols(u'\U00002022')
        
        ps = self.ps
        
        physical_constants = ps.physical_constants
        physical_constant = random.choice(physical_constants)
        physical_constant_str = ps.get_physical_constant_str(physical_constant)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""
            What is dimension and base unit of {physical_constant}?
            """
        problemTemplate.add_problem(statement.strip(), "text/html")
        
        problemTemplate.add_solution(physical_constant_str.strip(), 'text/html')

        soln_s = f"""
        To get names of all compatible units:
        ke.ps.get_compatible_units(unit_name)
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        
        
        
        problemTemplate.add_answer(physical_constant_str.strip(), 'text/html')
        return problemTemplate


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

        doc_style: xv_doc
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        ps = self.ps
        unit_name = random.choice(ps.unit_names)
        
        #quantity = ps.ureg.Quantity(unit_name)
        compatible_units = ps.get_compatible_units(unit_name)
        compatible_units_str = ps.get_compatible_units_to_str(compatible_units)

        problem_s = f"""
        Write compatible units of <b>{unit_name}.</b>
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        Compatible units of <b>{unit_name}:</b>
        
        {compatible_units_str}
        
        <h4>Note:</h4>
        To get all unit names, use
        ke.ps.unit_names
        
        To search:
        ke.ps.search_unit
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        Compatible units of <b>{unit_name}:</b>
        
        {compatible_units_str}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_temperature_mp_bp(self, *args, **kwargs):
        tc_helper = self.base_helper.get_common_helper().get_temperature_conv_helper()
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        problem_s = f"""
        Write the melting point and boiling point of water in 
        1. {tc_helper.names[0]}
        2. {tc_helper.names[1]}
        3. {tc_helper.names[2]}
        """
        
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        1. {tc_helper.names[0]}: 
        Melting point: $\\displaystyle {tc_helper.C_range[0]}{tc_helper.C_unit}$
        Boiling point: $\\displaystyle {tc_helper.C_range[1]}{tc_helper.C_unit}$

        2. {tc_helper.names[1]}:
        Melting point: $\\displaystyle {tc_helper.F_range[0]}{tc_helper.F_unit}$
        Boiling point: $\\displaystyle {tc_helper.F_range[1]}{tc_helper.F_unit}$
        
        3. {tc_helper.names[2]}: 
        Melting point: $\\displaystyle {tc_helper.K_range[0]}{tc_helper.K_unit}$
        Boiling point: $\\displaystyle {tc_helper.K_range[1]}{tc_helper.K_unit}$


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

    def _problem_temperature_conversion_equation(self, *args, **kwargs):
        tc_helper = self.base_helper.get_common_helper().get_temperature_conv_helper()
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        name_idxs = random.sample(list(range(len(tc_helper.names))), 2)
        names = tc_helper.get_names(name_idxs)
        default_soln_s = tc_helper.get_conversion_solution(name_idxs)
        eq_C_F = tc_helper.get_conversion_eq(name_idxs)
        
        problem_s = f"""
        Write equation to convert between {names[0]} and {names[1]}.
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        {default_soln_s}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(eq_C_F)}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


    def _problem_temperature_conversion(self, *args, **kwargs):
        tc_helper = self.base_helper.get_common_helper().get_temperature_conv_helper()
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        name_idxs = random.sample(list(range(len(tc_helper.names))), 2)
        names = tc_helper.get_names(name_idxs)
        units = tc_helper.get_units(name_idxs)
        
        C, F = tc_helper.get_symbs(name_idxs)
        eq_C_F = tc_helper.get_conversion_eq(name_idxs)

        C_val = random.randint(0, 100)
        eq_C_F_subs = eq_C_F.subs({C: Symbol(latex(C_val))})

        eq_C_F1 = eq_C_F.subs({C: C_val})
        eq_solved = Eq(F, solve(eq_C_F1, F)[0])

        
        problem_s = f"""
        Convert $\\displaystyle {C_val}{units[0]}$ into {names[1]}.
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        $\\displaystyle {latex(eq_C_F)}$

        $\\displaystyle \\implies {latex(eq_C_F_subs)}$
        
        $\\displaystyle \\implies {latex(eq_solved)}$
        
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {latex(eq_solved.rhs)}{units[1]}$
        
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate


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


        doc_style: xv_doc
        """

        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

