'''
Created on 10-Feb-2021

@author: bthakur
'''
import matplotlib.pyplot as plt
import numpy as np
from sympy import Rational, latex
#import sympy
#import math
import random
from sympy.core.sympify import sympify

from ...util import MathUtil


from xv.km.util import ( 
                     latex_fraction, 
                     wrap_latex_statement)
        
from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate, KPrintElement
        
# _rational_number_line_manager 
# RationalNumberLineManager

class RationalNumberLineManager(BaseManager):
    '''
    Rational and Fractional Numbers


    Minimum Grade: 3
    Maximum Grade: 8


    Examples
    --------
        ke = RationalNumberLineManager()
        ke
        
        ke.printProblemTypes()
        
        ke.getRandomProblem()
        ke.getRandomProblem(problem_type = 0)
        ...
        
        ke.printProblem()
        ke.printAnswer()
        ke.printSolution()
    
        
        ke.sum()
        ke.average()
        ke.sort()
        ke.median()
        ke.plot()

        
        ke.getNotebookTemplates()
        ke.generateNotebook(templateName = 'SOME_TEMPLATE_NAME', saveToFolder = 'output')        


    doc_style: xv_doc
    ''' 

    _DEFAULT_FONT_SIZE = 15

    def __new__(cls, *args, **kwargs):
        instance = super(RationalNumberLineManager, cls).__new__(cls, *args, **kwargs)
        for base_class in RationalNumberLineManager.__bases__:
            base_class.__init__(instance)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.rationalNumbers = None
        self.setContent()
        pass
    
    @classmethod
    def _getHtmlDisplay(cls, *args):
        try:
            from IPython.display import HTML as htmlDisplay
            return htmlDisplay
        except:
            return None
            pass

    @classmethod
    def _getMathDisplay(cls, *args):
        try:
            from IPython.display import Math as mathDisplay
            return mathDisplay
        except:
            return None
            pass



    def setContent(self, *args, **kwargs):
        self._content = f"""
        Rational Numbers
        """.strip()
        pass

    def getRandomProblem(self, *numbers, **kwargs):
        self._init_data(*numbers, **kwargs)
        success = self._processProblemTemplate(**kwargs)
        if not success:
            print(f"Failed to get random problem")
            return 
        show = kwargs.get('show', True)
        if show:
            return self.printProblem()
        return self._problem
        pass

    def _init_data(self, *numbers, **kwargs):
        size = kwargs.get('size', random.randint(2, 6))
        minV = kwargs.get('minV', 1)
        maxV = kwargs.get('maxV', 9)
        
        if len(numbers) < 1:
            self.rationalNumbers = self._initRandomNumbers(size, minV, maxV)
        else:
            self.rationalNumbers = self._convertToRationals(*numbers)
        pass


    def initProblem(self, expr = None, statement = None, **kwargs):
        raise Exception("Not Implemented")
        pass
    
    @property
    def help(self):
        s = "TODO"
        return s
        pass

    def _convertToRationals(self, *numbers):
        return [Rational(n[0], n[1]) for n in numbers]
        pass
    
    
    def _initRandomNumbers(self, size, minV, maxV):
        rationalNumbers = []
        while len(rationalNumbers) < size:
            nSign = 1 if random.random() < 0.5 else -1
            n = np.random.randint(low = minV,
                            high = maxV,
                            size = (2) 
                           ).astype('int')  
            r = Rational(n[0] * nSign, n[1])
            rationalNumbers.append(r)
            pass 
        return rationalNumbers
        pass

    


    def _rationalsToStr(self, rationalNumbers, 
                        multiplier = 1,
                        simplify = True,
                        prepend = "= ",
                        operator = "+"
                        ):
        sOut = ""
        for idx, r in enumerate(rationalNumbers):
            if hasattr(r, "p") and hasattr(r, "q"):
                r_p = r.p
                r_q = r.q
            else:
                r_p = r[0]
                r_q = r[1]
                r = Rational(r_p, r_q)
                
            if multiplier == 1:
                rf = latex_fraction(abs(r_p), abs(r_q))
            else:
                if simplify:
                    rf = latex_fraction(abs(r_p * multiplier[idx]), 
                                        abs(r_q * multiplier[idx]))
                else:
                    rf = latex_fraction(f"{abs(r_p)} * {multiplier[idx]}", 
                                        f"{abs(r_q)} * {multiplier[idx]}")
                    
            if operator == "+":
                if r < 0:
                    sOut = sOut + " - " + rf
                else:  
                    sOut = sOut + " + " + rf
            else:
                if r < 0:
                    sOut = sOut + f" {operator} -" + rf
                else:  
                    sOut = sOut + f" {operator} " + rf

        sOut = sOut.strip()
        if sOut[0] == operator:
            sOut = sOut[1:]
            
        return wrap_latex_statement(prepend + sOut)
        pass

    def _get_denoms_lcm(self, rationalNumbers):
        denoms = [r.q for r in rationalNumbers]
        return MathUtil.lcm(*denoms)

    def _getCommonDenomFactors(self, rationalNumbers):
        denoms_lcm = self._get_denoms_lcm(rationalNumbers)
        return [denoms_lcm // r.q for r in rationalNumbers]
        pass
    
    def _initAverage(self):
        nums = [r[0] for r in self.modifiedNumbers]
        q = self.commonDenominator
        sumNumbers = sum(nums)
        avg = (sumNumbers, q * len(nums))
        avg_common_den = (sumNumbers / len(nums), q)
        
        avg_index = None
        n = int(avg_common_den[0])
        if avg_common_den[0] == n:
            if n in nums:
                avg_index = nums.index(n)
            pass
        
        return avg, avg_common_den, avg_index

    def _initMedian(self):
        nums = [r[0] for r in self.modifiedNumbers]
        q = self.commonDenominator
        med_numerator = np.median(np.array(nums))
        
        median_common_den = (med_numerator, q)
        median = Rational(med_numerator, q)
        median = (int(median.p), int(median.q))

        median_index = None
        n = int(median_common_den[0])
        if median_common_den[0] == n:
            if n in nums:
                median_index = nums.index(n)
            pass
        
        return median, median_common_den, median_index
    
    def _initLineNumbers(self, pointCount = 10):
        nums = [r[0] for r in self.modifiedNumbers]
        low = min(nums)
        high = max(nums)

        interval = self._getInterval(high-low, pointCount)
        
        low = (low // interval - 1) * interval
        high = (high // interval + 1) * interval
        
        linspacePoints = np.arange(low - interval, 
                                   high +  interval + 1, 
                                   interval)
        return linspacePoints
        pass
    
    
    def _getInterval(self, numRange, pointCount = 10):
        a1 = 10 ** np.arange(6)
        a2 = a1 * 2
        a5 = a1 * 5
        intervalArray =  np.concatenate((a1, a2, a5))
        interval = numRange // pointCount + 1
        offsetArray =  abs(intervalArray - interval)
        
        indOfMinOffset = np.unravel_index(np.argmin(
            offsetArray, axis=None), offsetArray.shape)[0]
        interval = intervalArray[indOfMinOffset]
        return interval
        pass
    
    
    
    def _init_modified_numbers(self):
        modifiedNumbers = []
        exprs = []
        exprs_latex = ""
        q = self.commonDenominator
        for i, r in enumerate(self.rationalNumbers):
            mFactor = int(q / r.q) 
            
            p = int(mFactor * r.p) 
            rTemp = (p, q)
            modifiedNumbers.append(rTemp)
        
            expr = f"({r.p}*{mFactor})/({r.q}*{mFactor})"
            
            fTemp = latex_fraction(f"{r.p}*{mFactor}", f"{r.q}*{mFactor}" )
            
            if p/q < 0:
                exprs_latex = exprs_latex + " - " + fTemp
            else:
                exprs_latex = exprs_latex + " + " + fTemp
                
            expr = sympify(expr, evaluate = False)
            exprs.append(expr)
            
        exprs_latex = wrap_latex_statement(exprs_latex)
        return modifiedNumbers, exprs, exprs_latex
        pass
    
    
    def plot(self):
        self.numerators = [int(r.p) for r in self.rationalNumbers]
        self.denominators = [int(r.q) for r in self.rationalNumbers]
        self.commonDenominator = MathUtil.lcm(*self.denominators)
        self.modifiedNumbers, self.exprs, self.exprs_latex = self._init_modified_numbers()
        self.modifiedNumbers_sum = [sum([r[0] for r in self.modifiedNumbers]), self.commonDenominator]
        self.avg, self.avg_common_den, self.avg_index = self._initAverage()
        self.med, self.med_common_den, self.med_index = self._initMedian()
        self.lineNumbers = self._initLineNumbers()
        
        xytext = (0, 0)
        fig = plt.figure(figsize= (6, 10))
        ax = plt.axes()  
        plt.xlim(-2, 2)
        lns = self.lineNumbers

        plt.plot((0, 0), (lns[0], lns[-1]))
        for i in lns:
            plt.plot(0, i, marker = 'o', markersize = '5', color = "grey")

            label = f"{i}/{self.commonDenominator}"
            plt.annotate(label, 
                        xy=(-.1, i), 
                        ha='right',
                        rotation = 0,
                        fontsize= int(RationalNumberLineManager._DEFAULT_FONT_SIZE * .75),
                        )

        mns = self.modifiedNumbers
        rns = self.rationalNumbers
        for idx, n in enumerate(mns):
            point, = plt.plot(0, n[0], marker = 'o', markersize = '10')
            label = f"{rns[idx]} = {mns[idx][0]}/{mns[idx][1]}"
            
            if idx == self.avg_index:
                label = f"{label} (Avg)"
                pass
            
            if idx == self.med_index:
                label = f"{label} (Med)"
                pass
            
            plt.annotate(label, 
                        xy=(+.2, n[0]),  
                        ha='left',
                        rotation = 0,
                        fontsize= RationalNumberLineManager._DEFAULT_FONT_SIZE,
                         color = point.get_color()
                        )


        if self.avg_index is None:
            self._plotAvg()
        
        if self.med_index is None:
            self._plotMedian()
        self._showAxesAndGrid(plt.gca())
        plt.show()
        pass

    def _plotAvg(self):
        #self.avg, self.avg_common_den, self.avg_index
        #avgNumber
        plt.plot(-0.2, self.avg_common_den[0], marker = '*', markersize = '10', 
                color = 'red')

        label = f"Avg={self.avg[0]}/{self.avg[1]}"
        
        if self.avg[0] != self.avg_common_den[0]:
            label = f"{label}\n= {self.avg_common_den[0]:.2f}/{self.avg_common_den[1]}"

        plt.annotate(label, 
                    xy=(-.7, self.avg_common_den[0]),  
                    ha='right',
                    rotation = 0,
                        fontsize= int(RationalNumberLineManager._DEFAULT_FONT_SIZE * .8),
                     color = 'red'
                    )
        
        pass
    

    def _plotMedian(self):
        #self.med, self.med_common_den, self.med_index 
        
        plt.plot(+0.1, self.med_common_den[0], marker = 's', markersize = '8', 
                color = 'blue')

        label = f"Med={self.med[0]}/{self.med[1]}"
        
        if self.med[0] != self.med_common_den[0]:
            label = f"{label}\n= {self.med_common_den[0]:.2f}/{self.med_common_den[1]}"

        plt.annotate(label, 
                    xy=(2.0, self.med_common_den[0]),  
                    ha='left',
                    rotation = 0,
                        fontsize= int(RationalNumberLineManager._DEFAULT_FONT_SIZE * .8),
                     color = 'blue'
                    )
        
        pass
    
    def _showAxesAndGrid(self, ax, showFlag = False):
        ax.xaxis.set_visible(showFlag)
        ax.yaxis.set_visible(showFlag)
        ax.set_frame_on(showFlag)
        ax.grid(showFlag) 
        
    
    def _printSolutionByProblemType(self, problem_type_name):
        problem_type = self._get_problem_type_by_name(problem_type_name)
        success = self._processProblemTemplate(problem_type = problem_type)
        if not success:
            print(f"Error while getting sum")
            return 
        return self.printSolution()
        pass
    
    def sum(self):
        problem_type_name = '_problem_add_rational_numbers'
        return self._printSolutionByProblemType(problem_type_name)
        pass

    def average(self):
        problem_type_name = '_problem_average_of_rational_numbers'
        return self._printSolutionByProblemType(problem_type_name)
        pass

    def sort(self):
        problem_type_name = '_problem_sort_rational_numbers'
        return self._printSolutionByProblemType(problem_type_name)
        pass

    def median(self):
        problem_type_name = '_problem_median_of_rational_numbers'
        return self._printSolutionByProblemType(problem_type_name)
        pass

    
    def _areDenomsCommon(self, rationalNumbers):
        denoms = set([r.q for r in rationalNumbers])
        if len(denoms) == 1:
            return True
        return False
        pass

    def _solution_common_denom(self, *args, **kwargs):
        solution = []
        rationalNumbers = self.rationalNumbers
        
        if not self._areDenomsCommon(rationalNumbers):
            denoms_lcm = self._get_denoms_lcm(rationalNumbers)
            solution.append(KPrintElement(
                f"""Let us make all denominators equal to their LCM = {denoms_lcm}
                """, 'text/html'))
            
            denoms_factors = self._getCommonDenomFactors(rationalNumbers)
            expr = self._rationalsToStr(rationalNumbers, 
                            multiplier = denoms_factors,
                            simplify = False,
                            operator= ",")
             
            solution.append(KPrintElement(expr, 'latex/html'))
            
            expr = self._rationalsToStr(rationalNumbers, 
                            multiplier = denoms_factors,
                            simplify = True,
                            operator= ",")
             
            solution.append(KPrintElement(expr, 'latex/html'))
            pass
        
        
        return solution
        pass
    

    
    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_convert_denominator',
            '_problem_convert_to_similar_rational_numbers',
            '_problem_add_rational_numbers',
            '_problem_average_of_rational_numbers',
            '_problem_sort_rational_numbers',
            '_problem_median_of_rational_numbers',
         ])
        pass

    def _problem_convert_denominator(self, *args, **kwargs):
        """
        Convert to similar rational numbers


        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_convert_denominator')


        doc_style: xv_doc
        """

        
        p = random.randint(0, 9)
        q = random.randint(1, 9)
        ratio = random.randint(2, 9)
        n = Rational(p, q)
        
        n_latex = latex_fraction(p, q)
        n_similar_expanded_latex = latex_fraction(f"{p} * {ratio}", f"{q} * {ratio}")
        n_similar_latex = latex_fraction(p * ratio, q * ratio)
        factor_latex = latex_fraction(q * ratio, q)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Write a number equal to ${n_latex}$ but with denominator ${latex(q * ratio)}$ 
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        We have denominator ${latex(q)}$
        The desired denominator is ${latex(q * ratio)}$. 
        
        We know, ${factor_latex} = {latex(ratio)}$
        
        So, we can multiply both numerator and denominator of ${n_latex}$ with ${latex(ratio)}$
        
        ${n_latex}
        
        = {n_similar_expanded_latex}
        
        = {n_similar_latex}$
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        ${n_similar_latex}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
        
    def _problem_convert_to_similar_rational_numbers(self, *args, **kwargs):
        """
        Convert to similar rational numbers


        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_convert_to_similar_rational_numbers')


        doc_style: xv_doc
        """

        
        p1 = random.randint(0, 9)
        q1 = random.randint(1, 9)
        n1 = Rational(p1, q1)
        n1_latex = latex_fraction(p1, q1)
        
        p2 = random.randint(0, 9)
        q2 = random.randint(1, 9)
        n2 = Rational(p2, q2)
        n2_latex = latex_fraction(p2, q2)
        
        n1_sim_expanded = latex_fraction(f"{p1} * {q2}", f"{q1} * {q2}")
        n2_sim_expanded = latex_fraction(f"{p2} * {q1}", f"{q2} * {q1}")

        n1_sim = latex_fraction(f"{p1 * q2}", f"{q1 * q2}")
        n2_sim = latex_fraction(f"{p2 * q1}", f"{q2 * q1}")

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Make the numbers ${n1_latex}$ and ${n2_latex}$ similar.
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        We have denominator ${latex(q1)}$ and ${latex(q2)}$
        
        We will use a simpler approach is to make denominator equal to the product of both denominators
        ${latex(q1)} * {latex(q2)} =  {latex(q1 * q2)}$ 
        
        Therefore, multiply numerator and denominator of the first number with denominator of the second number, ${latex(q2)}$
        
        ${n1_latex} = {n1_sim_expanded} = {n1_sim}$ 
        
        Similarly, multiply numerator and denominator of the second number with denominator of the first number, ${latex(q1)}$
        
        ${n2_latex} = {n2_sim_expanded} = {n2_sim}$ 
    
        Thus, the similar form of numbers are 
        
        ${n1_sim} , {n2_sim}$
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        ${n1_sim} , {n2_sim}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    

    def _problem_convert_to_common_denoms(self, *args, **kwargs):
        """
        Transform rational numbers into numbers with common denominators



        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_convert_to_common_denoms')


        doc_style: xv_doc
        """

        
        statement = "Convert the numbers to  numbers with common denominator:"
        
        expr = self._rationalsToStr(self.rationalNumbers, 
                                    operator= ",",
                                    prepend = "")

        solution = []
        solution.append(KPrintElement("Fractions:", 'text/html'))
        solution.append(KPrintElement(expr, 'latex/html'))
        solution.extend(self._solution_common_denom())

        answer = solution[-1]
        
        problemTemplate = KProblemTemplate(
                problem_type = '_problem_convert_to_common_denoms',
                problem = [statement, expr],
                answer = answer,
                solution = solution
                )

        return problemTemplate
        pass

    def _problem_add_rational_numbers(self, *args, **kwargs):
        """
        Compute sum of rational numbers



        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_add_rational_numbers')


        doc_style: xv_doc
        """

        
        statement = "Find sum of the following:"
        
        expr = self._rationalsToStr(self.rationalNumbers,
                                    prepend = "")
        answer = sum(self.rationalNumbers)
        
        solution = []
        solution.append(KPrintElement("Sum of numbers:", 'text/html'))
        solution.append(KPrintElement(expr, 'latex/html'))
        solution.extend(self._solution_sum())
        
        
        problemTemplate = KProblemTemplate(
                problem_type = '_problem_add_rational_numbers',
                problem = [statement, expr],
                answer = answer,
                solution = solution
                )

        
        return problemTemplate
        pass
    
    
    def _solution_sum(self, *args, **kwargs):
        solution = []
        rationalNumbers = self.rationalNumbers
        
        denoms_lcm = self._get_denoms_lcm(rationalNumbers)
        denoms_factors = self._getCommonDenomFactors(rationalNumbers)
        
        if not self._areDenomsCommon(rationalNumbers):
            solution.append(KPrintElement(
                f"""Let us make all denominators equal to their LCM = {denoms_lcm}
                """, 'text/html'))
            
            expr = self._rationalsToStr(rationalNumbers, 
                            multiplier = denoms_factors,
                            simplify = False)
             
            solution.append(KPrintElement(expr, 'latex/html'))
            
            expr = self._rationalsToStr(rationalNumbers, 
                            multiplier = denoms_factors,
                            simplify = True)
             
            solution.append(KPrintElement(expr, 'latex/html'))
            pass

        solution.append(KPrintElement(
            f"""As we have common denominator for all fractions, we can add all numerators.""", 'text/html'))
        
        numer_sum = sum([r.p * denoms_factors[idx] 
                         for idx, r in enumerate(rationalNumbers)])

        expr = wrap_latex_statement(
                " = " + 
                latex_fraction(numer_sum, denoms_lcm)
              )
        solution.append(KPrintElement(expr, 'latex/html'))
        
        expr = wrap_latex_statement(
                " = " + 
                str(sum(self.rationalNumbers))
              )
        solution.append(KPrintElement(expr, 'latex/html'))
        
        
        return solution
        pass


    def _problem_average_of_rational_numbers(self, *args, **kwargs):
        """
        Compute average of rational numbers



        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_average_of_rational_numbers')


        doc_style: xv_doc
        """

        
        statement = "Find average of the following:"
        
        expr = self._rationalsToStr(self.rationalNumbers, 
                                    operator= ",",
                                    prepend = "")
        
        solution = []
        solution.append(KPrintElement("Sum of fractions:", 'text/html'))
        solution.append(KPrintElement(expr, 'latex/html'))
        solution.extend(self._solution_sum())
        
        solution_average, answer = self._solution_average()
        solution.extend(solution_average)
        
        problemTemplate = KProblemTemplate(
                problem_type = '_problem_average_of_rational_numbers',
                problem = [statement, expr],
                answer = answer,
                solution = solution
                )
        
        return problemTemplate
        pass
        

    def _solution_average(self, *args, **kwargs):
        solution = []
        
        sum_nums = sum(self.rationalNumbers)
        len_nums = len(self.rationalNumbers)
        
        
        sHint = f"The number of fractions is {len_nums}. So, we will divide the sum with {len_nums} to get average:"
        solution.append(KPrintElement(sHint, 'text/html'))
        
        expr = self._rationalsToStr([sum_nums, Rational(1, len_nums)], 
                        multiplier = 1,
                        simplify = False,
                        operator= "*")
        
        solution.append(KPrintElement(expr, 'latex/html'))
        
        avg = sum_nums * Rational(1, len_nums)
        expr = wrap_latex_statement(
                "= " + 
                str(avg) 
              )
        solution.append(KPrintElement(expr, 'latex/html'))
        return solution, avg
        pass
    
        

    def _problem_sort_rational_numbers(self, *args, **kwargs):
        """
        Sort rational numbers in ascending order



        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_sort_rational_numbers')


        doc_style: xv_doc
        """

        
        statement = "Sort the following numbers in ascending order:"
        
        expr = self._rationalsToStr(self.rationalNumbers, 
                                    operator= ",",
                                    prepend = "")
        
        rationalNumbers = sorted(self.rationalNumbers)
        answer = self._rationalsToStr(rationalNumbers, 
                                      operator= ",")

        solution = []
        solution.append(KPrintElement("Fractions in given order:", 'text/html'))
        solution.append(KPrintElement(expr, 'latex/html'))
        solution.extend(self._solution_sort())
        
        
        problemTemplate = KProblemTemplate(
                problem_type = '_problem_sort_rational_numbers',
                problem = [statement, expr],
                answer = answer,
                solution = solution
                )

        return problemTemplate
        pass
        
    def _solution_sort(self, *args, **kwargs):
        solution = []
        rationalNumbers = self.rationalNumbers
        
        denoms_lcm = self._get_denoms_lcm(rationalNumbers)
        denoms_factors = self._getCommonDenomFactors(rationalNumbers)

        if not self._areDenomsCommon(rationalNumbers):
            solution.append(KPrintElement(
                f"""Let us make all denominators equal to their LCM = {denoms_lcm}
                """, 'text/html'))
            
            expr = self._rationalsToStr(rationalNumbers, 
                            multiplier = denoms_factors,
                            simplify = False,
                            operator= ",")
             
            solution.append(KPrintElement(expr, 'latex/html'))
            
            expr = self._rationalsToStr(rationalNumbers, 
                            multiplier = denoms_factors,
                            simplify = True,
                            operator= ",")
             
            solution.append(KPrintElement(expr, 'latex/html'))
            
            pass

        solution.append(KPrintElement(
            f"""As we have common denominator for all fractions, we can sort all numerators.""", 'text/html'))
        
        numers = [r.p * denoms_factors[idx] 
                         for idx, r in enumerate(rationalNumbers)]
        numers_sorted = sorted(numers)

        frct_sorted = [ (numer, denoms_lcm)
                        for idx, numer in enumerate(numers_sorted)
                      ]
        
        expr = self._rationalsToStr(frct_sorted, 
                        operator= ",")

        solution.append(KPrintElement(expr, 'latex/html'))

        solution.append(KPrintElement(
            f"""Write back original numbers in the sorted order:""", 'text/html'))
        
        rationalNumbers = sorted(self.rationalNumbers)
        expr = self._rationalsToStr(rationalNumbers, operator= ",")
        solution.append(KPrintElement(expr, 'latex/html'))
        
        return solution
        pass
    


    
    def _problem_median_of_rational_numbers(self, *args, **kwargs):
        """
        Find median of rational numbers



        Minimum Grade: 3
        Maximum Grade: 6


    
        Examples
        --------
        from xv.math.algebra import RationalNumberLineManager
        ke = RationalNumberLineManager()


        ke.getRandomProblem(problem_type = '_problem_median_of_rational_numbers')


        doc_style: xv_doc
        """

        
        statement = "Find the median of the numbers:"
        
        expr = self._rationalsToStr(self.rationalNumbers, 
                                    operator= ",",
                                    prepend = "")
        
        
        solution = []
        solution.append(KPrintElement("Fractions in given order:", 'text/html'))
        solution.append(KPrintElement(expr, 'latex/html'))
        solution.extend(self._solution_sort())
        
        med_soln, answer = self._solution_median()
        solution.extend(med_soln)
        
        problemTemplate = KProblemTemplate(
                problem_type = '_problem_median_of_rational_numbers',
                problem = [statement, expr],
                answer = answer,
                solution = solution
                )

        return problemTemplate
        pass
        
    def _solution_median(self, *args, **kwargs):
        solution = []
        sorted_rationalNumbers = sorted(self.rationalNumbers)
        arr_len = len(sorted_rationalNumbers)
        
        if arr_len % 2 != 0:
            midx = (arr_len - 1) // 2 
            sHint = f"The number of fractions is {arr_len}, an odd number."
            sHint = sHint + "\n" + f"Hence, the median will be the middle term, ({arr_len}+1)/2 = {midx + 1}th term."
            solution.append(KPrintElement(sHint, 'text/html'))

            median = sorted_rationalNumbers[midx]
            solution.append(KPrintElement(
                f"Median = {median}", 'text/html'))

            return solution, median 

        midxs = [arr_len // 2 - 1, arr_len // 2]
        sHint = f"The number of fractions is {arr_len}, an even number."
        sHint = sHint + "\n" + f"The middle term is, ({arr_len}+1)/2 = {(arr_len + 1)/2}th term."
        sHint = sHint + "\n" + f"Hence, the median will be average of {midxs[0] + 1}th and {midxs[1] + 1}th terms."
        solution.append(KPrintElement(sHint, 'text/html'))
        
        sHint = f"Sum of {midxs[0] + 1}th and {midxs[1] + 1}th terms"
        solution.append(KPrintElement(sHint, 'text/html'))
        
        medianNumbers = [sorted_rationalNumbers[idx] for idx in midxs]
        expr = self._rationalsToStr(medianNumbers, 
                        multiplier = 1,
                        simplify = False)
        solution.append(KPrintElement(expr, 'latex/html'))
                
        denoms_factors = self._getCommonDenomFactors(medianNumbers)
        expr = self._rationalsToStr(medianNumbers, 
                        multiplier = denoms_factors,
                        simplify = False)
         
        solution.append(KPrintElement(expr, 'latex/html'))
        
        expr = self._rationalsToStr(medianNumbers, 
                        multiplier = denoms_factors,
                        simplify = True)
         
        solution.append(KPrintElement(expr, 'latex/html'))
        
        
       
        numer_sum = sum([r.p * denoms_factors[idx] 
                         for idx, r in enumerate(medianNumbers)])

        denoms_lcm = self._get_denoms_lcm(medianNumbers)
        expr = wrap_latex_statement(
                " = " + 
                latex_fraction(numer_sum, denoms_lcm)
              )
        solution.append(KPrintElement(expr, 'latex/html'))
        
        expr = wrap_latex_statement(
                "= " + 
                str(sum(medianNumbers))
              )
        solution.append(KPrintElement(expr, 'latex/html'))
        
        sHint = f"Therefore, median, the {midxs[0] + 1}th and {midxs[1] + 1}th terms is"
        solution.append(KPrintElement(sHint, 'text/html'))
        
        expr = self._rationalsToStr([sum(medianNumbers), Rational(1, 2)], 
                        multiplier = 1,
                        simplify = False,
                        operator= "*")
        solution.append(KPrintElement(expr, 'latex/html'))
        
        median = sum(medianNumbers)/2
        expr = wrap_latex_statement(
                "Median = " + 
                str(median) 
              )
        solution.append(KPrintElement(expr, 'latex/html'))
        
        
        return solution, median
        pass
    
    
    pass
#class RationalNumberLineManager
   
