'''
Created on 16-Apr-2021

@author: bthakur

https://faculty.math.illinois.edu/~hildebr/347.summer19/nt1.pdf

'''
import os 
import numpy as np
import random
import inspect
import traceback


from sympy import ( symbols, factor)

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 toIntOrFloat

# DivisibilityManager
# _divisibility_manager
        
class DivisibilityManager(BaseManager):
    '''
    Divisibility test by some common integers


    Minimum Grade: 3
    Maximum Grade: 12


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


    ke.printProblem()
    ke.printAnswer()
    ke.printSolution()


    doc_style: xv_doc
    ''' 


    def __new__(cls, *args, **kwargs):
        instance = super(DivisibilityManager, cls).__new__(cls, *args, **kwargs)
        for base_class in DivisibilityManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        self._colors = ['indigo', 'blue', 'green', 'red', 'cyan', 'magenta', 'violet', 'black', 'orange']  
        self._markers = ['o', 'v', '^', '<', '>', '1', '2', '3', '4', 
                         '8', 's', 'p', 'P', '*', 'h', 'H', 'X', 'D', 'd', ]
        
        self._precision = 6
        pass


    def sum_digits(self, n, sum_type = 'all'):
        if sum_type == 'even':
            n = [x[1] for x in enumerate(list(str(n)))
                 if (x[0] + 1) % 2 == 0]
            n = int("".join(n))

        if sum_type == 'odd':
            n = [x[1] for x in enumerate(list(str(n)))
                 if (x[0] + 1) % 2 != 0]
            n = int("".join(n))

        r_str = " + ".join([x for x in list(str(n))] )  
        
        r = 0
        while n:
            r, n = r + n % 10, n // 10
            
        return r, r_str
    
    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
                '_problem_traditional_division',
                '_problem_divisible_by_multiples_of_10',
                '_problem_divisible_by_2_5',
                '_problem_divisible_by_3_9',
                '_problem_divisible_by_11',
                '_problem_divisible_by_powers_of_2_5',
                '_problem_divisible_by_6',
                '_problem_divisible_by_7_13_17_19_29',
             ])
        pass

    def _problem_traditional_division(self, *args, **kwargs):
        """
        Traditional division


        Minimum Grade: 3
        Maximum Grade: 10

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


        ke.getRandomProblem(problem_type = '_problem_traditional_division')


        doc_style: xv_doc
        """
        
        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_traditional_division(**kwargs)
        
        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
        pass

    def _problem_divisible_by_multiples_of_10(self, *args, **kwargs):
        """
        Test divisibility by multiples of 10 


        Minimum Grade: 3
        Maximum Grade: 6

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_multiples_of_10')


        doc_style: xv_doc
        """

        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_divisible_by_multiples_of_10(**kwargs)
        
        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
        
        pass

    def _problem_divisible_by_2_5(self, *args, **kwargs):
        """
        Test divisibility by 2 or 5


        Minimum Grade: 3
        Maximum Grade: 6

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_2_5')


        doc_style: xv_doc
        """

        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_divisible_by_2_5(**kwargs)
        
        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
        pass


    def _problem_divisible_by_3_9(self, *args, **kwargs):
        """
        Test divisibility by 3 or 9


        Minimum Grade: 3
        Maximum Grade: 6

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_3_9')


        doc_style: xv_doc
        """
        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_divisible_by_3_9(**kwargs)
        
        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

        
        pass

    def _problem_divisible_by_11(self, *args, **kwargs):
        """
        Test divisibility by 11


        Minimum Grade: 3
        Maximum Grade: 6

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_11')


        doc_style: xv_doc
        """

        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_divisible_by_11(**kwargs)
        
        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

        
        pass

    def _problem_divisible_by_powers_of_2_5(self, *args, **kwargs):
        """
        Test divisibility by 4 or 8


        Minimum Grade: 3
        Maximum Grade: 6

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_powers_of_2_5')


        doc_style: xv_doc
        """

        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_divisible_by_powers_of_2_5(**kwargs)
        
        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

        pass

    def _problem_divisible_by_6(self, *args, **kwargs):
        """
        Test divisibility by 6


        Minimum Grade: 3
        Maximum Grade: 6

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_6')


        doc_style: xv_doc
        """

        div_helper = self.base_helper.get_arithmetic_helper().get_divisibility_helper()
        
        result = div_helper.get_problem_divisible_by_6(**kwargs)
        
        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
        pass

    def _problem_divisible_by_7_13_17_19_29(self, *args, **kwargs):
        """
        Test divisibility by 7, 13, 17, 19 or 29


        Minimum Grade: 6
        Maximum Grade: 12

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


        ke.getRandomProblem(problem_type = '_problem_divisible_by_7_13_17_19_29')


        doc_style: xv_doc
        """

        
        pq = [(7, -2),
              (13, 4),
              (17, -5),
              (19, 2),
              (29, 3),
              ]
        random_idx = random.randint(0, len(pq)-1)
        divisor = pq[random_idx][0]
        reduction_factor = pq[random_idx][1]
        
        dividend = random.choice([random.randint(0, 1000000),
                                  divisor * random.randint(0, 100000)])
        
        isDivisible = round(dividend / divisor, self._precision) == dividend // divisor
        statement = f"Is {dividend} divisible by {divisor}?"
        expr = ""
        answer = KPrintElement(str(isDivisible), 'text/html')
        
        solution = []
        
        s_step = f"We will apply last digit reduction method.\nThe reduction factor for {divisor} is {reduction_factor}."
        solution.append(KPrintElement(s_step, 'text/html'))
        
        reducedNumber = dividend
        loopCount = 0
        while reducedNumber > divisor * 5 and reducedNumber > 10 and loopCount < 50:
            loopCount = loopCount + 1
            s_step = f"<h3>Step {loopCount}: Number = {reducedNumber}</h3>"
            solution.append(KPrintElement(s_step, 'text/html'))
            
            lastDigit = reducedNumber % 10
            lastDigit_reduction = reduction_factor * lastDigit
            
            s_step = f"{reduction_factor} times of the last digit of {reducedNumber} \n= {reduction_factor} * {lastDigit} = {lastDigit_reduction}"
            solution.append(KPrintElement(s_step, 'text/html'))

            s_step = f"Remove the last digit from {reducedNumber} \n= {reducedNumber // 10}"
            solution.append(KPrintElement(s_step, 'text/html'))

            reducedNumber = reducedNumber // 10
            if lastDigit_reduction >= 0:
                s_step = f"Add {lastDigit_reduction} from {reducedNumber} \n= {reducedNumber} + {lastDigit_reduction} = {reducedNumber + lastDigit_reduction}"
            else:
                s_step = f"Add {lastDigit_reduction} from {reducedNumber} \n= {reducedNumber} - {abs(lastDigit_reduction)} = {reducedNumber + lastDigit_reduction}"
                
            solution.append(KPrintElement(s_step, 'text/html'))
            reducedNumber = reducedNumber + lastDigit_reduction
     
            pass
        
        isDivisible_reducedNumber = round(reducedNumber / divisor, self._precision) == reducedNumber // divisor
        
        s_step = f"<h3>Result:</h3>"
        solution.append(KPrintElement(s_step, 'text/html'))
        if isDivisible != isDivisible_reducedNumber:
            s_step = f"<h1>ERROR:</h1>"
            solution.append(KPrintElement(s_step, 'text/html'))
                
        if isDivisible_reducedNumber:
            s_step = f"{reducedNumber} is divisible by divisor {divisor}. \nHence, {dividend} is also divisible by {divisor}."
            pass
        else:
            s_step = f"{reducedNumber} is not divisible by divisor {divisor}. \nHence, {dividend} is also not divisible by {divisor}."
            pass
        solution.append(KPrintElement(s_step, 'text/html'))
        
        
        problemTemplate = KProblemTemplate(
                problem_type = '_problem_divisible_by_7',
                problem = [statement, expr],
                answer = answer,
                solution = solution)
        
        return problemTemplate
        pass

