'''
Created on 20-Jan-2022

@author: bthakur
'''

import inspect
import traceback
import random
import numpy as np
import math
from xv.util import lcm

from sympy import (simplify, Rational, latex, 
                   UnevaluatedExpr as ueval,
                   nsimplify,
                   Eq,
                   Symbol, symbols,
                   )
import sympy as sp

from xv.km.util import latex_fraction


from ...util import MathUtil
from ._prime_number_helper import PrimeNumberHelper

from xv.util import UtilClass, list_to_str


# _ratio_helper
# RatioHelper

class RatioHelper(UtilClass):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.prime_number_helper = PrimeNumberHelper(**kwargs) 
        
        pass

    def get_problem_calculate_ratio_of_ints(self, *args, **kwargs):
        
        nums = kwargs.get('nums', None)
        if nums is None:
            nums_seed = np.random.randint(low = 1, 
                                          high = 20, 
                                          size = random.randint(2, 5))  
            
            nums = [int(x) for x in nums_seed * random.randint(1, 10)]
            pass
        
        nums_hcf = math.gcd(*nums)
        
        nums_str = list_to_str(nums, last_sep = f" \\text{{ and }} ")
        
        nums_div_latex = [f""" \\frac{{ {latex(x)} }}{{ {latex(nums_hcf)} }} """ 
                          for x in nums]
        nums_div_latex = " : ".join(nums_div_latex)

        nums_ratios = [x//nums_hcf for x in nums]
        
        nums_ratios_latex = [f"{latex(x)}" for x in nums_ratios]
        nums_ratios_latex = " : ".join(nums_ratios_latex)
        
        prob_s = f"""
        Find the ratio of numbers $\\displaystyle {nums_str}$
        """

        soln_s = f"""
        The greatest common divisor (GCD) of the numbers $\\displaystyle 
        {nums_str} = {nums_hcf}
        $
        
        To get ratio, we have to divide the numbers by the GCD.
        
        Ratio of numbers $\\displaystyle {nums_str}$
        
        $\\displaystyle = {nums_div_latex}$
        
        $\\displaystyle = {nums_ratios_latex}$
        
        """

        answer_s = f"""
        $\\displaystyle {nums_ratios_latex}$
        """
        
        result = {
            'nums_hcf': nums_hcf,
            'nums_ratios': tuple(nums_ratios),
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'nums_ratios_latex': nums_ratios_latex,
            }
        
        return result
        pass

    def get_problem_calculate_ratio_of_rational_numbers(self, *args, **kwargs):
        
        nums = kwargs.get('nums', None)
        if nums is None:
            count = random.randint(2, 4)
            nums = []
            while len(nums) < count:
                nums.append(Rational(random.randint(1, 10), random.randint(2, 10)))
                pass
            pass
        
        denms = [x.q for x in nums]
        
        denms_lcm = lcm(*denms)
        
        nums_str = list_to_str(nums, last_sep = f" \\text{{ and }} ", to_latex = True)
        denms_str = list_to_str(denms, last_sep = f" \\text{{ and }} ", to_latex = True)
        
        
        nums_comm_denm_latex = [f""" \\frac
                            {{ {latex(x.p)} \\times {latex(denms_lcm // x.q)} }}
                            {{ {latex(x.q)} \\times {latex(denms_lcm // x.q)} }} 
                          """ for x in nums]
        nums_comm_denm_latex = " : ".join(nums_comm_denm_latex)

        nums_comm_denm_latex_eval = [f""" \\frac
                            {{ {latex(x.p * denms_lcm // x.q)} }}
                            {{ {latex(x.q * denms_lcm // x.q)} }} 
                          """ for x in nums]
        nums_comm_denm_latex_eval = " : ".join(nums_comm_denm_latex_eval)

        nums_ratios = [x.p * denms_lcm // x.q for x in nums]
        nums_ratios_latex = [f"""{latex(x)}""" for x in nums_ratios]
        
        nums_ratios_latex = " : ".join(nums_ratios_latex)
        
        prob_s = f"""
        Find the ratio of numbers $\\displaystyle {nums_str}$
        """
        
        soln_stmts = []

        soln_stmt = f"""
        To get the ratio of rational numbers, we need to convert them into numbers with a common denominator.
        
        The lowest common multiple (LCM) of the denominators $\\displaystyle 
        {denms_str} = {denms_lcm}
        $
        
        Ratio of numbers $\\displaystyle {nums_str}$
        
        $\\displaystyle = {nums_comm_denm_latex}$
        
        $\\displaystyle = {nums_comm_denm_latex_eval}$
        
        Now, we can remove the common denominator to get the ratio.
        
        $\\displaystyle = {nums_ratios_latex}$
        
        """
        
        soln_stmts.append(soln_stmt.strip())
        
        ints_result = self.get_problem_calculate_ratio_of_ints(nums = nums_ratios)

        nums_hcf = ints_result['nums_hcf']

        if nums_hcf != 1:
            ints_ratios = ints_result['nums_ratios']
            ints_ratios_latex = list_to_str(ints_ratios, 
                                            last_sep = f" \\text{{ and }} ", 
                                            to_latex = True)
            
            soln_stmt = f"""
            Divide the numbers with their greatest common divisor ${nums_hcf}$:
            
            $\\displaystyle = {ints_ratios_latex}$
            
            """
            soln_stmts.append(soln_stmt.strip())
            
            nums_ratios = ints_ratios
            nums_ratios_latex = ints_ratios_latex
            pass
            
        
        soln_s = "\n\n".join(soln_stmts)

        answer_s = f"""
        $\\displaystyle {nums_ratios_latex}$
        """
        
        
        result = {
            'denms_lcm': denms_lcm,
            'nums_ratios': tuple(nums_ratios),
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass


    def get_problem_calculate_ratio_of_decimal_numbers(self, *args, **kwargs):
        
        nums_deci = kwargs.get('nums', None)
        if nums_deci is None:
            count = random.randint(2, 4)
            nums = []
            nums_deci = []
            while len(nums) < count:
                num = Rational(random.randint(1, 100), 10 ** random.randint(0, 3), gcd=1 )
                nums.append(num)
                nums_deci.append(float(num))
                pass
            pass
        else:
            nums = [nsimplify(x) for x in nums_deci]
            pass
        
        denms = [x.q for x in nums]
        
        denms_lcm = lcm(*denms)
        
        nums_deci_str = list_to_str(nums_deci, last_sep = f" \\text{{ and }} ", to_latex = True)
        nums_str = list_to_str(nums, last_sep = f" \\text{{ and }} ", to_latex = True)
        denms_str = list_to_str(denms, last_sep = f" \\text{{ and }} ", to_latex = True)
        
        
        nums_comm_denm_latex = [f""" \\frac
                            {{ {latex(x.p)} \\times {latex(denms_lcm // x.q)} }}
                            {{ {latex(x.q)} \\times {latex(denms_lcm // x.q)} }} 
                          """ for x in nums]
        nums_comm_denm_latex = " : ".join(nums_comm_denm_latex)

        nums_comm_denm_latex_eval = [f""" \\frac
                            {{ {latex(x.p * denms_lcm // x.q)} }}
                            {{ {latex(x.q * denms_lcm // x.q)} }} 
                          """ for x in nums]
        nums_comm_denm_latex_eval = " : ".join(nums_comm_denm_latex_eval)

        nums_ratios = [x.p * denms_lcm // x.q for x in nums]
        nums_ratios_latex = [f"""{latex(x)}""" for x in nums_ratios]
        
        nums_ratios_latex = " : ".join(nums_ratios_latex)
        
        prob_s = f"""
        Find the ratio of numbers $\\displaystyle {nums_deci_str}$
        """
        
        soln_stmts = []
        soln_stmt = f"""
        Let us first convert numbers into rational numbers.
        
        $\\displaystyle {nums_deci_str} $
        
        $\\displaystyle = {nums_str} $
        
        To get the ratio of rational numbers, we need to convert them into numbers with a common denominator.
        
        The lowest common multiple (LCM) of the denominators $\\displaystyle 
        {denms_str} = {denms_lcm}
        $
        
        Ratio of numbers $\\displaystyle {nums_str}$
        
        $\\displaystyle = {nums_comm_denm_latex}$
        
        $\\displaystyle = {nums_comm_denm_latex_eval}$
        
        Now, we can remove the common denominator to get the ratio.
        
        $\\displaystyle = {nums_ratios_latex}$
        
        """
        
        soln_stmts.append(soln_stmt.strip())
        
        ints_result = self.get_problem_calculate_ratio_of_ints(nums = nums_ratios)

        nums_hcf = ints_result['nums_hcf']

        if nums_hcf != 1:
            ints_ratios = ints_result['nums_ratios']
            ints_ratios_latex = list_to_str(ints_ratios, 
                                            last_sep = f" \\text{{ and }} ", 
                                            to_latex = True)
            
            soln_stmt = f"""
            Divide the numbers with their greatest common divisor ${nums_hcf}$:
            
            $\\displaystyle = {ints_ratios_latex}$
            
            """
            soln_stmts.append(soln_stmt.strip())
            
            nums_ratios = ints_ratios
            nums_ratios_latex = ints_ratios_latex
            pass
            

        soln_s = "\n\n".join(soln_stmts)

        answer_s = f"""
        $\\displaystyle {nums_ratios_latex}$
        """
        
        result = {
            'nums_ratios': tuple(nums_ratios),
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass

    def get_problem_calculate_ratio_of_numbers(self, *args, **kwargs):
        
        count = random.randint(3, 5)
        nums_mixed = kwargs.get('nums', None)
        
        if nums_mixed is None:
            
            num_tuples = []
            num = random.randint(1, 10)
            num_tuples.append((num, Rational(num)))
            
            num = Rational(random.choice([1, 3, 4, 8, 9]), random.choice([2, 5, 7]) )
            num_tuples.append((num, num))
            
            num = Rational(random.randint(1, 100), 10 ** random.randint(1, 3), gcd=1 )
            num_tuples.append((float(num), num))
            
            while len(num_tuples) < count:
                num = Rational(random.randint(1, 10), 10 ** random.randint(1, 2) )
                if random.choice([True, False]):
                    num_tuples.append((float(num), num))
                else:
                    num_tuples.append((num, num))
                pass
            
            random.shuffle(num_tuples)
            
            nums_mixed = [x[0] for x in num_tuples]
            nums = [x[1] for x in num_tuples]
            pass
        else:
            nums = [nsimplify(x) for x in nums_mixed]
            pass
        
        denms = [x.q for x in nums]
        
        denms_lcm = lcm(*denms)
        
        nums_mixed_str = list_to_str(nums_mixed, last_sep = f" \\text{{ and }} ", to_latex = True)
        nums_str = list_to_str(nums, last_sep = f" \\text{{ and }} ", to_latex = True)
        denms_str = list_to_str(denms, last_sep = f" \\text{{ and }} ", to_latex = True)
        
        
        nums_comm_denm_latex = [f""" \\frac
                            {{ {latex(x.p)} \\times {latex(denms_lcm // x.q)} }}
                            {{ {latex(x.q)} \\times {latex(denms_lcm // x.q)} }} 
                          """ for x in nums]
        nums_comm_denm_latex = " : ".join(nums_comm_denm_latex)

        nums_comm_denm_latex_eval = [f""" \\frac
                            {{ {latex(x.p * denms_lcm // x.q)} }}
                            {{ {latex(x.q * denms_lcm // x.q)} }} 
                          """ for x in nums]
        nums_comm_denm_latex_eval = " : ".join(nums_comm_denm_latex_eval)

        nums_ratios = [x.p * denms_lcm // x.q for x in nums]
        nums_ratios_latex = [f"""{latex(x)}""" for x in nums_ratios]
        
        nums_ratios_latex = " : ".join(nums_ratios_latex)
        
        prob_s = f"""
        Find the ratio of numbers $\\displaystyle {nums_mixed_str}$
        """
        
        soln_stmts = []
        soln_stmt = f"""
        Let us first convert numbers into rational numbers.
        
        $\\displaystyle {nums_mixed_str} $
        
        $\\displaystyle = {nums_str} $
        
        To get the ratio of rational numbers, we need to convert them into numbers with a common denominator.
        
        The lowest common multiple (LCM) of the denominators $\\displaystyle 
        {denms_str} = {denms_lcm}
        $
        
        Ratio of numbers $\\displaystyle {nums_str}$
        
        $\\displaystyle = {nums_comm_denm_latex}$
        
        $\\displaystyle = {nums_comm_denm_latex_eval}$
        
        Now, we can remove the common denominator to get the ratio.
        
        $\\displaystyle = {nums_ratios_latex}$
        
        """
        
        soln_stmts.append(soln_stmt.strip())
        
        ints_result = self.get_problem_calculate_ratio_of_ints(nums = nums_ratios)

        nums_hcf = ints_result['nums_hcf']

        if nums_hcf != 1:
            ints_ratios = ints_result['nums_ratios']
            ints_ratios_latex = list_to_str(ints_ratios, 
                                            last_sep = f" \\text{{ and }} ", 
                                            to_latex = True)
            
            soln_stmt = f"""
            Divide the numbers with their greatest common divisor ${nums_hcf}$:
            
            $\\displaystyle = {ints_ratios_latex}$
            
            """
            soln_stmts.append(soln_stmt.strip())
            
            nums_ratios = ints_ratios
            nums_ratios_latex = ints_ratios_latex
            pass
            

        soln_s = "\n\n".join(soln_stmts)

        answer_s = f"""
        $\\displaystyle {nums_ratios_latex}$
        """
        
        result = {
            'nums_ratios': tuple(nums_ratios),
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass

    def get_problem_even_odd_divisor_ratio(self, *args, **kwargs):
        
        pn_helper = self.prime_number_helper
        prime_numbers = pn_helper.getPrimeNumbers(start = 3, size = 7)
        
        odd_divisor_symb = Symbol('n')
        
        num_dict = {}
        num_dict[2] = random.randint(2, 5)
        for prime_number in prime_numbers:
            if random.choice([True, False]):
                num_dict[prime_number] = random.randint(1, 3)
            pass
        
        num = sp.Mul(*[k**v for k, v in num_dict.items()])
        
        num_latex = [f"{latex(k**ueval(v))}" for k, v in num_dict.items()]
        num_latex = f" \\cdot ".join(num_latex)
        
        odd_divisors = []
        divisors_2s = []
        for k, v in num_dict.items():
            for x in range(v):
                divisor = k ** ueval(x + 1)
                if k == 2:
                    divisors_2s.append(divisor)
                else:
                    odd_divisors.append(divisor)
                pass
            pass
        
        even_divisors = []
        even_divisors_strs = []
        for odd_divisor in odd_divisors:
            
            even_divisors_str = []
            for divisor_2 in divisors_2s:
                even_divisor = divisor_2 * odd_divisor
                even_divisors.append(even_divisor)
                
                even_divisors_str.append(f"{latex(even_divisor)}")
                pass
            
            even_divisors_str = f",\\quad ".join(even_divisors_str)
            even_divisors_strs.append(f"${even_divisors_str}$")
            pass
        
        
        odd_divisors_str = [f"${latex(item)}$" for item in odd_divisors]
        odd_divisors_str = "<br/>".join(odd_divisors_str)
                
        divisors_2s_latexes = [latex(item) for item in divisors_2s]
        divisors_2s_str = f", \\quad".join(divisors_2s_latexes)
        divisors_2s_sum = " + ".join(divisors_2s_latexes)
        
        odd_divisor_symbs_latexes = [f"{latex(item)} {latex(odd_divisor_symb)}" 
                                     for item in divisors_2s]
        odd_divisor_symbs_str = f", \\quad".join(odd_divisor_symbs_latexes)
        odd_divisor_symbs_sum = " + ".join(odd_divisor_symbs_latexes)
        
        even_sum = sp.Add(*[simplify(divisors_2) for divisors_2 in divisors_2s])
        
        odd_even_ratio = Rational(1, even_sum)

        prob_s = f"""
        Find the ratio of the sum of the odd divisors of $\\displaystyle {num_latex}
        $ to the sum of its even divisors? 
        """

        soln_s = f"""
        ${divisors_2s_str} $ are the divisors of the number.
        
        So, for every odd divisor ${odd_divisor_symb}$, we can form the following odd divisors.
        ${odd_divisor_symbs_str}$
        
        Therefore, the ratio of the sum of its odd divisors to the sum of its even divisors
        
        $\\displaystyle 
        \\begin{{aligned}}
        
        & = \\frac 
        {{ \\Sigma \\text {{ (odd divisors) }} }} 
        {{ \\Sigma \\text {{ (even divisors) }} }}
        \\\\ \\\\
        
        & = \\frac 
        {{ \\Sigma {latex(odd_divisor_symb)} }} 
        {{ \\Sigma \\left( {odd_divisor_symbs_sum} \\right) }}
        \\\\ \\\\
        
        & = \\frac 
        {{1 }} 
        {{ {divisors_2s_sum} }}
        \\qquad \\text {{ cancel the common factor }} \\Sigma {latex(odd_divisor_symb)}
        \\\\ \\\\
        
        & = {latex(odd_even_ratio)}
        
        
        \\end{{aligned}}
        $
        """

        answer_s = f"""
        $\\displaystyle {latex(odd_even_ratio)}$
        """
        
        result = {
            'prob_s': prob_s.replace("\n", " ").strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass

    def get_problem_ratio_with_proportion(self, *args, **kwargs):
        
        pn_helper = self.prime_number_helper
        prime_numbers = pn_helper.getPrimeNumbers(start = 3, size = 10)
        prime_numbers = tuple(random.sample(prime_numbers, 4))
        
        while True:
            prime_numbers_shuffled = tuple(random.sample(prime_numbers, len(prime_numbers)))
            nums = [sp.Mul(*prime_numbers[:2]),
                    sp.Mul(*prime_numbers_shuffled[:2]),
                    sp.Mul(*prime_numbers_shuffled[2:]),
                    sp.Mul(*prime_numbers[2:]),
                      ]
            
            if len(set(nums)) == 4:
                break
            pass
        
        
        missing_idx = random.randint(0, len(nums) - 1)
        
        mod_nums = nums.copy()
        missing_num = Symbol(f"x")
        mod_nums[missing_idx] = missing_num
        
        correct_num = nums[missing_idx]
        
        nums_symbs = [Symbol(f"{latex(x)}") for x in mod_nums]
        
        nums_factr_dict = {nums_symbs[idx]: pn_helper.getPrimeFactors_as_str(x, as_latex = True) 
                           for idx, x in enumerate(mod_nums) 
                           if x != missing_num}
        
        nums_factr_dict = {k:Symbol(f"\\left( {v} \\right)") 
                           for k, v in nums_factr_dict.items()}
        
        missing_eq = Eq(nums_symbs[0] / nums_symbs[1], nums_symbs[2] / nums_symbs[3])
        
        missing_num_ueval = sp.solve(missing_eq, missing_num)[0]
        
        missing_expr = f"{mod_nums[0]} : {mod_nums[1]} :: {mod_nums[2]} : {mod_nums[3]}"
        expr = f"{nums[0]} : {nums[1]} :: {nums[2]} : {nums[3]}"
        
        missing_num_eq_ueval = Eq(missing_num, missing_num_ueval)
        
        missing_num_eq_fctr = Eq(missing_num_eq_ueval.lhs, 
                                  sp.sympify(missing_num_eq_ueval.rhs).subs(nums_factr_dict))
        
        missing_num_eq = Eq(missing_num, correct_num)
        
        prob_s = f"""
        Find value of $\\displaystyle {latex(missing_num)} $ if
        
        $\\displaystyle {missing_expr} $
        """

        soln_s = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        & {missing_expr}
        \\\\ \\\\
        
        \\implies
        & {latex(missing_eq)}
        \\\\ \\\\
        
        \\implies
        & {latex(missing_num_eq_ueval)}
        \\\\ \\\\
        
        \\implies
        & {latex(missing_num_eq_fctr)}
        \\\\ \\\\
        
        \\implies
        & {latex(missing_num_eq)}
        \\\\ \\\\
        
        \\end{{aligned}}
        $
        """

        answer_s = f"""
        $\\displaystyle {latex(correct_num)}$
        """
        
        result = {
            'prob_s': prob_s.replace("\n", " ").strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass

    pass
#class RatioHelper:
