'''
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 UtilClass

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

    def get_random_model(self, **kwargs):
        methods = [self.model_sum_diff,
                   self.model_sum_multiple,
                   self.model_sum_less_than_multiple,
                   self.model_sum_more_than_multiple,
                   ]

        method = random.choice(methods)
        random_model = method()

        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 model_sum_diff(self):
        var_names = self._get_random_var_names(2)
        x, y = var_names.keys()

        '''
        Luka is making lemonade to sell at a school fundraiser.
        His recipe requires 4 times as much water as sugar and
        twice as much sugar as lemon juice.
        He uses 3 cups of lemon juice.
        How many cups of water does he need?
        '''

        x_val = random.randint(10, 50)
        y_val = random.randint(10, 50)
        while x_val == y_val:
            x_val = random.randint(10, 50)

        var_vals = dict()

        solutions = {
            x: x_val,
            y: y_val,
            }

        methods = [self.get_model_sum,
                   self.get_model_diff,
                   ]

        model1, model2 = self._get_model_list(var_names, solutions, 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': var_vals,
            'solutions': solutions,
            }

        return random_model
        pass


    def model_sum_multiple(self):
        var_names = self._get_random_var_names(2)
        m, w = var_names.keys()

        if random.choice([True, False]):
            m_val = random.randint(10, 50)
            w_val = m_val * random.randint(2, 5)
        else:
            w_val = random.randint(10, 50)
            m_val = w_val * random.randint(2, 5)

        var_vals = dict()

        solutions = {
            m: m_val,
            w: w_val,
            }



        methods = [self.get_model_sum,
                   self.get_model_diff,
                   self.get_model_multiple,
                   ]
        model1, model2 = self._get_model_list(var_names, solutions, 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': var_vals,
            'solutions': solutions,
            }

        return random_model
        pass

    def model_sum_less_than_multiple(self):
        var_names = self._get_random_var_names(2)
        m, w = var_names.keys()

        if random.choice([True, False]):
            m_val = random.randint(10, 50)
            w_val = m_val * random.randint(2, 5) - random.randint(1, m_val)
        else:
            w_val = random.randint(10, 50)
            m_val = w_val * random.randint(2, 5) - random.randint(1, w_val)

        var_vals = dict()

        solutions = {
            m: m_val,
            w: w_val,
            }


        methods = [self.get_model_sum,
                   self.get_model_diff,
                   self.get_model_less_than_multiple,
                   ]
        model1, model2 = self._get_model_list(var_names, solutions, 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': var_vals,
            'solutions': solutions,
            }

        return random_model
        pass


    def model_sum_more_than_multiple(self):
        var_names = self._get_random_var_names(2)
        m, w = var_names.keys()

        if random.choice([True, False]):
            m_val = random.randint(10, 50)
            w_val = m_val * random.randint(2, 5) + random.randint(1, m_val)
        else:
            w_val = random.randint(10, 50)
            m_val = w_val * random.randint(2, 5) + random.randint(1, w_val)

        var_vals = dict()

        solutions = {
            m: m_val,
            w: w_val,
            }


        methods = [self.get_model_sum,
                   self.get_model_diff,
                   self.get_model_more_than_multiple,
                   ]
        model1, model2 = self._get_model_list(var_names, solutions, 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': var_vals,
            'solutions': solutions,
            }

        return random_model
        pass


    def get_model_sum(self, var_names, var_vals):
        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, var_vals):


        try:
            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()
            pass
        except Exception as e:
            sError = f"""Error: {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            var_names = {var_names}
            var_vals = {var_vals}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
            pass

        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, var_vals):
        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"{latex_fraction(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)

        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)

        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, var_vals):
        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, var_vals):
        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_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')],
            [(c, 'cakes'), (p, 'pizzas'), (i, 'ice-creams')],
            [(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 ModellingMultiVariableHelper:
