'''
Created on 07-Feb-2021

@author: bthakur
'''
from functools import reduce
from ..util import MathUtil
import numpy as np
        
class Lcm():
    _CACHED_PRIMES = [2, 3, 5, 7, 11]
    def __init__(self):
        self._nums = None
        self._result = None
        self._steps = None
        pass
    pass

    def _initProb(self, *args):
        self._nums = [x for x in args if isinstance(x, int)]
        self._result = MathUtil.lcm(*self._nums)
        self._steps = []
        pass

    def _lcm(self, *args, **kwargs):
        self._initProb(*args)

        sNum = ", ".join([str(x) for x in self._nums])
        sHint = f"Calculating LCM of {sNum}"
        step = {'hint': sHint }
        self._appendStep(step)

        method_type = kwargs.get('method_type', 1)
        if method_type == 2:
            self._calLcm_method_2()
        else:
            self._calLcm_method_1()
        
        show = kwargs.get('show', True)
        if show:
            self.printResult(self._steps)
            
        return self._result

    def _calLcm_method_2(self):
        #get Prime Numbers
        #Lcm._CACHED_PRIMES
        self._updatePrimes(max(self._nums))

        step = {'hint': '\nFactorize numbers:' }
        self._appendStep(step)
        
        primeFactorsSet = []
        primes = set()
        for n in self._nums:
            primeFactors = self._getPrimeFactors(n)
            primeFactorsSet.append(primeFactors)
            primes.update(set(primeFactors.keys()))

            sNum = " * ".join([f"{k}^{v}" for k, v in primeFactors.items()])
            expr = f"{n} = {sNum}"
            step = {'expr_latex': expr }
            self._appendStep(step)
            

        step = {'hint': '\nTake highest power of each prime factor from all numbers:' }
        self._appendStep(step)

        #get highest power
        lcmFactors = {}
        for p in primes:
            pExp = [ primeFactors.get(p, 0)  for primeFactors in primeFactorsSet ]
            
            spExp = ",".join([str(x) for x in pExp])
            sHint = f"""\nHighest power of {p} = max({spExp}) = {max(pExp)}"""
            step = {'hint':  sHint}
            self._appendStep(step)
            
            lcmFactors[p] = max(pExp)
            pass

        sNum = " * ".join([f"{k}^{v}" for k, v in lcmFactors.items()])
        expr = f"{sNum} = {self._result}"

        sNum = ", ".join([str(x) for x in self._nums])
        sHint = f"\nLCM of {sNum}"
        step = {'expr_latex': expr , 'hint': sHint}
        self._appendStep(step)
        
        pass

    def _getPrimeFactors(self, n):
        primeFactors = {}
        tempN = n
        isComposite = True
        while isComposite and tempN != 1:
            isComposite = False
            for p in Lcm._CACHED_PRIMES:
                if tempN % p == 0:
                    primeFactors[p] = primeFactors.get(p, 0) + 1
                    tempN = tempN //p
                    isComposite = True
                    break
                pass
            pass
        if not primeFactors or tempN != 1:
            primeFactors[tempN] = primeFactors.get(tempN, 0) + 1
            
        return primeFactors
        pass
    
    def _updatePrimes(self, n):
        sqrt_n = int(n ** 0.5) 
        maxPrime = max(Lcm._CACHED_PRIMES)
        if maxPrime >= sqrt_n:
            return
        
        tempN = maxPrime
        while tempN <= sqrt_n:
            tempN = tempN + 1
            
            #check prime
            isPrime = self.isPrime(tempN)
            if isPrime:
                Lcm._CACHED_PRIMES.append(tempN)
                #print(Lcm._CACHED_PRIMES)
            pass
        
        pass
    
    def isPrime(self, n):
        for p in Lcm._CACHED_PRIMES:
            if n % p == 0:
                return False
        return True
        pass
    
    def _calLcm_method_1(self):
        n_multiples = [(n, np.arange(1, 1  + self._result // n) * n)  for n in self._nums]

        for n, m in  n_multiples:
            sHint = f"\nMultiples of {n} = {m}"
            step = {'hint': sHint }
            self._appendStep(step)
            pass

        sHint = f"\nThe lowest common multiple (LCM) of all numbers {self._result}"
        step = {'hint': sHint }
        self._appendStep(step)
        
        pass
    
    def _appendStep(self, data):
        self._steps.append(data)
        pass

        
    def _multiply(self, *nums):
        return reduce(lambda x, y: x * y, nums, 1)
        pass


    def printResult(self, result):
        try:
            from IPython.display import display, Math
        except:
            display = print()
            Math = self._math
        
        for row in result:
            if not isinstance(row, dict):
                display(Math(
                    row
                ))
                continue
            
                
            hint = row.get('hint', None)
            if not hint is None:
                print(hint)
        
            expr_latex = row.get('expr_latex', None)
            if not expr_latex is None:
                display(Math(expr_latex))
        
            expr = row.get('expr', None)
            if not expr is None:
                display(Math(str(expr)))
        
             
    def _math(self, x):
        return x
        pass
  
