'''
Created on 07-Feb-2021

@author: bthakur
'''
from ._arithmetichelper import ArithmeticHelper
from bs4 import BeautifulSoup

class Sum():
    def __init__(self):
        self._soup = None
        self._defaultImageName = None
        self.steps = None
        self._result = None
        self._nums = None
        pass
    pass

    def _initProb(self, *nums):
        self._nums = nums
        self._result = sum(self._nums)
        self._steps = []
        pass

    def _add(self, *args, **kwargs):
        imageName = [x for x in args if isinstance(x, str)]
        if imageName:
            imageName = imageName[0]
        else:
            imageName = None
        self._setDefaultImageName(imageName)
        
        nums = [x for x in args if isinstance(x, int)]
        self._initSoup()
        
        method_type = kwargs.get('method_type', 1) 
        posNums = [x for x in nums if x < 0]
        negNums = [x for x in nums if x > 0]
        absMax = max([abs(x) for x in nums])
        if (method_type == 2 and 
            (len(nums) > 2 or absMax > 9) and 
            len(posNums) > 0 and 
            len(negNums) > 0 
            ):
            self._initProb(*nums)
            return self._add_method_type_2()
        
        #for default method_type
        if all(abs(n) <= 10 for n in nums): 
            return self._addSmall(*nums)
        return self._addBig(*nums)
        
    def _add_method_type_2(self, **kwargs):
        expr = self._getNumArrayToString(*self._nums)
        step = {'expr': expr, 'hint': 'Solve' }
        self._appendStep(step)
        
        posNums = [x for x in self._nums if x > 0]
        negNums = [x for x in self._nums if x < 0]
        
        if 0 in self._nums:
            sHint = f"\nIgnore all zeros"
            step = {'hint': sHint }
            self._appendStep(step)
            pass
        
        posSum = sum(posNums)
        negSum = abs(sum(negNums))
    
        if len(posNums) > 1:    
            posStr = " + ".join([str(x) for x in posNums])
            expr = f"""  {posStr} \n= {posSum}"""
            sHint = f"\nAdd all positive numbers"
            step = {'expr': expr, 'hint': sHint }
            self._appendStep(step)
        else:
            sHint = f"\nThe positive number is"
            step = {'expr': posSum, 'hint': sHint }
            self._appendStep(step)
    
        if len(negNums) > 1:    
            negStr = " + ".join([str(abs(x)) for x in negNums])
            expr = f"""  {negStr} \n= {negSum}"""
            sHint = f"\nAdd all negative numbers, ignoring the negative sign"
            step = {'expr': expr, 'hint': sHint }
            self._appendStep(step)
        else:
            sHint = f"\nNegative number, ignoring the negative sign"
            step = {'expr': negSum, 'hint': sHint }
            self._appendStep(step)
            
        if posSum >= negSum:
            sHint = f"\nAs the sum of positive numbers is bigger,"
            sHint = f"{sHint}\ndeduct the sum of negative numbers from the sum of positive numbers."
            expr = f"{posSum} - {negSum} = {posSum - negSum}"
            pass
        else:
            sHint = f"\nAs the sum of negative numbers is bigger,"
            sHint = f"{sHint}\ndeduct sum of positive numbers from the sum of negative numbers."
            expr = f"{negSum} - {posSum} = {negSum - posSum}"
            pass
        
        step = {'expr': expr, 'hint': sHint }
        self._appendStep(step)
        
        
        if posSum == negSum:
            sHint = f"\nThe answer is zero"
        elif posSum > negSum:
            sHint = f"\nAs sum of positive numbers are bigger, the answer will be positive."
        else:
            sHint = f"\nAs sum of negative numbers are bigger, the answer will be negative."
        
        step = {'expr': self._result, 'hint': sHint }
        self._appendStep(step)
        
        
        show = kwargs.get('show', True)
        if show:
            self.printResult(self._steps)
        
        return self._result
        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 _appendStep(self, data):
        self._steps.append(data)
        pass


    def _addBig(self, *args):
        addTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_SECTION_TAG)
        
        #add add statement
        addTag.append(self._getAddTag(*args))
        
        #add expanded form
        addTag.append(self._getExpandedNumbersTag(*args))
        
        addTag.append(self._getSortedExpandedNumbersTag(*args))
        
        self._soup.find('body').append(addTag)
        
        htmlDisplay = ArithmeticHelper._getHtmlDisplay()
        if htmlDisplay is None:
            return self._soup.prettify()
         
        #print(self._soup.prettify())
        return htmlDisplay(self._soup.prettify())
        pass
    
    def _getAddTag(self, *nums):
        #strAdd_0 = ' + '.join([str(n) for n in nums])
        sArr = self._getNumArrayToString(*nums)
        
        tagAdd_0 = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        tagAdd_0.string = sArr
        return tagAdd_0
        pass
    
    def _getNumArrayToString(self, *nums):
        sArr = ""
        strOp = ""
        for n in nums:
            if n < 0:
                sArr = sArr + " - " + str(abs(n))
            else:
                sArr = sArr + f" {strOp} " + str(abs(n))
                pass
            strOp = "+"
            pass
        return sArr
        pass

    def _getExpandedNumbersTag(self, *nums):
        strAdd_1 = []
        for num in nums:
            arrPos = self._getPositionalValues(num)
            arrPos = list(reversed(list(arrPos.values())))
            arrPos = [str(x) for x in  arrPos]
            
            strContent = " + ".join(arrPos) 
            strContent = f"({strContent})"
            strAdd_1.append(strContent)
        
        tagAdd_1 = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        strContent = "= " +  " + ".join(strAdd_1)
        tagAdd_1.string = strContent
        return tagAdd_1
        pass

    def _getSortedExpandedNumbersTag(self, *nums):
        sortdDict = {}
        #dict(sorted(x.items(), key=lambda item: item[1]))
        for num in nums:
            arrPos = self._getPositionalValues(num)
            for k, v in arrPos.items():
                keyItem = sortdDict.get(k, None)
                if keyItem:
                    keyItem.append(v)
                    sortdDict[k] = keyItem
                else:
                    sortdDict[k] = [v]
                #print(sortdDict)
        
        sumContent = []
        for k in sorted(sortdDict.keys(), reverse = True):
            items = sortdDict.get(k, None)
            if not items:
                continue
            
            sumContent.append(items)
            pass

        sortedTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_SECTION_TAG)
        sortedTag.append(self._getDeepArraySumTag(sumContent))
        
        while True:
            recSumTag, sumContent = self._getRecurringSum(sumContent)
            if recSumTag is None:
                break
                pass
            sortedTag.append(recSumTag)
            pass

        sumTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        sumTag.string = "= " + str(sum(nums))
        sortedTag.append(sumTag)
        
        return sortedTag
        pass

    def _getDeepArraySumTag(self, sumContent):
        strArray = []
        strOp = ""
        for term in sumContent:
            strTerm = self._getNumArrayToString(*term).strip()
            if strTerm.startswith("-"):
                strTerm = f"{strTerm}"
            else:
                strTerm = f" {strOp} {strTerm}"
            strArray.append(strTerm)
            strOp = " + "
            
        rowTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        rowTag.string = "= " +  " ".join(strArray)
        return rowTag
        pass


    def _getRecurringSum(self, sumContent):
        if len(sumContent) <= 1:
            return None, sumContent
        
        if len(sumContent[0]) > 1:
            sumContent[0] = [sum(sumContent[0])]
            rowTag = self._getDeepArraySumTag(sumContent)
            return rowTag, sumContent
        
        if len(sumContent[1]) > 1:
            sumContent[1] = [sum(sumContent[1])]
            rowTag = self._getDeepArraySumTag(sumContent)
            return rowTag, sumContent
        
        
        tempContent = [sumContent[0] + sumContent[1]]
        if len(sumContent) > 2:
            tempContent.extend(sumContent[2:])
            
        rowTag = self._getDeepArraySumTag(tempContent)
        return rowTag, tempContent
        pass

    def _getPositionalValues(self, num):
        nSign = 1
        if num < 0:
            num = abs(num)
            nSign = -1
            pass
        
        strNum = str(num)
        
        arrPos = {}
        for i in range(len(strNum)):
            k = -(i+1)
            dgt = int(strNum[k])
            if dgt == 0:
                continue
            posNum = dgt * 10 ** i  * nSign
            arrPos.update({abs(k)-1: posNum})
            pass
        
        return arrPos
        pass

    def _addSmall(self, *args):
        addTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_SECTION_TAG)
        
        #add add statement
        addTag.append(self._getAddTag(*args))

        shapeImage = self._defaultImageName
        strHtml = ''
        tot = 0
        for num in args:
            rowTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
            
            shapeTag = self._getShapeSeriesWithSup(num, shapeImage, supStart = tot)
            rowTag.append(shapeTag)

            tot = tot + num
            addTag.append(rowTag)
            pass
        
        sumTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        sumTag.string = "= " + str(sum(args))
        addTag.append(sumTag)

        self._soup.find('body').append(addTag)
        
        htmlDisplay = ArithmeticHelper._getHtmlDisplay()
        if htmlDisplay is None:
            return self._soup.prettify()
         
        #print(self._soup.prettify())
        return htmlDisplay(self._soup.prettify())
        pass

    
    
    def _getShapeSeriesWithSup(self, num, shapeImage, supStart = 0):
        shapesTag = self._soup.new_tag("span")
        
        supIdx = supStart + 1
        
        nStart = 0
        nEnd = num
        nIncr = 1
        if num < 0:
            nStart = num
            nEnd = 0
            nIncr = -1
            pass
        
        #print("Range: ", nStart, nEnd, nIncr)
        
        for i in range(nStart, nEnd, 1):
            #print(f"i = {i}")
            supStart = supStart + nIncr
            supTag = self._soup.new_tag("sup")
            supTag.string = f"{supStart}"
            
            shapeTag = self._soup.new_tag("span")
            shapeTag.string = f" {shapeImage}"
            
            supTag['style'] = "color: red; font-weight: normal; font-size: 0.6em;"
            
            shapesTag.append(shapeTag)
            shapesTag.append(supTag)
            pass
        return shapesTag
        pass

    def _setDefaultImageName(self, imageName = None):
        self._defaultImageName = ArithmeticHelper._getImageOrDefault(imageName)
        pass

    def _initSoup(self):
        self._soup = BeautifulSoup("""
        <html>
            <head></head>
            <body>
            </body>
        </html>
        """, 'html.parser')
        pass
    
