'''
Created on 02-Mar-2021

@author: bthakur
'''
'''
Created on 07-Feb-2021

@author: bthakur
'''
from functools import reduce
from ..util import MathUtil

import numpy as np
import random
import inspect
import traceback


from sympy import Mul, Symbol

        
from xv.km.util import latex_fraction
from xv.km.kelements import KPrintElement
        
from ..helper import BaseHelper
from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate
from xv.util import list_to_str
from xv.km.util import KmTemplateUtil

        
class LcmManager(BaseManager):
    '''
    LCM / Lowest common multiples


    Minimum Grade: 3
    Maximum Grade: 8


    Examples
    --------
    ke = LcmManager()
    ke 
    
    ke.printProblemTypes()
    ke.helpProblemType(0)
    
    ke.getRandomProblem()
    ke.getRandomProblem(problem_type = 0)

    ke.initProblem(10, 24)
    ke.initProblem(15, 25, 30)


    ke.printAnswer()
    ke.printSolution()
    

    doc_style: xv_doc
    ''' 


    
    _CACHED_PRIMES = [2, 3, 5, 7, 11]
    def __new__(cls, *args, **kwargs):
        instance = super(LcmManager, cls).__new__(cls, *args, **kwargs)
        for base_class in LcmManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        pass


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_integer_multiples',
            '_problem_lcm_by_integer_multiples',
            '_problem_prime_number',
            '_problem_prime_divisors',
            '_problem_prime_factors',
            '_problem_lcm_by_prime_factors',
            '_problem_hcf_by_prime_factors',
            '_problem_lcm_by_common_divisors',
            '_problem_hcf_by_common_divisors',
            '_problem_lcm_hcf_pair_relations',
            '_problem_lcm_prime_number_abstract_algebra',
         ])
        pass

    def _problem_integer_multiples(self, *args, **kwargs):
     
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()
        n = random.randint(2, 20)
        size = random.choice([3, 5, 10])
        
        result = lcm_hcf_helper.get_random_multiples_data(n, size)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Write first {size} multiples {n}.
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        
        soln_s = f"""
        First {size} multiples {n}:
        
        $\\displaystyle = {result['n_table_str']}$
        $\\displaystyle = {result['n_multiples_str']}$
            
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {result['n_multiples_str']}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
    

    def _problem_lcm_by_integer_multiples(self, *args, **kwargs):
        """
        lcm by integer multiples


        doc_style: xv_doc
        """
        
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()
        result = lcm_hcf_helper.get_lcm_solution(rule = 'integer_multiples',
                                             size = 2)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        
        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_prime_number(self, *args, **kwargs):
        """
        @generate_notebook: Default True

        doc_style: xv_doc
        """

        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: 
        creating notebook and getting problem from PrimeNumberManager
        """
        self._log(sInfo.strip())
        
        
        #create notebook
        generate_notebook = kwargs.get('generate_notebook', True)
        
        if generate_notebook:
            saveToFolder = f"./"
            KmTemplateUtil.CREATE_MANAGER_NOTEBOOK("xv.math.basicmaths", 
                                                   "PrimeNumberManager", 
                                                   saveToFolder = saveToFolder,
                                                   overwrite = False, 
                                                   verbose = self.verbose)

        #get a problem        
        from xv.math.basicmaths import PrimeNumberManager
        ke = PrimeNumberManager(verbose = self.verbose)
        problem_types = [
            '_problem_divisibility',
            '_problem_list_prime_numbers',
            '_problem_is_prime',
            '_problem_factorize',
            '_problem_prime_factorize',
            '_problem_prime_coprime',
        ]
        
        problem_type = random.choice(problem_types)
        
        kwargs['problem_type'] = problem_type
        kwargs['generate_notebook'] = False
        problemTemplate = ke.getProblemTemplate(*args, **kwargs)
        
        return problemTemplate

    def _problem_prime_divisors(self, *args, **kwargs):
        """
        prime divisors


        Minimum Grade: 3
        Maximum Grade: 8

    
        Examples
        --------
        from xv.math.basicmaths import LcmManager
        ke = LcmManager()


        ke.getRandomProblem(problem_type = '_problem_prime_divisors')


        doc_style: xv_doc
        """
        
        pnhelper = self.base_helper.get_arithmetic_helper().get_prime_number_helper()
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        nums = lcm_hcf_helper.get_random_numbers(size = 3)
        n = random.choice(nums)

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        
        prime_numbers = pnhelper.getPossibleFactors(n * 2)
        
        stmts = []
        is_prime_number = True
        for prime_number in prime_numbers:
            if prime_number ** 2 > n :
                stmt = f"""
                As {prime_number} * {prime_number} = {prime_number ** 2} > {n}, we will stop checking further.
                """
                stmts.append(stmt.strip())
                break
            else:
                stmt = f"""
                As {prime_number} * {prime_number} = {prime_number ** 2} <= {n}, we will continue checking divisibility by {prime_number}.
                """
                stmts.append(stmt.strip())
                
            
            if n % prime_number == 0:
                is_prime_number = False
                stmt = f"""{n} / {prime_number} = {n // prime_number}. 
                Hence, {prime_number} is a factor of {n}."""
            else:
                stmt = f"""Remainder of {n} / {prime_number} = {n % prime_number}. 
                Hence, {prime_number} is a not factor of {n}."""
                pass
        
            stmts.append(stmt.strip())
            pass
        
        if is_prime_number:
            stmt = f"""As we did not find any divisor for {n} other than itself, it is a prime number."""
            stmts.append(stmt.strip())
            
        stmts = "\n\n".join(stmts)
        
        primeFactors = pnhelper.getPrimeFactors(n)
        primeKeys_str = ", ".join([str(x) for x in primeFactors])

        problem_s = f"""
        Find prime numbers that divide {n}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        Note: Every prime number is its own prime divisor.
        
        Prime numbers that divide {n}
        
        Let us try first few prime numbers:
        {list_to_str(prime_numbers, empty_list_as = None)}
        
        Note: We will try till the square of prime number is less than {n}.
        
        {stmts}
        
        Therefore, prime numbers that divide {n} are:
        {primeKeys_str}
        
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {primeKeys_str}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    

    def _problem_prime_factors(self, *args, **kwargs):
        """
        prime factors


        Minimum Grade: 3
        Maximum Grade: 8

    
        Examples
        --------
        from xv.math.basicmaths import LcmManager
        ke = LcmManager()


        ke.getRandomProblem(problem_type = '_problem_prime_factors')


        doc_style: xv_doc
        """

        pnhelper = self.base_helper.get_arithmetic_helper().get_prime_number_helper()
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        while True:
            nums = lcm_hcf_helper.get_random_numbers(size = 3)
            n1 = random.choice(nums)
            pnhelper = self.base_helper.get_arithmetic_helper().get_prime_number_helper()
            result = pnhelper.init_prime_factor_data(n1 = n1)
            if result:
                n1, sqrt_n, possibleFactors, nFactors, nfactorSets = result
                break
            pass
        
        primeFactors = pnhelper.getPrimeFactors_as_str(n1)
        
        primeFactors_latex = str(primeFactors).replace("**", "^")
        
        factor_12_latex = str("2**2 * 3*1").replace("**", "^")

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

        problem_s = f"""
        Express {n1} as product of prime numbers.
        Example:
        Factors pairs of 12 = $\\displaystyle {factor_12_latex}$
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')
        
        soln_s = f"""
        {n1} is less than {sqrt_n**2}, square of {sqrt_n}.
        If {n1} is divisible by a number higher than {sqrt_n}, the quotient of that division must be lower than {sqrt_n}..
        Therefore, in any factor set, one element must be a prime number lower than {sqrt_n}..

        Prime numbers lower than {sqrt_n} are {list_to_str(possibleFactors, empty_list_as = None)}.
        Among them, {n1} is divisible by {list_to_str(nFactors, empty_list_as = None)}.
        
        Therefore, factor pairs of {n1} are:
        $\\displaystyle {primeFactors_latex}$
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        $\\displaystyle {primeFactors_latex}$
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
        pass

    def _problem_lcm_by_prime_factors(self, *args, **kwargs):
        """
        lcm of pair by prime factors

        doc_style: xv_doc
        """
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        nums = kwargs.get('numbers', None)

        if nums is None:
            size = random.choice([2, 3])
            nums = lcm_hcf_helper.get_random_lcm_numbers(size = size)
        
        result = lcm_hcf_helper.get_lcm_solution(*nums, 
                                             rule = 'prime_factors')
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        
        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

        pass
        
    def _problem_hcf_by_prime_factors(self, *args, **kwargs):
        """
        hcf of pair by prime factors

        doc_style: xv_doc
        """
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        nums = kwargs.get('numbers', None)

        if nums is None:
            size = random.choice([2, 3])
            nums = lcm_hcf_helper.get_random_hcf_numbers(size = size)
        
        result = lcm_hcf_helper.get_hcf_solution(*nums, 
                                             rule = 'prime_divisors')
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        
        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

        pass

    def _problem_lcm_by_common_divisors(self, *args, **kwargs):
        """
        lcm by common divisors


        Minimum Grade: 3
        Maximum Grade: 8

        doc_style: xv_doc
        """
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        nums = kwargs.get('numbers', None)

        if nums is None:
            size = random.choice([2, 3])
            nums = lcm_hcf_helper.get_random_lcm_numbers(size = size)
        
        result = lcm_hcf_helper.get_lcm_solution(*nums, 
                                             rule = 'common_divisors')
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        
        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass
        
    def _problem_hcf_by_common_divisors(self, *args, **kwargs):
        """
        hcf by common divisors


        Minimum Grade: 3
        Maximum Grade: 8

        doc_style: xv_doc
        """
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        nums = kwargs.get('numbers', None)

        if nums is None:
            size = random.choice([2, 3])
            nums = lcm_hcf_helper.get_random_hcf_numbers(size = size)
        
        result = lcm_hcf_helper.get_hcf_solution(*nums, 
                                             rule = 'common_divisors')
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        
        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_lcm_hcf_pair_relations(self, *args, **kwargs):
        """
        _problem_lcm_hcf_pair_relations


        Minimum Grade: 3
        Maximum Grade: 8

        doc_style: xv_doc
        """
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()

        nums = kwargs.get('numbers', None)

        if nums is None:
            size = 2
            nums = lcm_hcf_helper.get_random_hcf_numbers(size = size)
 
        rule = random.choice(['prime_factors',
                              'common_divisors',
                            ])       
        result = lcm_hcf_helper.get_lcm_solution(*nums, rule = rule)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        problem_s = f"""
        Find LCM and HCF of {result['nums_str']}.
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        nums = result['nums']
        LCM = result['answer']
        n1, n2 = nums
        HCF = Symbol('HCF')
        
        hcf_fraction = latex_fraction(f"{n1} * {n2}", f"{LCM}")
        hcf_computed = int((n1 * n2) / LCM)
        
        soln_s = f"""
        {result['soln_s']}
        
        The product of two numbers is equal to the product of their LCM and HCF.
        Therefore, 
        
        $\\displaystyle {n1} * {n2} = {LCM} * {HCF} $
        
        $\\displaystyle \\implies {HCF} = {hcf_fraction}   $
        
        $\\displaystyle \\implies {HCF} = {hcf_computed}   $
        
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        LCM = {LCM}
        HCF = {hcf_computed}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass



    def _problem_lcm_prime_number_abstract_algebra(self, *args, **kwargs):
        lcm_hcf_helper = self.base_helper.get_arithmetic_helper().get_lcm_hcf_helper()
        
        result = lcm_hcf_helper.get_problem_lcm_prime_number_abstract_algebra()

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

        prob_s = f"""
        {result['prob_s']}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
                

    def _sample(self, *args, **kwargs):
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        prob_s = f"""
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        
        soln_s = f"""
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
    
                