'''
Created on 01-Apr-2021
@author: bthakur
'''
import os

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


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

class NumberSystemManager(BaseManager):
    '''
    Number systems in Mathematics


    Minimum Grade: 3
    Maximum Grade: 10


    Examples
    --------
        ke = NumberSystemManager()
        ke.getRandomProblem()
        ke.getRandomProblem(problem_type = 0)
        ...

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

        ke.printProblemTypes()

    doc_style: xv_doc
    '''

    def __new__(cls, *args, **kwargs):
        instance = super(NumberSystemManager, cls).__new__(cls, *args, **kwargs)
        for base_class in NumberSystemManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance

    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        self._equation = None
        pass



    def _TEMPLATE_introduction_to_ns(self, is_advanced, *args, **kwargs):
        ns_helper = self.base_helper.get_common_helper().get_number_system_helper()

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

        n_sample1 = random.randint(1, 9)
        n_sample2 = random.randint(1, 3)
        n_selected, num_types_kv = ns_helper.get_number_with_types(is_advanced)

        n_types_str = "<ol><li>" + "</li><li>".join(num_types_kv.keys()) + "</li></ol>"
        num_types_kv_str = "<ol><li>" + "</li><li>".join(
            [f"{k}:\t\t{v}" for k, v in num_types_kv.items()]
            ) + "</li></ol>"

        n_selected_latex = latex(n_selected)


        use_cache = kwargs.get('use_cache', False)
        svg_str = ns_helper.plot_sets_number_system(num = n_selected,
                                                    is_advanced = is_advanced,
                                                    use_cache = use_cache)

        self.num = n_selected

        problem_s = f"""
        Which type of number is $\\displaystyle {n_selected_latex}$?{n_types_str}

        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_ul = []

        soln_li = f"""
        <b>Natural Numbers:</b>
        <p>Numbers $1, 2, 3, 4, 5, \\cdots$ are called natural numbers.</p>
        <p>Is $\\displaystyle {n_selected_latex}$ is a natural number? {num_types_kv['natural']}</>

        """
        soln_ul.append(soln_li.strip())

        soln_li = f"""
        <b>Whole Numbers:</b>
        <p>Numbers $0, 1, 2, 3, 4, 5, \\cdots$ are called whole numbers.</p>
        <p>Is $\\displaystyle {n_selected_latex}$ is a whole number? {num_types_kv['whole']}</>
        """
        soln_ul.append(soln_li.strip())

        soln_li = f"""
        <b>Integer Numbers:</b>
        <p>Numbers $..., -3, -2, -1, 0, 1, 2, 3, 4, 5, \\cdots$ are called integer numbers.</p>
        <p>Is $\\displaystyle {n_selected_latex}$ is a integer number? {num_types_kv['integer']}</>
        """
        soln_ul.append(soln_li.strip())

        soln_li = f"""
        <b>Rational Numbers:</b>
        <p>When we divide one integer with another integer, it is called rational number.</p>
        <p>Numbers like $\\displaystyle
        {latex_fraction(random.randint(1, 9), random.randint(1, 9))},
        {latex_fraction(random.randint(1, 9), random.randint(1, 9))},
        {latex_fraction(n_sample1, 1)} \\space or \\space {n_sample1},
        {latex_fraction(0, random.randint(1, 9))} \\space or \\space {0},
        {latex_fraction(n_sample2*2, 2)} \\space or \\space {n_sample2},
         \\cdots
        $ are examples of rational numbers.</p>
        <p>Is $\\displaystyle {n_selected_latex}$ is a rational number? {num_types_kv['rational']}</>
        """
        soln_ul.append(soln_li.strip())



        if is_advanced:
            soln_li = f"""
            <b>Real Numbers:</b>
            <p>Numbers that can be expressed on a number line are real numbers.</p>
            <p>Is $\\displaystyle {n_selected_latex}$ is a real number? {num_types_kv['real']}</>
            """
            soln_ul.append(soln_li.strip())

            soln_li = f"""
            <b>Complex Numbers:</b>
            <p>Numbers that can or can not be expressed on a number line are complex numbers.</p>
            <p>Is $\\displaystyle {n_selected_latex}$ is a complex number? {num_types_kv['complex']}</>
            """
            soln_ul.append(soln_li.strip())

            pass

        from xv.util import html_list
        soln_ul = [f"{li}<br/><br/>" for li in soln_ul]
        soln_s = html_list(soln_ul, 'ol')

        problemTemplate.add_solution(soln_s.strip(), 'text/html',
                                     add_html_line_break = False,
                                     convert_tab_to_html_spaces = False)

        problemTemplate.add_solution(svg_str, 'text/svg')

        answer_s = f"""
        {num_types_kv_str}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html',
                                     add_html_line_break = False,
                                     convert_tab_to_html_spaces = False)

        return problemTemplate

    def _TEMPLATE_ns_property(self, prop_name, is_advanced, *args, **kwargs):
        ns_helper = self.base_helper.get_common_helper().get_number_system_helper()


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

        nsp = ns_helper.get_random_ns_prop(prop_name = prop_name, is_advanced = is_advanced)

        problem_s = f"""
        Does {nsp.get('ns_name')} numbers follow {prop_name} property in {nsp.get('op_details')[1]}?
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        Does {nsp.get('ns_name')} numbers follow {prop_name} property in {nsp.get('op_details')[1]}?
        {nsp.get('is_true')}

        {nsp.get('statement')}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {nsp.get('is_true')}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html',
                                     add_html_line_break = False,
                                     convert_tab_to_html_spaces = False)

        return problemTemplate


    def _TEMPLATE_ns_commutative_property(self, is_advanced, *args, **kwargs):
        ns_helper = self.base_helper.get_common_helper().get_number_system_helper()


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

        commutative_properties = ns_helper.get_commutative_properties(is_advanced)
        ns_name = random.choice(list(commutative_properties.keys()))
        commutative_prop_for_type = commutative_properties[ns_name]
        op = random.choice(list(commutative_prop_for_type.keys()))
        commutative_property = commutative_prop_for_type[op]
        op_desc = commutative_property.get('op_desc')
        op_symbol = commutative_property.get('op_symbol')

        n1 = commutative_property.get('n1')
        n2 = commutative_property.get('n2')
        n1n2 = commutative_property.get('n1n2')
        n2n1 = commutative_property.get('n2n1')
        n1n2_latex = commutative_property.get('n1n2_latex')
        n2n1_latex = commutative_property.get('n2n1_latex')
        is_true = commutative_property.get('is_true')

        if is_true:
            is_true_stmt = f"="
        else:
            is_true_stmt = f"\\neq"

        problem_s = f"""
        Does {ns_name} numbers follow commutative property in {op_desc}?
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        Does {ns_name} numbers follow commutative property in {op_desc}?
        {is_true}

        Rule:
        For every {ns_name} numbers a and b,
        $\\displaystyle a {op_symbol} b = b {op_symbol} a$

        Example:

        $\\displaystyle {latex(n1)}$ and $\\displaystyle {latex(n2)}$ are {ns_name} numbers.

        $\\displaystyle {n1n2_latex} {is_true_stmt} {n2n1_latex}$ \t\t\t as $\\displaystyle
        {latex(n1n2)} {is_true_stmt} {latex(n2n1)}$

        """

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {is_true}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html',
                                     add_html_line_break = False,
                                     convert_tab_to_html_spaces = False)

        return problemTemplate



    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_zero_divided_by_a_number',
            '_problem_a_number_divided_by_zero',
            '_problem_introduction_to_ns',
            '_problem_introduction_to_ns_advanced',
            '_problem_examples_of_ns',
            '_problem_real_number_properties',
            '_problem_ns_closure_property',
            '_problem_ns_closure_prop_advanced',
            '_problem_ns_associative_property',
            '_problem_ns_associative_prop_advanced',
            '_problem_ns_commutative_property',
            '_problem_ns_commutative_prop_advanced',
            '_problem_ns_distributivity_property',
            '_problem_ns_distributivity_prop_advanced',
            '_problem_inverse_of_a_number',
            '_problem_inverse_of_a_number_advanced',
            '_problem_sqrt_of_prime_number_is_irrational',
            ])
        pass




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


        doc_style: xv_doc
        """

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

        q = random.randint(1, 10)
        frac = latex_fraction(0, q)

        problem_s = f"""
        Find the value of $\\displaystyle {frac}$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        If we have zero cakes, and we have to divide it among {q} friends,
        each will get zero cakes. So, the answer is zero.
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        0
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

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


        doc_style: xv_doc
        """

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

        p = random.randint(0, 10)
        frac = latex_fraction(p, 0)

        problem_s = f"""
        Find the value of $\\displaystyle {frac}$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        <h1>Somewhat correct explanation:</h1>
        If we have {p} cakes, and we have to divide it among zero friends,
        there is no one to get the take. So, division is meaningless.
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        soln_s = f"""
        <h1>Correct explanation:</h1>
        Let us start dividing {p} with different numbers.
        """
        for i in [10, 5, 2, 1, 0.1, 0.01, 0.001, 10 ** -4, 10 ** -8 ]:
            frac_i = toIntOrFloat(i, 10)
            if not ("e" in f"{frac_i}" or "." in f"{frac_i}"):
                latex_i = f"{frac_i:,}"
            else:
                latex_i = f"{frac_i:,.10f}"
                latex_i = latex_i.rstrip("0")

            frac = latex_fraction(p, latex_i)
            float_num = toIntOrFloat(p/i,2)


            soln_s = f"""{soln_s}
            $\\displaystyle {frac} = {float_num:,}$
            """
            pass


        soln_s = f"""{soln_s}

        As we decrease the divisor, the result is getting bigger and bigger.
        So, when the divisor becomes almost zero, the result will be very large, say infinity.
        When it becomes zero, we have no idea what that number will be.
        So, the result is undefined.
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')


        answer_s = f"""
        undefined
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

    def _problem_introduction_to_ns(self, *args, **kwargs):
        """
        _problem_zero_divided_by_a_number


        doc_style: xv_doc
        """
        is_advanced = False
        return self._TEMPLATE_introduction_to_ns(is_advanced, *args, **kwargs)
        pass

    def _problem_introduction_to_ns_advanced(self, *args, **kwargs):
        """
        Natural, whole, integer, rational, real and complex numbers

        doc_style: xv_doc
        """
        is_advanced = True
        return self._TEMPLATE_introduction_to_ns(is_advanced, *args, **kwargs)
        pass

    def _problem_examples_of_ns(self, *args, **kwargs):
        """
        examples of number systems

        doc_style: xv_doc
        """

        ns_example_helper = self.base_helper.get_common_helper().get_number_system_example_helper()


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


        numbers = ns_example_helper.get_simple_examples()
        ns_types = list(numbers.keys())
        ns_type = random.choice(ns_types)
        n_vals = ns_example_helper.get_simple_examples_by_ns_type(ns_type, count = 3)

        result = ns_example_helper.get_examples_qa_text(ns_type, n_vals)

        n0, n1, n2 = result['vars']


        problem_s = f"""

        {result['q_text']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

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

        answer_s = f"""
        {soln_s}
        """

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

        return problemTemplate

        pass


    def _problem_real_number_properties(self, *args, **kwargs):
        """
        properties of real numbers

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

        natural_numbers = list(range(1, 10))
        whole_numbers = list(range(0, 10))
        integer_numbers = list(range(-9, 10))

        rational_numbers = set()
        while (len(rational_numbers) < 9):
            rational_numbers.add(Rational(random.randint(0, 9), random.randint(1, 9)))
            pass
        rational_numbers = list(rational_numbers)

        irrational_numbers = [sp.sqrt(ueval(x)) for x in range(0, 10)
                              if not isinstance(sp.sqrt(x), int)]

        real_numbers = []
        real_numbers.extend(rational_numbers)
        real_numbers.extend(irrational_numbers)

        numbers = {}
        numbers['natural_numbers'] = {x:x for x in natural_numbers}
        numbers['whole_numbers'] = {x:x for x in whole_numbers}
        numbers['integer_numbers'] = {x:x for x in integer_numbers}
        numbers['rational_numbers'] = {x:x for x in rational_numbers}
        numbers['irrational_numbers'] = {x:toIntOrFloat(x) for x in irrational_numbers}
        numbers['real_numbers'] = {x:toIntOrFloat(x) for x in real_numbers}


        n1, n2, n3 = symbols('a, b, c')

        real_number_props = []

        q_text = f"""
        If ${n1}$, ${n2}$ and ${n3}$ are real numbers, then find examples of
        $\\begin{{gathered}}
        {n1} = {n2} \\\\
        {n1} \\neq {n2} \\\\
        {n1} < {n2} \\\\
        {n1} > {n2}
        \\end{{gathered}} $
        """
        a_text = f"""
        """
        prop_item = {
            "q_text": q_text,
            "a_text": a_text
            }
        real_number_props.append(prop_item)


        q_text = f"""
        If ${n1} > {n2}$ and ${n2} > {n3}$, then give examples of
        $\\begin{{gathered}}
        {n1} = {n3} \\\\
        {n1} \\neq {n3} \\\\
        {n1} < {n3} \\\\
        {n1} > {n3}
        \\end{{gathered}} $
        """
        a_text = f"""
        $$\\displaystyle
        \\begin{{align}}
        \\text{{ if {n1} > {n2} and {n2} > {n3}, then }} \\\\
        & {n1} = {n3} \\text{{ is not possible.}} \\\\
        & {n1} \\neq {n3} \\\\
        & {n1} < {n3}  \\text{{ is not possible.}} \\\\
        & {n1} > {n3}
        \\end{{align}}
        $$
        """
        prop_item = {
            "q_text": q_text.strip(),
            "a_text": a_text.strip()
            }
        real_number_props.append(prop_item)



        identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}_real_number_props"
        random_prop = self.get_no_repeat_random_choice(identifier, real_number_props)

        problem_s = f"""
        {random_prop['q_text']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
            Exactly one of the following is true: ${n1} < {n2}$, ${n1} = {n2}$, ${n1}>{n2}$.
            If ${n1} > {n2}$ and ${n2} > {n3}$ then ${n1} > {n3}$.
            If ${n1} > {n2}$, then ${n2} < {n1}$.
            If ${n1} > {n2}$, then $-{n1} < -{n2}$.
            If ${n1} > {n2}$, then ${n1} \\pm {n3} > {n2} \\pm {n3}$.
            If ${n1} > {n2}$ and ${n3} > 0$, then ${n1} \\times {n3} > {n2} \\times {n3}$.
            If ${n1} > {n2}$ and ${n3} < 0$, then ${n1} \\times {n3} < {n2} \\times {n3}$.
            If ${n1} > {n2}$ and ${n3} > 0$, then ${n1} \\div {n3} > {n2} \\div {n3}$.
            If ${n1} > {n2}$ and ${n3} < 0$, then ${n1} \\div {n3} < {n2} \\div {n3}$.

        """
        soln_s = f"""
        {random_prop['a_text']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

        pass

    def _problem_ns_closure_property(self, *args, **kwargs):
        """
        closure property of natural, whole, integer and rational numbers


        doc_style: xv_doc
        """
        prop_name = 'closure'
        is_advanced = False
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass




    def _problem_ns_closure_prop_advanced(self, *args, **kwargs):
        """
        closure property of real and complex numbers



        doc_style: xv_doc
        """
        prop_name = 'closure'
        is_advanced = True
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_ns_associative_property(self, *args, **kwargs):
        """
        associative property of natural, whole, integer and rational numbers


        doc_style: xv_doc
        """
        prop_name = 'associative'
        is_advanced = False
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_ns_associative_prop_advanced(self, *args, **kwargs):
        """
        associative property of real and complex numbers


        doc_style: xv_doc
        """
        prop_name = 'associative'
        is_advanced = True
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_ns_commutative_property(self, *args, **kwargs):
        """
        commutative property of natural, whole, integer and rational numbers


        doc_style: xv_doc
        """
        prop_name = 'commutative'
        is_advanced = False
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_ns_commutative_prop_advanced(self, *args, **kwargs):
        """
        commutative property of real and complex numbers


        doc_style: xv_doc
        """
        prop_name = 'commutative'
        is_advanced = True
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_ns_distributivity_property(self, *args, **kwargs):
        """
        distributivity property of natural, whole, integer and rational numbers


        doc_style: xv_doc
        """
        is_advanced = False
        prop_name = 'distributivity'
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_ns_distributivity_prop_advanced(self, *args, **kwargs):
        """
        distributivity property of real and complex numbers



        doc_style: xv_doc
        """
        is_advanced = True
        prop_name = 'distributivity'
        return self._TEMPLATE_ns_property(prop_name, is_advanced, *args, **kwargs)
        pass

    def _problem_inverse_of_a_number(self, *args, **kwargs):
        """
        Inverse of natural, whole, integer and rational numbers


        doc_style: xv_doc
        """
        is_advanced = False
        return self._TEMPLATE_inverse_of_a_number(is_advanced, *args, **kwargs)


    def _problem_inverse_of_a_number_advanced(self, *args, **kwargs):
        """
        Inverse of real and complex numbers


        doc_style: xv_doc
        """
        is_advanced = True
        return self._TEMPLATE_inverse_of_a_number(is_advanced, *args, **kwargs)


    def _TEMPLATE_inverse_of_a_number(self, is_advanced, *args, **kwargs):
        ns_helper = self.base_helper.get_common_helper().get_number_system_helper()

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


        while True:
            (ns_name, op, num,
             x, eq, expr_latex, x_vals) = ns_helper.get_random_number(is_advanced)

            if hasattr(num, 'is_number'):
                if num.is_number:
                    break

        problem_s = f"""
        What is {op[1]} inverse of $\\displaystyle {latex(num)}$?
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        if x_vals is None:
            x_vals_str = f"{x_vals}"
            soln_s = f"""
            There is no {op[1]} inverse of $\\displaystyle {expr_latex}$
            """
            problemTemplate.add_solution(soln_s.strip(), 'text/html')
            pass
        else:
            x_vals_str = ", ".join([f"$\\displaystyle {latex(x)}$" for x in x_vals])

            soln_s = f"""
            let {x} is {op[1]} inverse of $\\displaystyle {latex(num)}$, then

            $\\displaystyle {expr_latex}$

            $\\displaystyle \\implies {x} = $ {x_vals_str}

            $\\displaystyle \\implies$ The {op[1]} inverse of $\\displaystyle {latex(num)}$ is {x_vals_str}
            """
            problemTemplate.add_solution(soln_s.strip(), 'text/html')

            pass

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

        return problemTemplate


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


        doc_style: xv_doc
        """
        ir_helper = self.base_helper.get_arithmetic_helper().get_irrational_number_helper()

        result = ir_helper.get_problem_sqrt_of_prime_number_is_irrational(**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['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

