'''
Created on 12-Jul-2021

@author: bthakur
'''
import random
import inspect
import traceback   #        {inspect.stack()[0][3]}

import re
from sympy import (Eq, symbols, sqrt, latex,
                   Rational, solve, log, nsimplify, E, exp,  
                   sin, cos, I, series, pi)
from sympy.abc import theta
import sympy


from xv.km import CustomLogarithm as Log
from xv.km.util import latex_fraction, latex_exponent
from ....util import LatexEquation
from xv.util import toIntOrFloat
from sympy.core.expr import UnevaluatedExpr


from xv.util import UtilClass

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


    def get_random_model(self, **kwargs):
        var_names = kwargs.get('var_names', None)
        var_vals = kwargs.get('var_vals', None)
        random_model = self._call_model(var_names = var_names, var_vals = var_vals)
    
        var_names = random_model['var_names']
        var_vals = random_model['var_vals']
        
        
        solutions = random_model['solutions']
        
        var_names_str = [f"${latex(k)}: {latex(v)}$" for k,v in var_names.items()]
        var_names_str = "\n".join(var_names_str)
        
        solutions_str = [f"${latex(k)}: {latex(v)}$" for k,v in solutions.items()]
        solutions_str = "\n".join(solutions_str)
        
        prob_s = f"""
        {random_model['statement_header']}
        {random_model['statement']}
        {random_model['statement_footer']}
        """
                
        soln_s = f"""
        <b>Let:</b>
        {var_names_str}
        
        <b>Equations:</b>
        {random_model['eqs_str']}
        
        <b>Solving the equations:</b>
        {solutions_str}
        """
        
        answer_s = f"""
        {solutions_str}
        """
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass

    def _call_model(self, var_names = None, var_vals = None):
        if var_names is None:
            var_names = self._get_random_var_names(2)
        
        if var_vals is None:
            var_vals = [random.randint(10, 50),
                          random.randint(10, 50)]
            while var_vals[0] == var_vals[1]:
                var_vals[1] = random.randint(10, 50)
            
        solutions = dict(zip(list(var_names.keys()), var_vals))
        model_methods = self._get_model_methods()

        model1, model2 = self._get_model_list(var_names, solutions, model_methods, 2)
        
        statement = f"""
        {model1['statement']} {model2['statement']} 
        """
        
        latex_eqs = [model1['latex_eq'], 
                     model2['latex_eq']]
        
        eqs_str = self._get_eqs_str(latex_eqs)
        
        statement_header = "Convert into mathematical model."
        statement_footer = ""
        
        random_model = {
            'statement_header': statement_header,
            'statement': statement,
            'statement_footer': statement_footer,
            'var_names': var_names,
            'latex_eqs': latex_eqs,
            'eqs_str': eqs_str,
            'var_names': var_names,
            'var_vals': dict(),
            'solutions': solutions,
            }
        
        return random_model
        pass
    
    def _get_model_methods(self):
        model_methods = [
                   #self.get_model_sum,
                   self.get_model_diff,
                   #self.get_model_multiple,
                   #self.get_model_less_than_multiple,
                   #self.get_model_more_than_multiple,
                   self.get_conditional_statement,
                   ]
        return model_methods
        pass
    
    
    def get_model_sum(self, var_names = None, var_vals = None):
        x, y = var_names.keys()
        x_label, y_label = var_names.values()
        x_val, y_val = var_vals.values()
        
        statement1 = f"""
        There are {x_val + y_val} {x_label} and {y_label}. 
        """

        statement2 = f"""
        The total of {x_label} and {y_label} is {x_val + y_val}. 
        """
        
        statement = random.choice([statement1, 
                                   statement2,
                                   ])

        
        eq = Eq(x + y, x_val + y_val)
        latex_eq = LatexEquation.from_eq(eq)
        
        model = {
            'statement': statement.strip(),
            'latex_eq': latex_eq
            }
        
        return model
        pass
    
    def get_model_diff(self, var_names = None, var_vals = None):
        x_val, y_val = var_vals.values()

        if x_val >= y_val:
            x, y = var_names.keys()
            x_label, y_label = var_names.values()
        else:
            y_val, x_val = var_vals.values()
            y, x = var_names.keys()
            y_label, x_label = var_names.values()
            
        statement1 = f"""
        There are {x_val - y_val} more {x_label} than {y_label}.
        """

        eq1 = Eq(x, y + x_val - y_val)
        latex_eq1 = LatexEquation(f"{latex(x)}", 
                                 f"{latex(y)} + {x_val - y_val}", eq1)


        statement2 = f"""
        There are {x_val - y_val} less {y_label} than {x_label}.
        """

        eq2 = Eq(y, x - (x_val - y_val))
        latex_eq2 = LatexEquation(f"{latex(y)}", 
                                 f"{latex(x)} - {x_val - y_val}", eq2)

        
        statement, latex_eq = random.choice([(statement1, latex_eq1),
                                             (statement2, latex_eq2)
                                            ])

        model = {
            'statement': statement.strip(),
            'latex_eq': latex_eq
            }
        
        return model
        pass

    def get_model_multiple(self, var_names = None, var_vals = None):
        x_val, y_val = var_vals.values()

        if x_val >= y_val:
            x, y = var_names.keys()
            x_label, y_label = var_names.values()
        else:
            y_val, x_val = var_vals.values()
            y, x = var_names.keys()
            y_label, x_label = var_names.values()

        multiple = x_val // y_val
            
        statement1 = f"""
        The {x_label} are {multiple} times the {y_label}.
        """

        eq1 = Eq(x , multiple * y)
        latex_eq1 = LatexEquation(f"{latex(x)}", 
                                 f"{multiple} * {latex(y)}", eq1)


        statement2 = f"""
        The {y_label} are ${latex_fraction(1, multiple)}$ of {x_label}.
        """

        eq2 = Eq(y , Rational(1, multiple) * x)
        latex_eq2 = LatexEquation(f"{latex(y)}", 
                                 f"{Rational(1, multiple)} * {latex(x)}", eq2)

        
        statement3 = f"""
        The ratio of {x_label} to {y_label} is {multiple}.
        """

        eq3 = Eq(x / y , multiple)
        #latex_eq3 = LatexEquation(f"{latex_fraction(x, y)}", 
        #                         f"{multiple}", eq3)
        
        latex_eq3 = LatexEquation(x / y, 
                                 f"{multiple}", eq3)
        
        statement4 = f"""
        The ratio of {y_label} to {x_label} is 1:{multiple}.
        """

        eq4 = Eq(y / x , Rational(1, multiple))
        #latex_eq4 = LatexEquation(f"{latex_fraction(y, x)}", 
        #                         f"{latex_fraction(1, multiple)}", eq4)
        
        latex_eq4 = LatexEquation(y / x, 
                                 f"{Rational(1, multiple)}", eq4)
        
        
        sOut = f"""
        x = {x}
        y = {y}
        latex_fraction(y, x) = {latex_fraction(y, x)}
        
        """

        statement, latex_eq = random.choice([(statement1, latex_eq1),
                                             (statement2, latex_eq2),
                                             (statement3, latex_eq3),
                                             (statement4, latex_eq4),
                                            ])
        
        model = {
            'statement': statement.strip(),
            'latex_eq': latex_eq
            }
        
        return model
        pass
    
    def get_model_less_than_multiple(self, var_names = None, var_vals = None):
        x_val, y_val = var_vals.values()

        if x_val >= y_val:
            x, y = var_names.keys()
            x_label, y_label = var_names.values()
        else:
            y_val, x_val = var_vals.values()
            y, x = var_names.keys()
            y_label, x_label = var_names.values()

            
        multiple = (x_val // y_val) + 1
        less_by = y_val * multiple - x_val
            
        statement1 = f"""
        The {x_label} are {less_by} less than {multiple} times the {y_label}.
        """

        eq1 = Eq(x , multiple * y - less_by)
        latex_eq1 = LatexEquation(f"{latex(x)}", 
                                 f"{multiple} * {latex(y)} - {less_by}", eq1)
            
            

        statement2 = f"""
        The difference between {x_label} and {multiple} times the {y_label} is {less_by}.
        """
        
        eq2 = Eq(x - multiple * y, less_by)
        latex_eq2 = LatexEquation(f"{latex(x)} - {multiple} * {latex(y)}", 
                                 f"{less_by}", eq2)
            

        statement3 = f"""
        The {x_label} are fewer by {less_by} than {multiple} times the {y_label}.
        """

        eq3 = Eq(x , multiple * y - less_by)
        latex_eq3 = LatexEquation(f"{latex(x)}", 
                                 f"{multiple} * {latex(y)} - {less_by}", eq3)
            
            
        statement4 = f"""
        The {multiple} times {y_label} is {less_by} more than the {x_label}.
        """

        eq4 = Eq(multiple * y, x + less_by)
        latex_eq4 = LatexEquation(f"{multiple} * {latex(y)}", 
                                 f"{latex(x)} + {less_by}", eq4)
            
            
        statement, latex_eq = random.choice([(statement1, latex_eq1),
                                             (statement2, latex_eq2),
                                             (statement3, latex_eq3),
                                             (statement4, latex_eq4),
                                            ])

        model = {
            'statement': statement.strip(),
            'latex_eq': latex_eq
            }
        
        return model
        pass
    
    def get_model_more_than_multiple(self, var_names = None, var_vals = None):
        x_val, y_val = var_vals.values()

        if x_val >= y_val:
            x, y = var_names.keys()
            x_label, y_label = var_names.values()
        else:
            y_val, x_val = var_vals.values()
            y, x = var_names.keys()
            y_label, x_label = var_names.values()

            
        multiple = (x_val // y_val) 
        more_by = x_val - y_val * multiple
            
        statement1 = f"""
        The {x_label} are {more_by} more than {multiple} times the {y_label}.
        """

        eq1 = Eq(x , multiple * y + more_by)
        latex_eq1 = LatexEquation(f"{latex(x)}", 
                                 f"{multiple} * {latex(y)} + {more_by}", eq1)
            

        statement2 = f"""
        The {x_label} exceed {multiple} times the {y_label} by {more_by}.
        """

        eq2 = Eq(x , multiple * y + more_by)
        latex_eq2 = LatexEquation(f"{latex(x)}", 
                                 f"{multiple} * {latex(y)} + {more_by}", eq2)
            
            
        
        statement3 = f"""
        The difference between {x_label} and {multiple} times the {y_label} is {more_by}.
        """
        
        eq3 = Eq(x - multiple * y, more_by)
        latex_eq3 = LatexEquation(f"{latex(x)} - {multiple} * {latex(y)}", 
                                 f"{more_by}", eq3)
            
        
        statement4 = f"""
        There are {more_by} more {x_label} than {multiple} times the {y_label}.
        """

        eq4 = Eq(x , multiple * y + more_by)
        latex_eq4 = LatexEquation(f"{latex(x)}", 
                                 f"{multiple} * {latex(y)} + {more_by}", eq4)
            
            

        statement5 = f"""
        {multiple} times the {y_label} are {more_by} fewer than {x_label}.
        """

        eq5 = Eq(multiple * y, x- more_by)
        latex_eq5 = LatexEquation(f"{multiple} * {latex(y)}", 
                                 f"{latex(x)} - {more_by}", eq5)
            
            
        
        statement, latex_eq = random.choice([(statement1, latex_eq1),
                                             (statement2, latex_eq2),
                                             (statement3, latex_eq3),
                                             (statement4, latex_eq4),
                                             (statement5, latex_eq5),
                                            ])
        


        model = {
            'statement': statement.strip(),
            'latex_eq': latex_eq
            }
        
        return model
        pass


    def get_conditional_statement(self, var_names = None, var_vals = None):
        x, y = var_names.keys()
        x_label, y_label = var_names.values()
        x_val, y_val = var_vals.values()
        
        data = []
        
        if x_val > y_val:
            
            half_diff = toIntOrFloat((x_val - y_val) / 2)
            statement = f"""
            If {half_diff} is added to {y_label} and the same 
            is deducted from {x_label}, they become equal.
            """.replace("\n", "")
            eq = Eq(y + half_diff, x - half_diff)
            latex_eq = LatexEquation(f"{latex(y)} + {latex(half_diff)}", 
                                     f"{latex(x)} - {latex(half_diff)}", eq)
            data.append([statement, latex_eq])
            pass
        elif x_val < y_val:
            double_diff = y_val * 2 - x_val
            statement = f"""
            If {double_diff} is added to {x_label}, it becomes 
            equal to the double of {y_label}.
            """.replace("\n", "")
            eq = Eq(x + double_diff, 2 * y)
            latex_eq = LatexEquation(f"{latex(x)} + {latex(double_diff)}", 
                                     f"2 * {latex(y)}", eq)
            data.append([statement, latex_eq])


            
            double_half_diff = toIntOrFloat(double_diff / 2)
            statement = f"""
            If {double_half_diff} is added to {x_label} and the same 
            is deducted from double of {y_label}, they become equal.
            """.replace("\n", "")
            eq = Eq(x + double_half_diff, 2 * y - double_half_diff)
            latex_eq = LatexEquation(f"{latex(x)} + {latex(double_half_diff)}", 
                                     f"2 * {latex(y)} - {latex(double_half_diff)}", eq)
            data.append([statement, latex_eq])
            
            
            
            pass
        
        
        sqr_diff = toIntOrFloat((y_val **2 - x_val) / 2)
        statement = f"""
        If {sqr_diff} is added to {x_label} and the same 
        is deducted from square of {y_label}, they become equal.
        """.replace("\n", "")
        eq = Eq(x + sqr_diff, y**2 - sqr_diff)
        latex_eq = LatexEquation(f"{latex(x)} + {latex(sqr_diff)}", 
                                 f"{latex(y**2)} - {latex(sqr_diff)}", eq)
        
        data.append([statement, latex_eq])
         
        
        statement, latex_eq = random.choice(data)
        model = {
            'statement': statement,
            'latex_eq': latex_eq
            }
        
        return model
        pass
    
    
    def _get_eqs_str(self, latex_eqs):
        eqs_str = [f'$\\displaystyle {latex_eq}$' for latex_eq in latex_eqs]
        eqs_str = "\n\n".join(eqs_str)
        return eqs_str
        pass
    
    def _get_random_var_names(self, count = 2):
        (a, b, c, d, e, f, g, h, i, j, k, l, 
         m, o, p, q, r, s, t, u, v, w, x, y, z) = symbols('a, b, c, d, e, f, g, h, i, j, k, l, m, o, p, q, r, s, t, u, v, w, x, y, z')
        
        var_names = [
            [(a, 'apples'), (g, 'grapes'), (m, 'mangoes')], 
            [(b, 'boys'), (g, 'girls')], 
            [(m, 'men'), (w, 'women')]
        ]
        
        var_names = random.choice(var_names)
        var_names = random.sample(var_names, count)
        
        return dict(var_names)
        pass


    def _get_model_list(self, var_names, var_vals, methods, count = 2):
        methods = random.sample(methods, count)
        model_list = [method(var_names, var_vals)
            for method in methods
            ]
        return model_list
        pass
    
    pass
#class ModellingTwoVariableHelper:
