'''
Created on 20-Jan-2022

@author: bthakur
'''

import inspect
import traceback
import random
import numpy as np

from xv.util.htmlutil import (
            get_option_and_answer_statements,
                              )

from sympy import (Mul, Add, latex, symbols, Rational,
                   )
import sympy as sp

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

from xv.util import UtilClass
from mpmath import rand

# _rational_operation_helper
# RationalOperationHelper

class RationalOperationHelper(UtilClass):

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

    def _problem_template_arrange_in_order(self, *args, **kwargs):
        '''
        '''

        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_rational_numbers,  rational_similar_data
        ##end:include_source##

        nums = get_rational_numbers(level = 0,
                                    count = random.randint(3, 5),
                                    numr_min_max = None,
                                    denm_min_max = None,
                                    numr_exclude_range = None,
                                    denm_exclude_range = None,
                                    distinct = True,
                                    allow_integers = False)

        similar_nums = rational_similar_data(nums)

        nums_mod_sorted = list(sorted(similar_nums['nums_mod']))

        nums_mod_sorted_str = [str(x[0]) for x in nums_mod_sorted]
        nums_mod_sorted_str = ", ".join(nums_mod_sorted_str)

        nums_sorted = list(sorted(nums))

        nums_sorted_items = [f"$\\displaystyle {latex(x)}$" for x in nums_sorted]
        nums_sorted_str = ", ".join(nums_sorted_items)

        correct_option = nums_sorted_str

        all_options = [
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ", ".join(random.sample(nums_sorted_items, len(nums_sorted_items))),
            ]

        incorrect_options = [x for x in all_options if x != correct_option]
        incorrect_options = list(set(incorrect_options))

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        nums_latex_str = ", ".join(similar_nums['nums_latex'])

        prob_s = f"""
        Arrange {nums_latex_str} in ascending order:

        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        {similar_nums['soln_s']}

        As denominators are same, we can ignore them while arranging:
        {nums_mod_sorted_str}

        Thus the sorted numbers are:
        {nums_sorted_str}


        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass



    def _problem_template_addition(self, *args, **kwargs):
        '''
        '''


        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_rational_numbers,  rational_sums_data
        ##end:include_source##

        level = 0
        nums = get_rational_numbers(level = 0,
                                    count = 2,
                                    numr_min_max = None,
                                    denm_min_max = None,
                                    numr_exclude_range = None,
                                    denm_exclude_range = None,
                                    distinct = True,
                                    allow_integers = False)

        correct_option = sum(nums)

        all_options = [*nums, correct_option + nums[0], correct_option + nums[-1],
                       correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2]

        incorrect_options = [x for x in all_options if x != correct_option]
        incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = wrap_in_latex(correct_option)
        options = [wrap_in_latex(x) for x in options]

        result = rational_sums_data(nums)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        What is sum of {join_with_comma(result['nums_latex'])}?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass


    def _problem_template_subtraction(self, *args, **kwargs):
        '''
        '''

        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_rational_numbers,  rational_diff_data
        ##end:include_source##
        def diff(nums):
            if len(nums) != 2:
                raise Exception(f"Error: Exactly two numbers should be provided.\nnums = {nums}")
            return nums[0] - nums[1]
            pass

        level = 0
        nums = get_rational_numbers(level = 0,
                                    count = 2,
                                    numr_min_max = None,
                                    denm_min_max = None,
                                    numr_exclude_range = None,
                                    denm_exclude_range = None,
                                    distinct = True,
                                    allow_integers = False)

        nums = list(sorted(nums, reverse = True))

        correct_option = diff(nums)

        all_options = [*nums, correct_option + nums[0], correct_option + nums[-1],
                       correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2]

        incorrect_options = [x for x in all_options if x != correct_option]
        incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = wrap_in_latex(correct_option)
        options = [wrap_in_latex(x) for x in options]

        result = rational_diff_data(nums)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        from ._arithmetic_operation_helper import ArithmeticOperationHelper
        ao_helper = ArithmeticOperationHelper(verbose = self.verbose)
        selected_stmt = ao_helper.get_pair_subtraction_stmt(nums)

        prob_s = f"""
        {selected_stmt['prob_stmt']}
        {option_s}
        """

        soln_s = f"""
        {selected_stmt['soln_stmt']}

        {result['soln_s']}
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_multiplication(self, *args, **kwargs):
        '''
        '''
        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_rational_numbers,  rational_prod_data
        ##end:include_source##


        level = 0
        nums = get_rational_numbers(level = 0,
                                    count = 2,
                                    numr_min_max = None,
                                    denm_min_max = None,
                                    numr_exclude_range = None,
                                    denm_exclude_range = None,
                                    distinct = True,
                                    allow_integers = False)

        nums = list(sorted(nums, reverse = True))

        correct_option = Mul(*nums)

        all_options = [*nums, correct_option + nums[0], correct_option + nums[-1],
                       correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2]

        incorrect_options = [x for x in all_options if x != correct_option]
        incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = wrap_in_latex(correct_option)
        options = [wrap_in_latex(x) for x in options]

        result = rational_prod_data(nums)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Multiply {join_with_comma(result['nums_latex'])}.
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_division(self, *args, **kwargs):
        '''
        '''
        import math
        from sympy import Mul
        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_rational_numbers,  rational_prod_data, rational_division_data
        ##end:include_source##


        level = 0
        nums = get_rational_numbers(level = 0,
                                    count = 2,
                                    numr_min_max = None,
                                    denm_min_max = None,
                                    numr_exclude_range = None,
                                    denm_exclude_range = None,
                                    distinct = True,
                                    allow_integers = False)

        nums_mul = Mul(*nums)

        mul_options = [nums_mul + nums[0], nums_mul + nums[-1],
                       nums_mul - 2, nums_mul - 1, nums_mul + 1, nums_mul + 2]

        correct_option = nums[0] / nums[1]

        all_options = [*nums, *mul_options,  correct_option + nums[0], correct_option + nums[-1],
                       correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2]

        incorrect_options = [x for x in all_options if x != correct_option and x > 0]
        incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = wrap_in_latex(correct_option)
        options = [wrap_in_latex(x) for x in options]

        result = rational_division_data(nums)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Divide {join_with_comma(result['nums_latex'], last_sep = " by ")}.
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        We have to do:

        {result['soln_s']}

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_additional_logical(self, *args, **kwargs):
        '''
        '''
        from sympy import latex

        ##start:include_source##
        from xv.util.htmlutil import join_with_comma, get_rational_numbers,  rational_sums_data, get_logical_inequality_data, rational_similar_data
        ##end:include_source##

        level = 0

        while True:
            nums_set1 = get_rational_numbers(level = 0,
                                        count = 2,
                                        numr_min_max = None,
                                        denm_min_max = None,
                                        numr_exclude_range = None,
                                        denm_exclude_range = None,
                                        distinct = True,
                                        allow_integers = False)
            if nums_set1[0] == nums_set1[1]:
                continue

            nums_set2 = get_rational_numbers(level = 0,
                                        count = 2,
                                        numr_min_max = None,
                                        denm_min_max = None,
                                        numr_exclude_range = None,
                                        denm_exclude_range = None,
                                        distinct = True,
                                        allow_integers = False)

            nums_rev = list(reversed(nums_set1))
            nums_set2 = random.choice([nums_rev, nums_set2])
            if (nums_set1[0] == nums_set2[0] and
                nums_set1[1] == nums_set2[1]):
                continue

            if abs(sum(nums_set1) - sum(nums_set2)) < min(sum(nums_set1), sum(nums_set2))  :
                break
            pass

        logical_result = get_logical_inequality_data(sum(nums_set1), sum(nums_set2))

        correct_option = logical_result['correct_option']
        options = logical_result['options']

        result1 = rational_sums_data(nums_set1)
        result2 = rational_sums_data(nums_set2)

        sums_nums = [sum(nums_set1), sum(nums_set2)]
        result_sums = rational_similar_data(sums_nums)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        {result1['nums_latex_sum']} ___________ {result2['nums_latex_sum']}
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        <h3>Left hand side:</h3>
        {result1['soln_s']}
        <h3>Right hand side:</h3>
        {result2['soln_s']}

        <h3>To LHS and RHS, we have make the denominators of both sides similar.</h3>
        {result_sums['soln_s']}

        $\\displaystyle
        \\implies {result_sums['nums_similar_latex_items'][0]}
        $ {correct_option} $\\displaystyle
         {result_sums['nums_similar_latex_items'][1]}
        $.

        Therefore, {result1['nums_latex_sum']} $\\displaystyle
        = {result_sums['nums_similar_latex_items'][0]}
        $ {correct_option} {result2['nums_latex_sum']} $\\displaystyle
        = {result_sums['nums_similar_latex_items'][1]}
        $.

        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_expr(self, *args, **kwargs):
        '''
        '''

        ##start:include_source##
        from xv.util.htmlutil import get_rational_numbers, rational_expr_to_latex, rational_sums_data
        from xv.util.htmlutil import get_random_rational_bodmas_expr_str, solve_rational_expr_str
        ##end:include_source##

        level = 0
        min_num = 10
        max_num = 99

        while True:
            count = random.randint(3, 5)
            nums = get_rational_numbers(level = 0,
                                        count = count,
                                        numr_min_max = None,
                                        denm_min_max = (2, 6),
                                        numr_exclude_range = None,
                                        denm_exclude_range = None,
                                        distinct = True,
                                        allow_integers = False)

            expr_str = get_random_rational_bodmas_expr_str(nums)
            latex_expr_str = rational_expr_to_latex(expr_str)
            result = solve_rational_expr_str(expr_str)

            expr_value = result['expr_value']

            if result['error'] > 0.10:
                continue

            correct_option = expr_value

            all_options = [*nums,
                           correct_option + nums[0], correct_option + nums[-1],
                           correct_option - nums[0], correct_option - nums[-1],
                           correct_option - 2, correct_option - 1, correct_option + 1, correct_option + 2]

            incorrect_options = [x for x in list(set(all_options)) if x != correct_option]
            incorrect_options = sorted(incorrect_options, key = lambda x: abs(x - correct_option))

            if len(incorrect_options) >= 3:
                break
            pass

        if len(incorrect_options) > 5:
            incorrect_options = incorrect_options[:5]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        options = [f"$\\displaystyle {sp.latex(x)}$" for x in options]
        correct_option = f"$\\displaystyle {sp.latex(correct_option)}$"

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Solve $\\displaystyle {latex_expr_str}$
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_s = f"""
        {result['soln_s']}
        """

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass



    def get_all_fraction_types(self):
        fraction_types = ['simple',
                        'common',
                        'vulgar',
                        'unit',
                        'dyadic',
                        'proper',
                        'improper',
                        ]
        return fraction_types

    def get_fraction_types(self, p, q):
        common_types = ['simple', 'common', 'vulgar']
        fraction_types = []
        fraction_types.extend(common_types)

        if abs(p) == 1:
            fraction_types.append("unit")

        if isinstance(sp.log(q, 2), sp.Integer):
            fraction_types.append("dyadic")

        num = Rational(p, q)
        if abs(num) < 1:
            fraction_types.append("proper")

        if abs(num) >= 1:
            fraction_types.append("improper")

        return fraction_types
        pass



    def _problem_template_fraction_types(self, *args, **kwargs):
        '''
        '''
        methods = [self._problem_template_compute_equivalent_fraction,
                   self._problem_template_correct_equivalent_fractions,
                   self._problem_template_incorrect_equivalent_fractions,
                   self._problem_template_odd_fraction_type,
                   self._problem_template_incorrect_fraction_types,
                   ]
        method = random.choice(methods[:1])
        return method(*args, **kwargs)
        pass

    def _problem_template_compute_equivalent_fraction(self, *args, **kwargs):
        '''
        '''

        correct_nums = set()

        q = random.choice([random.randint(1, 10), 2 ** random.randint(1, 10)])
        p = random.choice([random.randint(1, 10), 1, -1, q + 1])
        p = p * random.choice([1, -1])
        num = Rational(p, q)

        correct_nums.add((num.p, num.q))

        for multiplier in random.sample(list(range(2, 10)), 3):
            correct_nums.add((num.p * multiplier, num.q * multiplier))

        selected_num, other_num = random.sample(correct_nums, 2)


        missing_var = symbols('x')

        missing_type = random.choice(["numr", "denr"])
        if missing_type == "numr":
            correct_option = selected_num[0]
            selected_num_mod = (missing_var, selected_num[1])
        else:
            correct_option = selected_num[1]
            selected_num_mod = (selected_num[0], missing_var)
            pass

        incorrect_options = [correct_option + 1,
                             correct_option + 2,
                             correct_option - 1,
                             correct_option - 2,
                             correct_option * 1,
                             correct_option * 3,
                             ]
        incorrect_options = [x for x in incorrect_options if x != correct_option]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)


        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_num = f"$\\displaystyle \\frac {{ {selected_num_mod[0]} }}{{ {selected_num_mod[1]} }}$"
        other_num_latex = f"$\\displaystyle \\frac {{ {other_num[0]} }}{{ {other_num[1]} }}$"


        e_fracions_str = []
        p0, q0 = other_num #showing
        low_rno = Rational(p0, q0)

        for i in range(1, 10):
            p = low_rno.p * i
            q = low_rno.q * i
            e_fracion = f"""
            $\\displaystyle
            \\frac{{ {low_rno.p} \\cdot {i} }}{{ {low_rno.q} \\cdot {i} }}
            = \\frac{{ {p} }}{{ {q} }}
            $
            """
            e_fracions_str.append(e_fracion.strip())
            if abs(p) == abs(selected_num[0]):
                break
            pass

        e_fracions_str = "\n\n".join(e_fracions_str)


        prob_s = f"""
        If  {prob_num} and {other_num_latex} are equivalent fracions,
        what is value of ${missing_var}$?
        <br/>
        <br/>
        {option_s}
        """
        prob_s = prob_s.replace("\n", " ").strip()

        soln_stmts = []

        soln_stmt = f"""
        In an equivalent fraction, the ratio of numerator and denominator are same.
        <br/>
        We can get equivalent fractions of a fraction by multiplying both
        numerator and denominator by the same integer.
        <br/>
        We can also get equivalent fractions of a fraction by diving both
        numerator and denominator by a common divisor of the both.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        if other_num[0] != low_rno.p:
            if abs(other_num[0]) >  abs(low_rno.p):
                #divide
                temp_divisor = abs(other_num[0]) //  abs(low_rno.p)
                temp_latex = f"""
                \\frac{{ {other_num[0]} / {temp_divisor}}}
                {{ {other_num[1]} / {temp_divisor}}}
                """
            else:
                #multiply
                temp_multiplier = abs(low_rno.p) // abs(other_num[0])
                temp_latex = f"""
                \\frac{{ {other_num[0]} \\cdot {temp_multiplier}}}
                {{ {other_num[1]} \\cdot {temp_multiplier}}}
                """


            soln_stmt = f"""
            Let us get the lowest absolute numerators fraction first.
            <br/>
            <br/>
            $\\displaystyle
            \\frac{{ {other_num[0]}}}{{ {other_num[1]}}}
            = {temp_latex}
            = \\frac{{ {low_rno.p}}}{{ {low_rno.q}}}
            $
            """
            soln_stmts.append(soln_stmt.replace("\n", " ").strip())



        soln_stmt = f"""
        The following numbers are equivalent fractions of $\\displaystyle
        \\frac{{ {other_num[0]}}}{{ {other_num[1]}}}
        = \\frac{{ {low_rno.p}}}{{ {low_rno.q}}}
        $:

        {e_fracions_str}

        As we see, $\\displaystyle
        \\frac{{ {selected_num[0]}}}{{ {selected_num[1]}}}
        $ is one of equivalent fractions of {other_num_latex}.

        Therefore,

        {prob_num} $\\displaystyle
        = \\frac{{ {selected_num[0]}}}{{ {selected_num[1]}}}
        $

        $\\displaystyle \\implies
        {missing_var} = {correct_option}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        soln_s = "\n\n".join(soln_stmts)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_correct_equivalent_fractions(self, *args, **kwargs):
        '''
        '''

        correct_nums = set()
        incorrect_nums = set()

        q = random.choice([random.randint(1, 10), 2 ** random.randint(1, 10)])
        p = random.choice([random.randint(1, 10), 1, -1, q + 1])
        p = p * random.choice([1, -1])
        num = Rational(p, q)

        correct_nums.add((num.p, num.q))

        for multiplier in random.sample(list(range(2, 10)), 3):
            correct_nums.add((num.p * multiplier, num.q * multiplier))

        for num_delta in random.sample(list(range(-9, 10)), 19):
            if len(incorrect_nums) >= 4:
                break

            if num_delta == 0:
                continue

            if num.q + num_delta == 0:
                continue

            if Rational(num.p + num_delta, num.q + num_delta) == num:
                continue

            incorrect_nums.add((num.p + num_delta, num.q + num_delta))
            pass


        correct_option = random.choice(list(correct_nums))
        other_correct_nums = [x for x in correct_nums if x != correct_option]

        incorrect_options = [x for x in incorrect_nums if x != correct_option]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)


        correct_option = f"$\\displaystyle \\frac {{ {correct_option[0]} }}{{ {correct_option[1]} }}$"

        options = [f"$\\displaystyle \\frac {{ {x[0]} }}{{ {x[1]} }}$"
                   for x in options]

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_num = random.choice(other_correct_nums)
        prob_num = f"$\\displaystyle \\frac {{ {prob_num[0]} }}{{ {prob_num[1]} }}$"

        prob_s = f"""
        Which of these numbers is an equivalent fraction of {prob_num}?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_stmts = []

        soln_stmt = f"""
        In an equivalent fraction, the ratio of numerator and denominators are same.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_s = "\n\n".join(soln_stmts)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_incorrect_equivalent_fractions(self, *args, **kwargs):
        '''
        '''

        correct_nums = set()
        incorrect_nums = set()

        q = random.choice([random.randint(1, 10), 2 ** random.randint(1, 10)])
        p = random.choice([random.randint(1, 10), 1, -1, q + 1])
        p = p * random.choice([1, -1])
        num = Rational(p, q)

        #correct_nums.add((num.p, num.q))

        for multiplier in random.sample(list(range(2, 10)), 4):
            correct_nums.add((num.p * multiplier, num.q * multiplier))

        for num_delta in random.sample(list(range(-9, 10)), 19):
            if len(incorrect_nums) >= 4:
                break

            if num_delta == 0:
                continue

            if num.q + num_delta == 0:
                continue

            if Rational(num.p + num_delta, num.q + num_delta) == num:
                continue

            incorrect_nums.add((num.p + num_delta, num.q + num_delta))
            pass

        correct_option = random.choice(list(incorrect_nums))

        incorrect_options = [x for x in correct_nums if x != correct_option]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        correct_option = f"$\\displaystyle \\frac {{ {correct_option[0]} }}{{ {correct_option[1]} }}$"

        options = [f"$\\displaystyle \\frac {{ {x[0]} }}{{ {x[1]} }}$"
                   for x in options]

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)


        prob_s = f"""
        Which of these numbers is not an equivalent fraction of $\\displaystyle
        {latex(num)}
        $?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_stmts = []

        soln_stmt = f"""
        In an equivalent fraction, the ratio of numerator and denominators are same.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_s = "\n\n".join(soln_stmts)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_odd_fraction_type(self, *args, **kwargs):
        '''
        '''
        odd_types = [   'unit',
                        'dyadic',
                        'proper',
                        'improper',
                        ]
        selected_type = random.choice(odd_types)

        nums = set()
        incorrect_num = None
        while len(nums) < 4 or incorrect_num is None:
            q = random.choice([random.randint(1, 10), 2 ** random.randint(1, 10)])
            p = random.choice([random.randint(1, 10), 1, -1, q + 1])
            p = p * random.choice([1, -1])
            num = Rational(p, q)
            fraction_types = self.get_fraction_types(num.p, num.q)

            if selected_type in fraction_types:
                nums.add(num)
                continue

            if not incorrect_num is None:
                continue
            incorrect_num = num
            pass

        correct_option = incorrect_num

        incorrect_options = [x for x in list(nums) if x != correct_option]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Which of these numbers is not a type of {selected_type} fraction?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_stmts = []

        a,b = symbols("a, b")

        soln_stmt = f"""
        A simple fraction (also known as a common fraction or vulgar fraction,
        where vulgar is Latin for "common") is a rational number written as
        $\\displaystyle \\frac{{ {latex(a)} }}{{ {latex(b)} }} $,
        where a and b are both integers.
        <br/>
        <br/>
        A unit fraction is a common fraction if numerator is 1.
        <br/>
        <br/>
        A dyadic fraction is a common fraction if the denominator is a number with a power of 2.
        <br/>
        <br/>
        A fraction is a proper fraction,
        if the absolute value of the fraction is strictly less than 1.

        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_s = "\n\n".join(soln_stmts)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    def _problem_template_incorrect_fraction_types(self, *args, **kwargs):
        '''
        '''

        while True:
            p = random.randint(1, 10) * random.choice([1, -1])
            q = random.randint(1, 10)
            num = Rational(p, q)

            fraction_types = self.get_fraction_types(p, q)
            all_types = self.get_all_fraction_types()

            incorrect_types = list(set(all_types) - set(fraction_types))
            if not incorrect_types:
                continue
            break
            pass

        correct_option = random.choice(incorrect_types)

        all_options = fraction_types

        incorrect_options = [x for x in list(set(all_options)) if x != correct_option]

        options = random.sample(incorrect_options, 3)
        options.append(correct_option)

        option_s, answer_s = get_option_and_answer_statements(options, correct_option)

        prob_s = f"""
        Which of these is not correct about $\\displaystyle {latex(num)}$?
        {option_s}
        """
        prob_s = prob_s.strip()

        soln_stmts = []

        a,b = symbols("a, b")

        soln_stmt = f"""
        A simple fraction (also known as a common fraction or vulgar fraction,
        where vulgar is Latin for "common") is a rational number written as
        $\\displaystyle \\frac{{ {latex(a)} }}{{ {latex(b)} }} $,
        where a and b are both integers.
        <br/>
        <br/>
        A unit fraction is a common fraction if numerator is 1.
        <br/>
        <br/>
        A dyadic fraction is a common fraction if the denominator is a number with a power of 2.
        <br/>
        <br/>
        A fraction is a proper fraction,
        if the absolute value of the fraction is strictly less than 1.

        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_s = "\n\n".join(soln_stmts)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        problemTemplate.add_problem(prob_s.strip(), "text/html")
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        return problemTemplate
        pass

    pass
#class RationalOperationHelper:
