'''
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, list_to_str, CategorizedSampleData, format_integers

# _elementary_integer_helper
# ElementaryIntegerHelper
class ElementaryIntegerHelper:

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




    def get_number_words_pairs(self, category = 'basic'):
        '''
        @category:
        # 'single-digit' for 0-10, 20..., 90
        # basic for 0-10, 20..., 90
        #advanced
        '''
        if category == 'single_digit':
            nums = list(range(0, 10))
            return self.convert_numbers_to_words(nums)

        if category == 'basic':
            nums = list(range(0, 21))
            nums.extend(list(range(30, 100, 10)))
            return self.convert_numbers_to_words(nums)

        if category == 'double_digit':
            nums = list(range(0, 100))
            return self.convert_numbers_to_words(nums)

        if category == 'triple_digit':
            nums = list(range(0, 1000))
            return self.convert_numbers_to_words(nums)

        nums = []
        nums.append(random.randint(0, 9))
        nums.append(random.randint(1, 9) * 10)
        nums.append(random.randint(1, 9) * 10**2)
        for e1 in [0, 1, 2, 3, 6, 9, 12]:
            nums.append(random.randint(1, 9) * 10**e1)
            nums.append(random.randint(10, 99) * 10**e1)
            nums.append(random.randint(100, 999) * 10**e1)

        if category == 'extended':
            nums_extd = []
            for _, x in enumerate(nums):
                if random.choice([True, False]) or len(nums_extd) <= 3:
                    y = random.choice(nums)
                    nums_extd.append(x + y)
                else:
                    y = sum(random.sample(nums, random.randint(2, 3)))
                    nums_extd.append(y)
                pass
            nums.extend(nums_extd)
            pass

        return self.convert_numbers_to_words(nums)
        pass

    def get_multiples_html_table(self, b1, n2):
        style_str = f""" background: pink; vertical-align: middle; text-align: center; width: 50px; height: 50px; border: 1px solid black; """

        trs_list = [[], [], []]
        for idx in range(n2 + 1):
            if idx == 0:
                continue
            v1 = f"{b1:,}"
            v2 = f"{idx * b1:,}"
            v3 = f"{idx:,}"

            tr1 = f"""<tr><td style="{style_str}">{v1}</td><tr>"""
            tr2 = f"""<tr><td style="{style_str}">{v2}</td><tr>"""
            tr3 = f"""<tr><td style="{style_str}">{v3}</td><tr>"""

            trs_list[0].append(tr1)
            trs_list[1].append(tr2)
            trs_list[2].append(tr3)
            pass

        table_htmls = []
        for trs in trs_list:
            trs = "".join(trs)
            trs = f"<table>{trs}</table>"
            table_htmls.append(trs)
        return table_htmls


    def get_number_count_html_table(self, b1, n2):
        trs = []
        for idx in range(n2 + 1):
            if idx == 0:
                v1 = "<b>Count</b>"
                v2 = "<b>Value</b>"
                pass
            else:
                v1 = f"{idx:,}"
                v2 = f"{idx * b1:,}"
                pass

            tr = f"""
            <tr>
                <td>
                {v1}
                </td>
                <td>
                {v2}
                </td>
            <tr>
            """
            trs.append(tr.replace("\n", ""))
            pass

        table_html = "".join(trs)
        table_html = f"<table>{table_html}</table>"
        return table_html

    def get_reverse_count_html_table(self, num_sum, n1, label = None):
        if label is None:
            label = "Reverse count of total"
        trs = []
        for idx in range(n1 + 1):
            if idx == 0:
                v1 = "<b>Count</b>"
                v2 = f"<b>{label}</b>"
                pass
            else:
                v1 = f"{idx:,}"
                v2 = f"{num_sum - idx:,}"
                pass

            tr = f"""
            <tr>
                <td>
                {v1}
                </td>
                <td>
                {v2}
                </td>
            <tr>
            """
            trs.append(tr.replace("\n", ""))
            pass

        table_html = "".join(trs)
        table_html = f"<table>{table_html}</table>"
        return table_html

    def get_verbal_sum(self, category = 'basic'):
        count = random.randint(2, 4)

        people_names = CategorizedSampleData.get_human_names()
        people_names = random.sample(people_names, count)
        people_names_str = list_to_str(people_names, last_sep = ' and ')

        item_names = CategorizedSampleData.get_food_names()
        item_names = random.sample(item_names, count)

        #item_names_str = list_to_str(item_names, last_sep = ' and ')

        number_words_pairs = self.get_number_words_pairs(category = category)
        #nums = random.sample([x[0] for x in nums], count)
        nums = self.get_num_from_number_words_pairs(number_words_pairs, count = count)

        data = {name:nums[idx] for idx, name in enumerate(item_names)}

        num_sum = sum(nums)
        nums_temp = [f"{x:,}" for x in nums]
        nums_sum_str = list_to_str(nums_temp, sep = " + ")
        nums_str = list_to_str(nums_temp, last_sep = ' and ')

        order_str = [f"{v} {k}" for k, v in data.items()]
        order_str = list_to_str(order_str, last_sep = ' and ')

        prob_items = []
        prob_item = f"""
        {len(people_names)} friends, {people_names_str}, order {nums_str} {item_names[0]} respectively. What is the total quantity of the order?
        """
        prob_items.append(prob_item)

        prob_item = f"""
        {people_names[0]} orders {order_str}. What is the total quantity of the order?
        """
        prob_items.append(prob_item)

        prob_s = random.choice(prob_items)

        soln_s = []

        soln_item = f"""
        The total quantity of the order
        = {nums_sum_str}
        """
        soln_s.append(soln_item.strip())

        if num_sum < 40:
            symbs_str = self.nums_to_symbols_html(nums)

            soln_item = f"""
            Let us count each of the numbers together:
            {symbs_str}= {num_sum:,}
            """
            soln_s.append(soln_item.rstrip())
            pass
        else:
            numbers_to_sum_html = self.format_numbers_to_sum_html(nums)

            soln_item = f"""
            Let us write the numbers in proper format and add them, making the number digit in each number the same.
            If they are less, we will add zeroes in the left of the number.

            <div style="font-family: monospace; font-size:2rem; line-height: 1.3em;">{numbers_to_sum_html}</div>
            """
            soln_s.append(soln_item.rstrip())
            pass

        soln_s = "\n".join(soln_s)

        answer_s = f"{num_sum:,}"

        result = {
            'name': people_names,
            'item_names': item_names,
            'data': data,
            'nums': nums,
            'prob_s': prob_s,
            'soln_s': soln_s,
            'answer_s': answer_s,
            }
        return result
        pass

    def get_verbal_diff(self, category = 'basic'):
        count = 2

        people_names = CategorizedSampleData.get_human_names()
        people_names = random.sample(people_names, count)
        people_names_str = list_to_str(people_names, last_sep = ' and ')

        item_names = CategorizedSampleData.get_food_names()
        item_names = random.sample(item_names, count)

        item_names_str = list_to_str(item_names, last_sep = ' and ')

        number_words_pairs = self.get_number_words_pairs(category = category)
        #nums = random.sample([x[0] for x in nums], count)
        nums = self.get_num_from_number_words_pairs(number_words_pairs, count = count)

        data = {name:nums[idx] for idx, name in enumerate(item_names)}

        num_sum = sum(nums)
        nums_temp = [f"{x:,}" for x in nums]
        nums_sum_str = list_to_str(nums_temp, sep = " + ")
        nums_str = list_to_str(nums_temp, last_sep = ' and ')

        order_str = [f"{v} {k}" for k, v in data.items()]
        order_str = list_to_str(order_str, last_sep = ' and ')

        prob_items = []

        prob_item = f"""
        {people_names_str}
        order {num_sum:,} {item_names[0]} in total. If {people_names[0]} orders
        {nums[0]:,} {item_names[0]}, how many {people_names[1]} ordered?
        """.replace("\n", "")

        soln_stmt = f"""
        quantities ordered by {people_names[1]}
        = (total quantity ordered) - (quantities ordered by {people_names[0]})
        """.replace("\n", "")

        prob_items.append([prob_item, soln_stmt])

        prob_item = f"""
        {people_names[0]} orders {num_sum:,} {item_names_str} in total.
        If {nums[0]:,} {item_names[0]} was ordered,
        what was the quantity of {item_names[1]} ordered?
        """.replace("\n", "")

        soln_stmt = f"""
        the quantity of {item_names[1]} ordered
        = (total quantity ordered) - (the quantity of {item_names[0]} ordered)
        """.replace("\n", "")

        prob_items.append([prob_item, soln_stmt])

        prob_s, soln_stmt = random.choice(prob_items)

        soln_s = []

        soln_item = f"""
        {soln_stmt}
        """
        soln_s.append(soln_item.strip())


        if nums[0] < 10 and not nums[0]  in [0, 1]:
            html_table = self.get_reverse_count_html_table(num_sum, nums[0])

            soln_item = f"""
            Let us reverse count the total {num_sum:,} by {nums[0]:,} times:
            {html_table}
            """
            soln_s.append(soln_item.rstrip())
        else:
            soln_item = f"""
            Let us write the numbers in proper format and subtract, making the number digit in each number the same.
            If they are less, we will add zeroes in the left of the number.
            """
            soln_s.append(soln_item.rstrip())
            pass

        numbers_to_diff_html = self.format_numbers_to_diff_html(nums)
        soln_item = f"""
        Hence,
        <div style="font-family: monospace; font-size:2rem; line-height: 1.3em;">{numbers_to_diff_html}</div>
        """
        soln_s.append(soln_item.rstrip())

        soln_s = "\n".join(soln_s)

        answer_s = f"{nums[1]:,}"

        result = {
            'name': people_names,
            'item_names': item_names,
            'data': data,
            'nums': nums,
            'prob_s': prob_s,
            'soln_s': soln_s,
            'answer_s': answer_s,
            }
        return result
        pass


    def convert_number_to_pos_values(self, num):
        r =  10 ** 3
        num_str = self.format_numbers(num)
        pos_values = num_str.split(",")
        pos_values.reverse()

        pos_values = [(x,
                     int(x),
                     r**idx,
                     self.number_to_word(r**idx, plural = False).replace('one ', '')
                    )
                    for idx, x in enumerate(pos_values)]

        pos_values.reverse()
        return pos_values
        pass

    def convert_pos_values_to_str(self, pos_values):
        pos_values_str1 = []
        pos_values_str2 = []
        for pos_value in pos_values:
            if pos_value[2] == 1:
                pos_values_str1.append(f"{pos_value[0].zfill(3)}")
            else:
                pos_values_str1.append(f"{pos_value[0].zfill(3)} {pos_value[3]}")

            pos_values_str2.append(pos_value[0].zfill(3))
            pass
        pos_values_str1 = " + ".join(pos_values_str1)
        pos_values_str2 = ",".join(pos_values_str2)
        return pos_values_str1, pos_values_str2
        pass


    def convert_units_to_words(self, units):
        uts_str = [self.number_to_word(x, plural = True).replace('one ', '')
                   for x in units]
        return uts_str

    def convert_numbers_to_words(self, nums):
        import inflect
        p = inflect.engine()

        nw_pairs = [[x, p.number_to_words(x).replace(' and ', ' ')] for x in nums]
        return nw_pairs


    def convert_number_to_words(self, num, plural = True):
        return self.number_to_word(num, plural = plural)

    def number_to_word(self, num, plural = True):
        import inflect
        p = inflect.engine()
        num_word = p.number_to_words(num)
        if plural:
            num_word = p.plural(num_word)
        return num_word.replace(' and ', ' ')


    def format_numbers_to_sum_html(self, nums):
        nums_str = self.format_numbers(nums)
        nums_str[-1] = f"<u>{nums_str[-1]}</u>"
        nums_str.append(f"""<span style="color: red;">{sum(nums):,}</span>""")
        nums_str = "<br/>".join(nums_str)
        return nums_str




    def format_numbers_to_diff_html(self, nums):
        sum_str = f"&nbsp;{sum(nums):,}"
        nums_str = self.format_numbers(nums)
        diff_html = [sum_str, f"-{nums_str[0]}" ]
        diff_html[1] = f"<u>{diff_html[1]}</u>"

        diff_html.append(f"""<span style="color: red;">&nbsp;{nums_str[1]}</span>""")
        diff_html = "<br/>".join(diff_html)
        return diff_html

    def format_numbers(self, nums):
        return format_integers(nums, leading_space = "&nbsp;")



    def nums_to_symbols_html(self, nums):
        num_sum = sum(nums)

        if num_sum > 40:
            return None
        nums_str = [f"{x:,}" for x in nums]
        nums_str = list_to_str(nums_str, sep = " + ")
        symbs_str = []
        count = 0
        for num in nums:
            tr = []
            td = f"""<td style="width:30px; height:30px; background: white;
                         vertical-align: middle; text-align: center;">{num}: </td>"""
            tr.append(td)
            for idx in range(num):
                count = count + 1
                td = f"""<td style="width:30px; height:30px; background: pink;
                          vertical-align: middle; text-align: center;">{count}</td>"""
                tr.append(td)
                pass
            tr = "".join(tr)
            tr = f"""<tr> {tr} </tr> """
            symbs_str.append(tr)
            pass

        symbs_str = "".join(symbs_str)
        symbs_str = f""" <table style="border-collapse: separate;  border-spacing: 1em .5em; padding: 0 2em 1em 0;"> {symbs_str} </table>"""
        return symbs_str
        pass

    def get_num_from_number_words_pairs_NOT_USED(self, number_words_pairs,
                                        count = None,
                                        sort = True,
                                        reverse = True):
        if count is None:
            count = len(number_words_pairs)

        if count > len(number_words_pairs):
            count = len(number_words_pairs)

        if sort:
            number_words_pairs = sorted(number_words_pairs,
                                        key = lambda x: x[0],
                                        reverse = reverse)

        nums = []
        while len(nums) < count:
            for num, _ in number_words_pairs:
                if random.randint(0, 4) < 3:
                    if not num in nums:
                        nums.append(num)

                if len(nums) >= count:
                    return nums
                pass
            pass

        return nums
        pass

    def get_num_from_number_words_pairs(self, number_words_pairs,
                                        count = None,
                                        sort = True,
                                        reverse = True):
        if count is None:
            count = len(number_words_pairs)

        if count > len(number_words_pairs):
            count = len(number_words_pairs)

        if sort:
            number_words_pairs = sorted(number_words_pairs,
                                        key = lambda x: x[0],
                                        reverse = reverse)

        nums = random.sample([x[0] for x in number_words_pairs], count)
        return nums
        pass


    pass
#class ElementaryIntegerHelper:
