'''
Created on 20-Jan-2022

@author: bthakur
'''

import inspect
import traceback
import random
import numpy as np

from sympy import simplify, Rational, latex

from xv.km.util import latex_fraction
from sympy.core.expr import UnevaluatedExpr

from xv.km.ui import SvgHelper
from xv.util.data import CacheUtil
import matplotlib.pyplot as plt

from ...util import MathUtil
from xv.util import toIntOrFloat

class FastArithmeticHelper:
    
    def __init__(self, **kwargs):
        self.verbose = kwargs.get('verbose', False)
        pass

    def _get_n1_n2(self, n1, n2, ten_exp = 1, **kwargs):
        mixed = kwargs.get('mixed', False)
        advanced = kwargs.get('advanced', False)
        
        data = []
        for i in range(ten_exp + 1):
            data.append(n1 * 10**i)
            data.append(n2 * 10**i)
            if mixed:
                data.append(abs(n1 + n2) * 10**i)
                data.append(abs(n1 - n2) * 10**i)
                pass
            pass
        
        if advanced:
            data_copy = data.copy()
            for x in data_copy:
                for idx in range(len(data_copy)):
                    data.append(abs(data[i] + x))
                    data.append(abs(data[i] - x))
                    pass
                pass
        n1, n2 = random.sample(data, 2)
        return n1, n2
        pass
    

    def get_random_problems(self, ten_exp = 1, count = 4, **kwargs):
        '''
        mixed = True
        advanced = True
        '''
        statements = []
        solns = []
        answers = []
        while len(statements) < count:
            row = self.get_random_problem(ten_exp = ten_exp, **kwargs)
            
            statements.append(row['statement'])
            
            if row.get('soln_statement', None):
                soln_s = row['soln_statement']
            else:
                soln_s = f"""
                $\\displaystyle {row['expr_latex']} = {latex(row['answer'])}
                $""".strip()
                pass
            solns.append(soln_s)
            
            answers.append(f"""$\\displaystyle {latex(row['answer'])}$""")
            pass
        
        result = {
            'statements': self._wrap_in_ol_list(statements),
            'solns': self._wrap_in_ol_list(solns),
            'answers': self._wrap_in_ol_list(answers),
            }
        return result
        pass
    
    def get_random_problem(self, ten_exp = 1, **kwargs):
        op = random.choice([
            "add", "minus", "mul", "div",
            "lcm", "hcf", "pow",
            "mul_unit_sum_10"
            ])
        return self._get_problem(op, ten_exp = ten_exp, **kwargs)
        pass

    
    def _get_problem(self, op, ten_exp = 1, **kwargs):
        method_name = f"get_problem_{op}"
        if not hasattr(self, method_name):
            print(f"Method {method_name} not found.")
            return None
        
        method = getattr(self, method_name)
        return method(ten_exp = ten_exp, **kwargs)
        pass

    def get_problem_add(self, ten_exp = 1, **kwargs):
        n1 = random.randint(0, 9)
        n2 = random.randint(0, 9)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        return self._template_add(n1, n2)
        pass
    
    def get_problem_minus(self, ten_exp = 1, **kwargs):
        n1 = random.randint(0, 9)
        n2 = random.randint(0, 9)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        return self._template_minus(n1, n2)
        pass
    
    def get_problem_mul(self, ten_exp = 1, **kwargs):
        n1 = random.randint(0, 9)
        n2 = random.randint(0, 9)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        return self._template_mul(n1, n2)
        pass
    
    def get_problem_div(self, ten_exp = 1, **kwargs):
        n1 = random.randint(1, 9)
        n2 = random.randint(0, 9)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        return self._template_div(n1 * n2, n2)
        pass
    
    def get_problem_lcm(self, ten_exp = 1, **kwargs):
        n1 = random.randint(1, 9)
        n2 = random.randint(1, 9)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        return self._template_lcm(n1, n2)
        pass
    
    def get_problem_hcf(self, ten_exp = 1, **kwargs):
        n1 = random.randint(1, 20)
        n2 = random.randint(1, 9)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        return self._template_hcf(n1, n2)
        pass
    
    def get_problem_pow(self, ten_exp = 1, **kwargs):
        n1 = random.randint(1, 9)
        n2 = random.randint(0, 3)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)
        if n2 > 3:
            n2 = random.randint(0, 3)
        return self._template_pow(n1, n2)
        pass

    def get_problem_mul_unit_sum_10(self, ten_exp = 1, **kwargs):
        n1 = random.randint(1, 9)
        n2 = random.randint(0, 3)
        n1, n2 = self._get_n1_n2(n1, n2, ten_exp = ten_exp, **kwargs)

        return self._template_mul_unit_sum_10(n1, n2)
        pass

    
    def _template_add(self, n1, n2):
        expr = f"{n1} + {n2}"
        expr_latex = expr
        answer = simplify(expr)

        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Sum of {n1} and {n2}.",
            f"Add of {n1} and {n2}.",
            ]
        statement = random.choice(statements)

        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_minus(self, n1, n2):
        expr = f"{n1} - {n2}"
        expr_latex = expr
        answer = simplify(expr)

        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Deduct {n2} from {n1}.",
            f"You have {n1}. If you take out {n2}, how much is left?",
            ]
        statement = random.choice(statements)

        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_mul(self, n1, n2):
        expr = f"{n1} * {n2}"
        expr_latex = expr
        answer = simplify(expr)
        
        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Multiply {n1} and {n2}.",
            f"Find area of a rectangle whose sides are {n1} and {n2}.",
            ]
        
        statement = random.choice(statements)
        
        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_div(self, n1, n2):
        expr = UnevaluatedExpr(n1) / UnevaluatedExpr(n2)
        expr_latex = latex_fraction(n1, n2)
        
        if n2 == 0:
            answer = "Undefined"
        else:
            answer = toIntOrFloat(n1 / n2)
        
        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Divide {n1} by {n2}.",
            f"Find the other side of a rectangle whose area is {n1} and one side {n2}.",
            ]
        
        statement = random.choice(statements)
        
        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_lcm(self, n1, n2):
        expr = f"LCM of {n1} and {n2}"
        expr_latex = f"LCM({n1}, {n2})"
        answer = MathUtil.lcm(n1, n2)
        
        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Find LCM of {n1} and {n2}.",
            f"You have sticks of lengths {n1} and {n2}. If you stack them separately, when will they have same height?",
            ]
        
        statement = random.choice(statements)
        
        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_hcf(self, n1, n2):
        expr = f"HCF of {n1} and {n2}"
        expr_latex = f"HCF({n1}, {n2})"
        answer = MathUtil.hcf(n1, n2)
        
        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Find HCF of {n1} and {n2}.",
            ]
        
        statement = random.choice(statements)
        
        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_pow(self, n1, n2):
        expr = f"{n1}**{n2}"
        expr_latex = latex(UnevaluatedExpr(n1) ** UnevaluatedExpr(n2))
        answer = n1 ** n2
        
        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"If you multiply {n1} with itself {n2} times, what will be the result?",
            f"What is result of {n1} * {n1} * {n1} ... {n2} times?",
            ]
        
        statement = random.choice(statements)
        
        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'answer': answer,
            }
        return result
        pass
    
    def _template_mul_unit_sum_10(self, n1, n2):
        while n1 >= 100:
            n1 = n1 // 10
            pass

        u1 = random.randint(1, 9)
        u2 = 10 - u1
        
        b1 = n1
        
        n1 = b1 * 10 + u1
        n2 = b1 * 10 + u2
        
        expr = f"{n1}*{n2}"
        expr_latex = latex(UnevaluatedExpr(n1) * UnevaluatedExpr(n2))
        answer = n1 * n2
        
        non_unit_value_str = f"{b1} * {b1 + 1}"
        non_unit_value = simplify(non_unit_value_str)
        unit_value_str = f"{u1} * {u2}"
        unit_value = str(simplify(unit_value_str)).zfill(2)
        
        if n1 < 10 or n2 < 10:
            soln_statement = None
        else:
            soln_statement = f"""
            <b>Computing {n1} * {n2}:</b>
            
            This process applies when sum of unit digits is 10.
            
            Non-unit value: 
            (Multiply non-unit digits-number with its next number)
            ${non_unit_value_str} = {non_unit_value}$
                  
            Multiply unit values (make sure it has two digits):
            ${unit_value_str} = {unit_value}$
                  
            Put them together:
            ${str(non_unit_value)} \\; {str(unit_value)}$
            
            Hence:
            ${n1} * {n2} = {n1 * n2}$
            """
            pass
        
        statements = [
            f"Solve: $\\displaystyle {expr_latex}$",
            f"Multiply {n1} and {n2}.",
            f"Find area of a rectangle whose sides are {n1} and {n2}.",
            ]
        
        statement = random.choice(statements)
        
        result = {
            'n1': n1,
            'n2': n2,
            'expr': expr,
            'expr_latex': expr_latex,
            'statement': statement,
            'soln_statement': soln_statement.strip(),
            'answer': answer,
            }
        return result
        pass
    
    
    
    def _wrap_in_ol_list(self, rows):
        rows_str = [f"<li>{row}<br/><br/></li>"  
                    for row in rows]
        rows_str = "".join(rows_str)
        rows_str = f"<ol>{rows_str}</ol>"
        return rows_str
        pass

    pass
#class FastArithmeticHelper:
