'''
Created on 20-Jan-2022

@author: bthakur
'''

import inspect
import traceback
import random
import numpy as np


from xv.util.htmlutil import (join_with_comma, get_decimal_numbers, 
                              make_decimals_similar, decimal_sums_data, 
                              round_decimal_result, get_option_and_answer_statements,
                              
                              )

from xv.util.htmlutil import (get_random_html_shape,
                              int_to_html_shapes,
                              int_to_html_shapes_crossed,
                              get_nums_prod_shapes,
                              get_nums_division_shapes,
                              )


from sympy import (Mul, Add, latex, Symbol,
                   )
import sympy as sp

from xv.km.util import wrap_in_latex
from xv.km.kelements import KProblemTemplate

from xv.util import UtilClass, html_list, list_to_str

# _arithmetic_operation_helper
# ArithmeticOperationHelper


class ArithmeticOperationHelper(UtilClass):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        #self.verbose = kwargs.get('verbose', False)
        pass


    def get_identify_number_stmt(self, nums):
        '''
        @stmt_type: None, all, word
        '''
        prob_soln_stmts = []
  
        stmts = self._get_identify_number_stmts(nums)
        prob_soln_stmts.extend(stmts)

        selected_stmt = random.choice(prob_soln_stmts)
        result = {
            'nums': selected_stmt.get("nums"),
            'selected_num': selected_stmt.get("selected_num"),
            'prob_stmt': selected_stmt.get("prob_stmt").strip(),
            'soln_stmt': selected_stmt.get("soln_stmt", "").strip(),
            'post_soln_stmt': selected_stmt.get("post_soln_stmt", "").strip(),
            }
        return result

    def _get_identify_number_stmts(self, nums):
        '''
        '''
        
        stmt_dict = {
            'nums': nums,
            'prob_stmt': "",
            'soln_stmt': "",
            'post_soln_stmt': "",
            }

        
        prob_soln_stmts = []
        sorted_nums = list(sorted(nums))
        middle_nums = sorted_nums[1:-1]
        
        for idx, num in enumerate(middle_nums):
            if sorted_nums.count(num) != 1:
                continue
            
            org_idx = idx + 1
            selected_num = num
            
            left_limit = random.randint(sorted_nums[org_idx - 1], selected_num - 1)
            right_limit = random.randint(selected_num + 1, sorted_nums[org_idx + 1])
            
            if random.choice([True, False]):
                left_limit = random.randint(sorted_nums[org_idx - 1], selected_num - 1)
                right_limit = random.randint(selected_num + 1, sorted_nums[org_idx + 1])
                in_between_nums = list(range(left_limit, right_limit + 1))
            else:
                right_limit = random.randint(sorted_nums[org_idx - 1], selected_num - 1)
                left_limit = random.randint(selected_num + 1, sorted_nums[org_idx + 1])
                in_between_nums = list(range(left_limit, right_limit - 1, -1))
            
            in_between_nums_latexes_str = self._get_nums_latex_str(in_between_nums, sep = "\\;")

            stmt_type_a = stmt_dict.copy()
            stmt_type_a['selected_num'] = selected_num
            stmt_type_a['prob_stmt'] = f""" 
            Which one of the followings is between 
            {wrap_in_latex(left_limit)} and
            {wrap_in_latex(right_limit)}?
            """.replace("\n", " ").strip()
            
            stmt_type_a['post_soln_stmt'] = f""" 
            <br/>
            Now, count the numbers from {wrap_in_latex(left_limit)} to
            {wrap_in_latex(right_limit)}. 
            <br/>
            <br/>
            $\\displaystyle \\Large
            {{ {in_between_nums_latexes_str} }}
            $
            <br/>
            <br/>
            We see, {list_to_str(in_between_nums[1:-1], last_sep = " and " )} 
            are the numbers in between {wrap_in_latex(left_limit)} and
            {wrap_in_latex(right_limit)}.
            <br/>
            Therefore, the correct answer is {wrap_in_latex(selected_num)}.
            """.replace("\n", " ").strip()
            
            prob_soln_stmts.append(stmt_type_a)
            pass
        
        stmt = stmt_dict.copy()
        selected_num = max(nums)
        stmt['selected_num'] = selected_num
        stmt['prob_stmt'] = f""" Which one of the followings is the largest number?"""
        stmt['post_soln_stmt'] = f""" Therefore, {wrap_in_latex(selected_num)} is the largest number."""
        prob_soln_stmts.append(stmt)

         
        stmt = stmt_dict.copy()
        selected_num = max(nums)
        stmt['selected_num'] = selected_num
        stmt['prob_stmt'] = f""" Which one of the followings is the largest number?"""
        stmt['post_soln_stmt'] = f""" Therefore, {wrap_in_latex(selected_num)} is the largest number."""
        prob_soln_stmts.append(stmt)

        
        stmt = stmt_dict.copy()
        selected_num = min(nums)
        stmt['selected_num'] = selected_num
        stmt['prob_stmt'] = f""" Which one of the followings is the smallest number?"""
        stmt['post_soln_stmt'] = f""" Therefore, {wrap_in_latex(selected_num)} is the smallest number."""
        prob_soln_stmts.append(stmt)
        
        from num2words import num2words
        stmt = stmt_dict.copy()
        selected_num = random.choice(nums)
        num_name = num2words(selected_num, lang='en')
        
        num_names = [(x, num2words(x, lang='en')) for x in nums]
        num_names_str = [f"{wrap_in_latex(x)}: {y}" for x, y in num_names]
        num_names_str = "\n\n".join(num_names_str)
        
        stmt['selected_num'] = selected_num
        stmt['prob_stmt'] = f""" Which of the following has the number name {num_name}?"""
        stmt['post_soln_stmt'] = f""" 
        The name of each number is:
        {num_names_str}
        """
        prob_soln_stmts.append(stmt)
        
        
        selected_num = random.choice(nums)
        if nums.count(selected_num) == 1:
            list_range = list(range(selected_num - 1, selected_num + 4))
            range_latex = self._get_nums_latex_str(list_range, 
                                                   sep = "\;",
                                                   highlight = "second"
                                                   )
            
            stmt = stmt_dict.copy()
            stmt['selected_num'] = selected_num
            stmt['prob_stmt'] = f""" Which number comes after {wrap_in_latex(selected_num - 1)}?"""
            stmt['post_soln_stmt'] = f""" 
            <br/>
            Count some numbers starting from 
            {wrap_in_latex(selected_num - 1)}. 
            <br/>
            $\\displaystyle \\Large
            {{ {range_latex} }}
            $
            <br/>
            <br/>
            Therefore, {wrap_in_latex(selected_num)} comes after {wrap_in_latex(selected_num - 1)}.
            """.replace("\n", " ").strip()
            prob_soln_stmts.append(stmt)


            list_range = list(range(selected_num + 1, selected_num - 5, -1))
            range_latex = self._get_nums_latex_str(list_range, 
                                                   sep = "\;",
                                                   highlight = "second"
                                                   )

            stmt = stmt_dict.copy()
            stmt['selected_num'] = selected_num
            stmt['prob_stmt'] = f""" Which number comes before {wrap_in_latex(selected_num + 1)}?"""
            stmt['post_soln_stmt'] = f""" 
            <br/>
            Count some number numbers up to 
            {wrap_in_latex(selected_num + 1)}. 
            <br/>
            $\\displaystyle \\Large
            {{ {range_latex} }}
            $
            <br/>
            <br/>
            Therefore, {wrap_in_latex(selected_num)} comes before {wrap_in_latex(selected_num + 1)}.
            """.replace("\n", " ").strip()
            prob_soln_stmts.append(stmt)
            pass
        
        return prob_soln_stmts

    def _get_nums_latex_str(self, nums, sep = "\\;", highlight = "middle"):
        '''
        @highlight: 
            'middle': highlight all number except start and end
            'second': highlight the second number
        '''
        nums_latexes = [latex(x) for x in nums]
        for idx in range(len(nums_latexes)):
            if highlight == "second" and idx == 1:
                nums_latexes[idx] = f"""
                \\color{{ red }}{{ {nums_latexes[idx]} }}
                """.strip()
                continue

            if highlight == "middle" and not (idx == 0 or idx == len(nums_latexes) - 1):
                nums_latexes[idx] = f"""
                \\color{{ red }}{{ {nums_latexes[idx]} }}
                """.strip()
                continue
            pass
        nums_latexes_str = "\\;".join(nums_latexes)
        return nums_latexes_str
    
    def _get_pair_addition_stmts_math_expr(self, nums):
        '''
        '''
        prob_soln_stmts = []
        
        prob_stmt = f"""
        {wrap_in_latex(nums[0])} + {wrap_in_latex(nums[1])} = __________
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        return prob_soln_stmts

        pass


    def get_word_fact_stmts(self, nums):
        fact_stmts = []
        
        fact_stmt = self.get_pair_addition_stmt(nums, stmt_type = "word")
        fact_stmt['soln_stmt'] = f"""$\\displaystyle 
        {latex(nums[0])} + {latex(nums[1])} = {latex(nums[0] + nums[1])}
        $""".strip()
        fact_stmts.append(fact_stmt)
        
        fact_stmt = self.get_pair_subtraction_stmt(nums, stmt_type = 'word')
        fact_stmt['soln_stmt'] = f"""$\\displaystyle 
        {latex(nums[0])} - {latex(nums[1])} = {latex(nums[0] - nums[1])}
        $""".strip()
        fact_stmts.append(fact_stmt)
        
        return fact_stmts

    def get_addition_stmt(self, nums, stmt_type = None):
        if len(nums) == 2:
            return self.get_pair_addition_stmt(nums, stmt_type = stmt_type)
        
        return self._get_addition_stmt_mult_nums(nums, stmt_type = stmt_type)


    def _get_addition_stmt_mult_nums(self, nums, stmt_type = None):
        prob_soln_stmts = []
        stmts = self._get_addition_stmts_mult_nums(nums, stmt_type = stmt_type)
        prob_soln_stmts.extend(stmts)
        
        selected_stmt = random.choice(prob_soln_stmts)
        result = {
            'prob_stmt': selected_stmt.get("prob_stmt").strip(),
            'soln_stmt': selected_stmt.get("soln_stmt", "").strip(),
            }
        return result

    
    def get_pair_addition_stmt(self, nums, stmt_type = None):
        '''
        @stmt_type: None, all, word
        '''
        if len(nums) != 2:
            sError = f"""Warning {self.__class__.__name__}.{inspect.stack()[0][3]}: 
            Invalid input nums
            It must have two items.
            nums = {nums}
            """
            raise Exception(sError)
        
        if stmt_type is None:
            stmt_type = "all"
        
        prob_soln_stmts = []
  
        if stmt_type in ["all"]:
            stmts = self._get_pair_addition_stmts_math_expr(nums)
            prob_soln_stmts.extend(stmts)
            pass

        if stmt_type in ["all", "word"]:
            stmts = self._get_pair_addition_stmts_word(nums)
            prob_soln_stmts.extend(stmts)
            pass

        
        selected_stmt = random.choice(prob_soln_stmts)
        result = {
            'prob_stmt': selected_stmt.get("prob_stmt").strip(),
            'soln_stmt': selected_stmt.get("soln_stmt", "").strip(),
            }
        return result
        pass

    def get_pair_subtraction_stmt(self, nums, stmt_type = None):
        '''
        
        @stmt_type: None, all, non_negative, word
        '''
        if len(nums) != 2:
            sError = f"""Warning {self.__class__.__name__}.{inspect.stack()[0][3]}: 
            Invalid input nums
            It must have two items.
            nums = {nums}
            """
            raise Exception(sError)
        
        if stmt_type is None:
            stmt_type = "all"
        
        prob_soln_stmts = []
  
        if stmt_type in ["all"]:
            stmts = self._get_pair_subtraction_stmts_math_expr(nums)
            prob_soln_stmts.extend(stmts)
            pass

        if stmt_type in ["all", "word"]:
            stmts = self._get_pair_subtraction_stmts_word(nums)
            prob_soln_stmts.extend(stmts)
            pass

        if stmt_type in ["all", "word", "non_negative"]:
            stmts = self._get_pair_subtraction_stmts_non_negative(nums)
            prob_soln_stmts.extend(stmts)
            pass

        
        selected_stmt = random.choice(prob_soln_stmts)
        result = {
            'prob_stmt': selected_stmt.get("prob_stmt").strip(),
            'soln_stmt': selected_stmt.get("soln_stmt", "").strip(),
            }
        return result
        pass

    def _get_addition_stmts_mult_nums(self, nums, stmt_type = None):
        '''
        '''
        prob_soln_stmts = []
        
        nums_latex = [f"{latex(x)}" for x in nums]
        nums_str = list_to_str(nums, last_sep = " and ")
        
        prob_stmt = f"""
        Find sum of {nums_str}.
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        nums_latex_sum = " + ".join(nums_latex)
        
        prob_stmt = f"""
        Find value of $\\displaystyle {nums_latex_sum}$.
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        sections = list('ABCDEFGHIJKLMNOPQR')[:len(nums)]
        sections_nums_latex = [f"{latex(x)}: {latex(nums[idx])}"  
                               for idx, x in enumerate(sections)] 
        
        sections_nums_latex = " \\\\ ".join(sections_nums_latex)
        
        prob_stmt = f"""
        Find the number of students in all sections.
        $\\displaystyle {sections_nums_latex}$.
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        
        
        #Radha's mother bought 10 apples on Monday, 14 apples on Wednesday and 25 apples on Sunday. How many apples did her mother buy altogether?
        goods_names = ["ice creams", "chocolates", 
                                     "books", "pencils", "toys"
                                     "burgers", "pastries", "pizzas"
                                     ]

        goods_name = random.choice(goods_names)
        
        day_names = random.sample(["Monday", "Tuesday", 
                                     "Wednesday", "Thursday", "Friday"
                                     "Saturday", "Sunday"
                                     ], len(nums))
        
        purchase_stmt = [f"{wrap_in_latex(x)} {goods_name} on {y}" 
                         for x, y in zip(nums, day_names)]
        
        purchase_stmt_str = list_to_str(purchase_stmt, last_sep = " and ")

        prob_stmt = f"""
        If you bought {purchase_stmt_str}, how many {goods_name} you have.
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        return prob_soln_stmts
    

    def _get_pair_addition_stmts_word(self, nums):
        '''
        '''
        prob_soln_stmts = []
        
        shape = get_random_html_shape()

        prob_stmt = f"""
        Choose the correct addition for the given figures:
        <br/>
        {int_to_html_shapes(nums, shape = shape, show_nums = False)}
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        

        prob_stmt = f"""
        What is the sum of {wrap_in_latex(nums[0])} and {wrap_in_latex(nums[1])}?
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        prob_stmt = f"""
        The adding machine adds {wrap_in_latex(nums[0])} to any number that is put in it and 
        sends the sum out. 
        What is the sum when {wrap_in_latex(nums[1])} is put into the machine?
        """
        soln_stmt = f"""
        We put {wrap_in_latex(nums[1])}, the machine adds {wrap_in_latex(nums[0])}.
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.replace("\n", " ").strip()
                                })
        
        

        
        grade_name = random.randint(1, 6)
        prob_stmt = f"""
        How many students in all are there in grade ${grade_name}$?
        $\\displaystyle 
        {{{grade_name}A}}: {nums[0]} \\\\ 
        {{{grade_name}B}}: {nums[1]} 
        $
        """
        soln_stmt = f"""
        ${{{grade_name}A}}$ has {wrap_in_latex(nums[0])} 
        and ${{{grade_name}B}}$ has {wrap_in_latex(nums[1])}.
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.replace("\n", " ").strip()
                                })
        
        addl_num = sum(nums) // 2
        while addl_num in nums:
            addl_num = addl_num + 1
            
        revised_nums = [*nums, addl_num] 
        random.shuffle(revised_nums)
        num_dict = dict(zip(list('ABC'), revised_nums))
        
        s_keys = [k for k, v in num_dict.items() if v != addl_num]
        
        num_dict_latex = {f"{k}: {v}"
                          for k, v in num_dict.items()}
        num_dict_latex = " \\\\ ".join(num_dict_latex)
        prob_stmt = f"""
        How many students in all are there in sections ${s_keys[0]}$ and ${s_keys[1]}$?
        $\\displaystyle 
        {num_dict_latex}
        $
        """
        soln_stmt = f"""
        Section ${s_keys[0]}$ has {wrap_in_latex(num_dict[s_keys[0]])} 
        and section ${s_keys[1]}$ has {wrap_in_latex(num_dict[s_keys[1]])} .
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.strip(),
                                'soln_stmt': soln_stmt.replace("\n", " ").strip()
                                })
        
        

        prob_stmt = f"""
        What is the sum of {wrap_in_latex(nums[0])} and {wrap_in_latex(nums[1])}?
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        

        prob_stmt = f"""
        {wrap_in_latex(nums[1])} and {wrap_in_latex(nums[0])} added together is        
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        

        #additional stmts

        goods_name = random.choice(["ice creams", "chocolates", "dollars", "books", "pencils", "toys"])

        prob_stmt = f"""
        If you have {wrap_in_latex(nums[0])} and 
        your friend has {wrap_in_latex(nums[1])} {goods_name}, 
        how many {goods_name} you both have?
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ {goods_name} you both have }}  \\\\ \\\\
        = \\text{{ the {goods_name} you have }} + \\text{{ the {goods_name} your friend has }}  \\\\ \\\\
        = {latex(nums[0])} + {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })



        goods_names = random.sample(["ice creams", "chocolates", "books", "pencils", "toys"], 2)

        prob_stmt = f"""
        If you bought {goods_names[0]} for \$ {wrap_in_latex(nums[0])} 
        and  {goods_names[1]} for \$ {wrap_in_latex(nums[1])}, 
        how many \$ you spent?
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ \$ you spent }}  \\\\ \\\\
        = \\text{{ cost of {goods_names[0]} }} + \\text{{ cost of {goods_names[1]} }}  \\\\ \\\\
        = {latex(nums[0])} + {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })

        return prob_soln_stmts
        pass


    def _get_pair_subtraction_stmts_non_negative(self, nums):
        prob_soln_stmts = []

        goods_name = random.choice(["ice creams", "chocolates", "dollars", "books", "pencils", "toys"])

        prob_stmt = f"""
        If you bought {wrap_in_latex(nums[0])} {goods_name} 
        and later returned {wrap_in_latex(nums[1])}, how many {goods_name} you have now?
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ {goods_name} you have now }}  \\\\ \\\\
        = \\text{{ the number bought }} - \\text{{ the number returned }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })


        prob_stmt = f"""
        If you had {wrap_in_latex(nums[0])} {goods_name} 
        and gave {wrap_in_latex(nums[1])} to your friend, how many {goods_name} are left with you?
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ {goods_name} left with you }} \\\\ \\\\
        = \\text{{ the number you had }} - \\text{{ the number you gave to your friend }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })



        goods_names = random.sample(["ice creams", "chocolates", "books", "pencils", "toys"], 2)

        prob_stmt = f"""
        If you bought {goods_names[0]} and {goods_names[1]} for \$ {wrap_in_latex(nums[0])}.
        Later you returned {goods_names[1]} worth \$ {wrap_in_latex(nums[1])}, 
        how many \$ you spent?
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ Amount spent }} \\\\ \\\\
        = \\text{{ How much originally bought }} - \\text{{ how much was returned }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })

        return prob_soln_stmts

        pass


    def _get_pair_subtraction_stmts_math_expr(self, nums):
        prob_soln_stmts = []
        
        prob_stmt = f"""
        {wrap_in_latex(nums[0])} - {wrap_in_latex(nums[1])} = __________
        """
        soln_stmt = f"""
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        
        return prob_soln_stmts

        pass


    def _get_pair_subtraction_stmts_word(self, nums):
        prob_soln_stmts = []

        prob_stmt = f"""
        The sum of two numbers is {wrap_in_latex(nums[0])}. 
        If one of them is {wrap_in_latex(nums[1])} then the other is
        """
        soln_stmt = f"""
        If we know the sum of two numbers and one of those numbers. 
        Then we can find the unknown number by subtracting the known number from the sum.
        
        $\\displaystyle  
        \\text{{ the unknown number }}  \\\\ \\\\
        = \\text{{ the sum }} - \\text{{ the known number }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        

        prob_stmt = f"""
        {wrap_in_latex(nums[1])} less than {wrap_in_latex(nums[0])} is        
        """
        soln_stmt = f"""
        To know the result of some number less than another number, 
        we have to subtract that some number from the other number. 
        <br/>
        $\\displaystyle 
        \\text{{ result }}  \\\\ \\\\
        = \\text{{ the number }} - \\text{{ how much less we want }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.replace("\n", " ").strip()
                                })
        

        prob_stmt = f"""
        What is the value if {wrap_in_latex(nums[1])} is subtracted from {wrap_in_latex(nums[0])}?
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ result }}  \\\\ \\\\
        = \\text{{ the number }} - \\text{{ what has to be subtracted }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })
        

        prob_stmt = f"""
        From {wrap_in_latex(nums[0])} subtract {wrap_in_latex(nums[1])}.
        """
        soln_stmt = f"""
        $\\displaystyle 
        \\text{{ result }}  \\\\ \\\\
        = \\text{{ the number }} - \\text{{ what has to be subtracted }}  \\\\ \\\\
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })


        prob_stmt = f"""
        By how much {wrap_in_latex(nums[0])} exceeds {wrap_in_latex(nums[1])} ?
        """
        soln_stmt = f"""
        {wrap_in_latex(nums[0])} exceeds {wrap_in_latex(nums[1])} by 
        $\\displaystyle 
        = {latex(nums[0])} - {latex(nums[1])} 
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })


        prob_stmt = f"""
        How much less than {wrap_in_latex(nums[0])} is {wrap_in_latex(nums[1])} ?
        """
        x = Symbol('x')
        
        soln_stmt = f"""
        Let ${latex(x)}$ less than {wrap_in_latex(nums[0])} is {wrap_in_latex(nums[1])}.
        
        $\\displaystyle \\implies
        {latex(nums[0])} - {latex(x)} = {latex(nums[1])}
        $

        $\\displaystyle \\implies
        - {latex(x)} = {latex(nums[1])} - {latex(nums[0])}
        \\qquad \\text{{ Subtract {latex(nums[0])} from both sides. }}
        $

        $\\displaystyle \\implies
        {latex(x)} = {latex(nums[0])} - {latex(nums[1])}
        \\qquad \\text{{ Multiply both sides with -1. }}
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })



        prob_stmt = f"""
        What should be subtracted from {latex(nums[0])} to get {latex(nums[1])}?
        """
        x = Symbol('x')
        
        soln_stmt = f"""
        Let ${latex(x)}$ be subtracted from {latex(nums[0])} to get {latex(nums[1])}.
        
        $\\displaystyle 
        {latex(nums[0])} - {latex(x)} = {latex(nums[1])}
        $

        $\\displaystyle \\implies
        - {latex(x)} = {latex(nums[1])} - {latex(nums[0])}
        \\qquad \\text{{ Subtract {latex(nums[0])} from both sides. }}
        $

        $\\displaystyle \\implies
        {latex(x)} = {latex(nums[0])} - {latex(nums[1])}
        \\qquad \\text{{ Multiply both sides with -1. }}
        $
        """
        prob_soln_stmts.append({'prob_stmt': prob_stmt.replace("\n", " ").strip(),
                                'soln_stmt': soln_stmt.strip()
                                })


        return prob_soln_stmts

        pass
    
    
    def get_problem_mixed_operations(self, **kwargs):
        '''
        '''
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        index = kwargs.get('index', None)
        
        try:
            
            if index is None:
                random_choices = list(range(1, 3))
        
                identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}.random_choices"
                random_choice = self.get_no_repeat_random_choice(identifier, random_choices)
            else:
                random_choice = index
            
            if random_choice == 1:
                return self._sum_of_sqrs_of_sum_and_diff()
    
            if random_choice == 2:
                return self._remainder_of_large_exp_a_num_and_close_num()
    
            return self._sum_of_sqrs_of_sum_and_diff()
        
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            pass
        
        return None
        pass
    

    def _sum_of_sqrs_of_sum_and_diff(self):
        
        op = random.choice(['+', '-'])
        
        num1 = 10 ** random.randint(2, 4)
        num2 = random.randint(1, 10)
        
        nums_sum = num1 + num2
        nums_diff = num1 - num2
        
        nums_sum_symb  = Symbol(f""" \\left( {latex(num1)} + {latex(num2)} \\right) """)
        nums_diff_symb = Symbol(f""" \\left( {latex(num1)} - {latex(num2)} \\right) """)
        
        
        expr_symb = Symbol(f"""
        {latex(nums_sum)} \\times {latex(nums_sum)} 
        {op} 
        {latex(nums_diff)} \\times {latex(nums_diff)} 
        """)
        
        soln_stmts = []
        
        prob_s = f"""
        Solve $\\displaystyle {latex(expr_symb)}$
        """
        
        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        & \\; {latex(expr_symb)}
        \\\\ \\\\
        
        = & \\; {latex(nums_sum)}^2 {op} {latex(nums_diff)}^2
        \\\\ \\\\
        
        = & \\; {latex(nums_sum_symb)}^2 {op} {latex(nums_diff_symb)}^2
        \\\\ \\\\
        
        = & \\; \\left( {latex(num1)}^2 + 2 \\cdot {latex(num1)} \\cdot {latex(num2)} 
        + {latex(num2)}^2 \\right)
        
        {op}
        
        \\left( {latex(num1)}^2 - 2 \\cdot {latex(num1)} \\cdot {latex(num2)} 
        + {latex(num2)}^2 \\right)
        \\qquad \\text{{ applied expansion formula of }} (a + b)^2 \\text{{ and }} (a - b)^2
        \\\\ \\\\
        
        """
        soln_stmts.append(soln_stmt.strip())


        if op == '+':
            num_result = nums_sum**2 + nums_diff**2

            soln_stmt = f"""
            = & \\; 2 \\cdot {latex(num1)}^2 +  2 \\cdot {latex(num2)}^2
            \\qquad \\text{{ cancelled positive and negative terms }}
            \\\\ \\\\
            
            = & \\; 2 \\cdot {latex(num1 ** 2)} +  2 \\cdot {latex(num2 ** 2)}
            \\\\ \\\\
            
            = & \\; {latex(2 * num1 ** 2)} + {latex(2 * num2 ** 2)}
            \\\\ \\\\
            
            """
        else:
            num_result = nums_sum**2 - nums_diff**2

            soln_stmt = f"""
            = & \\; + 2 \\cdot {latex(num1)} \\cdot {latex(num2)}
            - - 2 \\cdot {latex(num1)} \\cdot {latex(num2)}
            \\qquad \\text{{ cancelled positive and negative terms }}
            \\\\ \\\\
            
            = & \\; 4 \\cdot {latex(num1)} \\cdot {latex(num2)}
            \\\\ \\\\
            
            = & \\; 4 \\cdot {latex(num1 * num2)}
            \\\\ \\\\
            
            = & \\; {latex(4 * num1 * num2)}
            \\\\ \\\\
            
            """
            pass

        soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        
        
        soln_stmt = f"""
        = & \\; {latex(num_result)}
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        
        answer_s = f"""
        $\\displaystyle {latex(num_result)}$
        """
        
        soln_s = "\n\n".join(soln_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass
    
    
    def _remainder_of_large_exp_a_num_and_close_num(self):
        
        op = random.choice(['+', '-'])
        
        num_exp = random.randint(10, 1000)
        num = random.randint(20, 1000)
        num_delta = random.choice([1, -1])

        nums_diff = num + num_delta
        
        if num_delta == 1:
            nums_diff_symb = Symbol(f"""\\left( {latex(num)} + {latex(num_delta)} \\right)""")
            num_delta_symb = Symbol(f""" {latex(num_delta)} """)
            pass
        else:
            nums_diff_symb = Symbol(f"""\\left( {latex(num)} - {latex(num_delta)} \\right)""")
            num_delta_symb = Symbol(f""" \\left({latex(num_delta)}\\right) """)
            pass
        
        expr_symb = Symbol(f"""{{ {latex(nums_diff)} }}^{{ {latex(num_exp)} }}""")
        
        soln_stmts = []
        
        prob_s = f"""
        Find the remainder when we divide $\\displaystyle 
        {latex(expr_symb)}  $ with $\\displaystyle 
        {latex(num)}  $.
        """
        
        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        & \\; {latex(expr_symb)}
        \\\\ \\\\
        
        = & \\; {{ {latex(nums_diff_symb)} }}^{{ {latex(num_exp)} }}
        \\\\ \\\\
        
        
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        
        t_exps = [num_exp, num_exp -1, num_exp -2, 2, 1, 0]
        
        terms_ltx = []
        for t_exp in t_exps:
            k = f"\\binom{{{latex(num_exp)}}}{{{latex(num_exp - t_exp)}}}"
            term = f""" 
            {k} 
            \\cdot {{ {latex(num)} }}^{{ {latex(t_exp)} }}  
            \\cdot {{ {latex(num_delta_symb)} }}^{{ {latex(num_exp - t_exp)} }}
            """
            
            if t_exp == 2:
                terms_ltx.append(f" \\cdots ")
                
            terms_ltx.append(term)
            
            pass

        last_term_symb = Symbol(f""" \\left( {terms_ltx[-1]} \\right) """)
        last_term = num_delta ** num_exp
        
        terms_ltx = " + ".join(terms_ltx)
        terms_symb = Symbol(terms_ltx)
        
        num_result = nums_diff ** num_exp % num
        
        soln_stmt = f"""
        
        = & \\; {latex(terms_symb)}
        \\\\ \\\\
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        
        soln_stmt = f"""
        As we see, every term is divisible by $\\displaystyle {latex(num)}  
        $ except the last one. Hence, the remainder will be decided by the last term only. 
        """
        soln_stmts.append(soln_stmt.strip())
        
        
        soln_stmt = f"""
        Therefore, the remainder when we divide $\\displaystyle 
        {latex(expr_symb)}  $ with $\\displaystyle 
        {latex(num)}  $
        <br/>
        <br/>
        
        $\\displaystyle 
        \\begin{{aligned}}
        
        & \\; {latex(expr_symb)} \\; \\% \\; {latex(num)}
        \\\\ \\\\
        
        = & \\; {latex(last_term_symb)} \\; \\% \\; {latex(num)}
        \\\\ \\\\
        
        = & \\; {latex(last_term)} \\; \\% \\; {latex(num)}
        \\\\ \\\\
        
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        
        if last_term < 0:
            soln_stmt = f"""
            \\text{{ as the dividend is less than one, we will add divisor to get remainder. }}
            \\\\ \\\\
            """
            soln_stmts.append(soln_stmt.replace("\n", " ").strip())
            


        soln_stmt = f"""
        = & \\; {latex(num_result)}
        \\\\ \\\\
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())
        
        
        
        answer_s = f"""
        $\\displaystyle {latex(num_result)}  $
        """
        
        soln_s = "\n\n".join(soln_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass
    
    
    pass
#class ArithmeticOperationHelper:
