'''
Created on 20-Jan-2022

@author: bthakur
'''

import inspect
import traceback
import random
import numpy as np

from xv.util.htmlutil import (join_with_comma, get_decimal_numbers, 
                              make_decimals_similar, decimal_sums_data, 
                              round_decimal_result, get_option_and_answer_statements,
                              
                              )

from sympy import (Mul, Add, latex,
                   )
import sympy as sp

from xv.km.util import wrap_in_latex
from xv.km.kelements import KProblemTemplate

from xv.util import UtilClass


# _decimal_operation_helper
# DecimalOperationHelper

class DecimalOperationHelper(UtilClass):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        #self.verbose = kwargs.get('verbose', False)
        pass
    

    def _problem_template_arrange_in_order(self, *args, **kwargs):
        '''
        '''

        level = 0
        while True:
            nums = get_decimal_numbers(level = 0,
                                        count = random.randint(3, 5),
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True)

            if nums[0] == int(nums[0]) and nums[1] == int(nums[1]):
                continue
            
            nums_similar = make_decimals_similar(nums)
            nums_similar_ordered = list(sorted(nums_similar))
            
            nums_similar_latex = f"\\\\".join([latex(x) for x in nums_similar])
            nums_similar_ordered_latex = f"\\\\".join([latex(x) for x in nums_similar_ordered])
            
            nums_ordered = list(sorted(nums))
            nums_ordered_latex = f"\\\\".join([latex(x) for x in nums_ordered])
            
            nums_str = [str(x) for x in nums]
            nums_comma_sep = ", ".join(nums_str)

            correct_option = ", ".join([str(x) for x in nums_ordered])

            all_options = [", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                           ", ".join(random.sample(nums_str, len(nums_str))),
                          ]
            all_options = list(set(all_options))

            incorrect_options = [x for x in all_options if x != correct_option]

            if len(incorrect_options) >= 3:
                break
            pass

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = wrap_in_latex(correct_option)
        options = [wrap_in_latex(x) for x in options]


        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Arrange the following numbers in ascending order:
        {nums_comma_sep}
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        Let us make the numbers similar:
        {nums_comma_sep}
        
        Let us make the numbers similar:
        $ \\large{{ {nums_similar_latex} }}$
        
        Now, compare each digit from left to right. The numbers in ascending order are:
        $ \\large{{ {nums_similar_ordered_latex} }}$
        
        Now, let us write the original numbers in ascending order:
        $ \\large{{ {nums_ordered_latex} }}$
        
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_addition(self, *args, **kwargs):
        '''
        '''

        level = 0
        while True:
            nums = get_decimal_numbers(level = 0,
                                        count = 2,
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True)

            if nums[0] == int(nums[0]) and nums[1] == int(nums[1]):
                continue

            result = decimal_sums_data(nums)
            int_sum = sum(result['nums_without_decimals'])

            correct_option = round_decimal_result(sum(nums))

            all_options = [correct_option + nums[0], correct_option + nums[-1],
                           correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2,
                          int_sum,
                           round(int_sum / 10, 1),
                           round(int_sum / 100, 2),
                           round(int_sum / 1000, 3),
                          ]
            all_options = [round_decimal_result(x) for x in all_options]

            incorrect_options = [x for x in all_options if x != correct_option and x > 0]
            incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

            if len(incorrect_options) >= 3:
                break
            pass

        if len(incorrect_options) > 7:
            incorrect_options = incorrect_options[:7]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = wrap_in_latex(correct_option)
        options = [wrap_in_latex(x) for x in options]



        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        What is sum of {join_with_comma(result['nums_latex'])}?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_subtraction(self, *args, **kwargs):
        '''
        '''


        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_decimal_numbers, make_decimals_similar, decimal_diff_data, round_decimal_result
        ##end:include_source##

        def _wrap_in_latex(s):
            from sympy import latex
            return f"$\\displaystyle {latex(s)}$"

        level = 0
        while True:
            nums = get_decimal_numbers(level = 0,
                                        count = 2,
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True)

            if nums[0] == int(nums[0]) and nums[1] == int(nums[1]):
                continue
            nums = list(sorted(nums, reverse = True))

            result = decimal_diff_data(nums)
            nums_without_decimals = result['nums_without_decimals']
            int_diff = abs(nums_without_decimals[0] - nums_without_decimals[1])

            correct_option = round_decimal_result(nums[0] - nums[1])

            all_options = [correct_option + nums[0], correct_option + nums[-1],
                           correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2,
                           int_diff,
                           round(int_diff / 10, 1),
                           round(int_diff / 100, 2),
                           round(int_diff / 1000, 3),
                          ]

            all_options = [round_decimal_result(x) for x in all_options]

            incorrect_options = [x for x in all_options if x != correct_option and x >= 0]
            incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

            if len(incorrect_options) >= 3:
                break
            pass

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = _wrap_in_latex(correct_option)
        options = [_wrap_in_latex(x) for x in options]

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        What is difference between {join_with_comma(result['nums_latex'])}?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_multiplication(self, *args, **kwargs):
        '''
        '''


        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_decimal_numbers, make_decimals_similar, decimal_prod_data, round_decimal_result
        ##end:include_source##

        def _wrap_in_latex(s):
            from sympy import latex
            return f"$\\displaystyle {latex(s)}$"

        level = 0
        while True:
            nums = get_decimal_numbers(level = 0,
                                        count = 2,
                                        max_digits = 3,
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True,
                                        allow_integers = False,
                                        )

            correct_option = round_decimal_result(Mul(*nums))

            all_options = [correct_option + nums[0],
                           correct_option + nums[-1],
                           correct_option - 2,
                           correct_option - 1,
                           correct_option + 1,
                           correct_option + 2,
                           correct_option * 10,
                           correct_option * 100,
                           correct_option / 10,
                           correct_option / 100,
                          ]

            all_options = [round_decimal_result(x) for x in all_options]
            incorrect_options = [x for x in all_options if x != correct_option and x > 0]
            incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

            if len(incorrect_options) >= 3:
                break
            pass

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = _wrap_in_latex(correct_option)
        options = [_wrap_in_latex(x) for x in options]

        result = decimal_prod_data(nums)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        What is product of {join_with_comma(result['nums_latex'])}?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_division(self, *args, **kwargs):
        '''
        '''


        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_decimal_numbers, decimal_similar_data
        from xv.util.htmlutil import make_decimals_similar, decimal_division_data, round_decimal_result
        ##end:include_source##

        def _wrap_in_latex(s):
            from sympy import latex
            return f"$\\displaystyle {latex(s)}$"

        def Divide(*lst):
            return Mul(lst[0], lst[1])
            pass

        level = 0

        while True:
            nums = get_decimal_numbers(level = 0,
                                        count = 2,
                                        max_digits = 3,
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True,
                                        allow_integers = False,
                                        )
            nums = sorted(nums, key = lambda x: len(str(x)), reverse = True)
            result = decimal_division_data(nums)

            nums_result = result['nums_result']
            correct_option = round_decimal_result(eval(f"{nums_result}"))

            all_options = [correct_option + nums[0],
                           correct_option + nums[-1],
                           correct_option - 2,
                           correct_option - 1,
                           correct_option + 1,
                           correct_option + 2,
                           correct_option * 10,
                           correct_option * 100,
                           correct_option / 10,
                           correct_option / 100,
                          ]

            all_options = [round_decimal_result(x) for x in all_options]

            incorrect_options = [x for x in all_options if x != correct_option and x >= 0]
            incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

            if len(incorrect_options) >= 3:
                break
            pass

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = _wrap_in_latex(correct_option)
        options = [_wrap_in_latex(x) for x in options]

        num_latex = result['nums_latex']

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Divide $\\displaystyle
        {num_latex[0]} \\text {{ by }} {num_latex[1]}
        $
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_addition_logical(self, *args, **kwargs):
        '''
        '''


        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_decimal_numbers, make_decimals_similar, decimal_sums_data
        from xv.util.htmlutil import get_logical_inequality_data, decimal_similar_data, round_decimal_result
        ##end:include_source##

        level = 0

        while True:
            nums_set1 = get_decimal_numbers(level = 0,
                                        count = 2,
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True)
            if nums_set1[0] == nums_set1[1]:
                continue

            nums_set2 = get_decimal_numbers(level = 0,
                                        count = 2,
                                        min_max = None,
                                        exclude_range = None,
                                        distinct = True)

            nums_rev = list(reversed(nums_set1))
            nums_set2 = random.choice([nums_rev, nums_set2])

            if random.choice([True, False]):
                nums_set1[1] = nums_set1[1] / 10

            if random.choice([True, False]):
                nums_set2[1] = nums_set2[1] + 1

            if (nums_set1[0] == nums_set2[0] and
                nums_set1[1] == nums_set2[1]):
                continue

            result1 = decimal_sums_data(nums_set1, comments = False)
            result2 = decimal_sums_data(nums_set2, comments = False)

            if abs(sum(nums_set1) - sum(nums_set2)) < min(sum(nums_set1), sum(nums_set2)) :
                break
            pass

        logical_result = get_logical_inequality_data(sum(nums_set1), sum(nums_set2))

        correct_option = logical_result['correct_option']
        options = logical_result['options']

        similar_sums = decimal_similar_data([result1['sum'], result2['sum']])

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        {result1['nums_latex_sum']} ___________ {result2['nums_latex_sum']}
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        <h3>Left hand side:</h3>
        {result1['soln_s']}
        <h3>Right hand side:</h3>
        {result2['soln_s']}

        <h3>Make the LHS and RHS sums similar to comapre them:</h3>
        $\\displaystyle \\text{{ LHS }} = {similar_sums['similar_nums'][0]} $
        $\\displaystyle \\text{{ RHS }} = {similar_sums['similar_nums'][1]} $

        $\\displaystyle \\implies $ LHS {correct_option} RHS
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_expr(self, *args, **kwargs):
        '''
        '''


        ##start:include_source##
        from xv.util.htmlutil import  get_decimal_numbers, make_decimals_similar, decimal_sums_data
        from xv.util.htmlutil import get_random_decimal_bodmas_expr_str, solve_decimal_expr_str, round_decimal_result
        ##end:include_source##

        level = 0
        while True:
            count = random.randint(3, 5)
            nums = get_decimal_numbers(level = 0,
                                        count = count,
                                        max_digits = 4,
                                        exclude_range = None,
                                        distinct = True)

            expr_str = get_random_decimal_bodmas_expr_str(nums)
            result = solve_decimal_expr_str(expr_str)

            expr_value = result['expr_value']

            correct_option = expr_value

            all_options = [*nums,
                           correct_option + nums[0], correct_option + nums[-1],
                           correct_option - nums[0], correct_option - nums[-1],
                           correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2]
            all_options = [round_decimal_result(x) for x in all_options]
            incorrect_options = [x for x in list(set(all_options)) if x != correct_option]
            incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

            if len(incorrect_options) >= 3:
                break
            pass

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        options = [f"$\\displaystyle {sp.latex(x)}$" for x in options]
        correct_option = f"$\\displaystyle {sp.latex(correct_option)}$"

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Solve $\\displaystyle {expr_str}$
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        {result['soln_s']}
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_expr_logical(self, *args, **kwargs):
        '''
        '''
        do_helper = self.base_helper.get_arithmetic_helper().get_decimal_operation_helper()

        ##start:include_source##
        from xv.util.htmlutil import  get_decimal_numbers, make_decimals_similar, decimal_sums_data
        from xv.util.htmlutil import get_random_decimal_bodmas_expr_str, solve_decimal_expr_str, round_decimal_result
        ##end:include_source##

        level = 0
        while True:
            count = random.randint(3, 5)
            nums_set1 = get_decimal_numbers(level = 0,
                                        count = count,
                                        max_digits = 4,
                                        exclude_range = None,
                                        distinct = True)

            expr1_str = get_random_decimal_bodmas_expr_str(nums_set1)

            count = random.randint(3, 5)
            nums_set2 = get_decimal_numbers(level = 0,
                                        count = count,
                                        max_digits = 4,
                                        exclude_range = None,
                                        distinct = True)

            expr2_str = get_random_decimal_bodmas_expr_str(nums_set2)

            result1 = solve_decimal_expr_str(expr1_str)
            result2 = solve_decimal_expr_str(expr2_str)

            expr1_value = result1['expr_value']
            expr2_value = result2['expr_value']

            if abs(expr1_value - expr2_value) < min(expr1_value, expr2_value):
                break
            pass

        ops_data = [
            ["==", "is equal to"],
            ["<", "is less than"],
            [">", "is more than"],
            ["", "can't say"],
        ]

        correct_option = None
        for op, op_text in ops_data[:-1]:
            if eval(f"{expr1_value} {op} {expr2_value}"):
                correct_option = op_text
                pass
            pass
        if correct_option is None:
            correct_option = ops_data[-1][1]

        options = [x[1] for x in ops_data]

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        ${expr1_str}$ ___________ ${expr2_str}$
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        <h3>Left Hand Side </h3>
        = {result1['soln_s']}

        <h3>Right Hand Side </h3>
        = {result2['soln_s']}

        <h3>Therefore,</h3>
        LHS = {expr1_value}
        RHS = {expr2_value}

        LHS {correct_option} RHS
        """


        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    pass
#class DecimalOperationHelper:
