'''
Created on 07-Feb-2021

@author: bthakur
'''
from ._arithmetichelper import ArithmeticHelper
from bs4 import BeautifulSoup
from functools import reduce
        
class Multiply():
    def __init__(self):
        self._soup = None
        self._defaultImageName = None
        pass
    pass

    def _multiply(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()
        
        if len(nums) > 2:
            return self._multiplyLargeSeries(*nums)
        
        if all(abs(n) <= 10 for n in nums): 
            return self._multiplySmall(*nums)
        
        expandSecond = kwargs.get('expandSecond', False)
        return self._multiplyBig(nums[0], nums[1], 
                                 expandSecond = expandSecond
                                 )

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

    def _multiplyBig(self, n1, n2, expandSecond = False):
        steps = []
        
        #step 0
        expr = f"{self._intToStr(n1)} * {self._intToStr(n2)}"
        steps.append({'expr_latex': expr})
        
        #step 1
        if abs(n2) < abs(n1):
            temp = n1
            n1 = n2
            n2 = temp
            expr = f"{self._intToStr(n1)} * {self._intToStr(n2)}"
            steps.append({'expr_latex': expr,
                          'hint': 'Flip to keep smaller number (absolute values) first' })
            
        #step 2
        n1 = self._getPositionalValues(n1)
        if expandSecond:
            n2 = self._getPositionalValues(n2)
        else:
            n2 = {'0': n2}
        
        n2Key = list(n2.keys())[0]
            
        if len(n2.keys()) < 2:
            expandSecond = False
        
        if len(n1.keys()) > 1 or expandSecond:
            expr1 = " + ".join([self._intToStr(x) for x in n1.values()])
            if len(n2.keys()) > 1:
                expr2 = " + ".join([self._intToStr(x) for x in n2.values()])
                expr2 = f"({expr2})"
            else:
                expr2 = f"{self._intToStr(n2[n2Key])}"
                
            expr = f"({expr1}) * {expr2}"
            steps.append({'expr_latex': expr, 'hint': 'Expand numbers' })
        
        #step 3
        if len(n1) > 1:
            exprArr = []
            if len(n2.keys()) > 1:
                expr_n2 = " + ".join([self._intToStr(x) for x in n2.values()])
            else:
                expr_n2 = f"{n2[n2Key]}"
                
            for t1 in n1.values():
                expr_t1 = f"{self._intToStr(t1)} * ({expr_n2})"
                exprArr.append(expr_t1)
            expr = " + ".join(exprArr)
            steps.append({'expr_latex': expr, 'hint': 'Distribute multiplier' })
        
        #step 4
        exprArr = []
        nums = []
        for t1 in n1.values():
            tArr  =[]
            for t2 in n2.values():
                x = t1 * t2
                nums.append(x)
                expr_t =self._intToStr(x)
                tArr.append(expr_t)
            exprArr.append(f"""({" + ".join(tArr)})""")
            
        if len(n2.values()) > 1:
            expr = " + ".join(exprArr)
            steps.append({'expr_latex': expr, 'hint': 'Multiply parts' })
        
        #step 4
        #nums.sort(reverse = True)

        if len(nums) > 1:
            nums = sorted(nums, key=lambda item: 0-abs(item) )
            expr = " + ".join([self._intToStr(x) for x in nums])
            steps.append({'expr': expr, 'hint': 'Sort' })
        

        expr = sum(nums)
        steps.append({'expr': expr, 'hint': 'Answer' })
            
        self.printResult(steps)
        #return steps
    
        pass
    
    def _intToStr(self, n):
        if n >= 0:
            return f"{n}"
        
        return f"({n})"
        pass
  
    def _math(self, x):
        return x
        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 _getMultiplyTag(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))
                sArr = f"{sArr} {strOp} ({n})"
            else:
                #sArr = sArr + f" {strOp} " + str(abs(n))
                sArr = f"{sArr} {strOp} {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._getDeepArrayMultiplyTag(sumContent))
        
        while True:
            recMultiplyTag, sumContent = self._getRecurringMultiply(sumContent)
            if recMultiplyTag is None:
                break
                pass
            sortedTag.append(recMultiplyTag)
            pass

        mulTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        result = reduce(lambda x, y: x * y, nums, 1)
        
        mulTag.string = "= " + str(result)
        sortedTag.append(mulTag)
        
        return sortedTag
        pass

    def _getDeepArrayMultiplyTag(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 _getRecurringMultiply(self, sumContent):
        if len(sumContent) <= 1:
            return None, sumContent
        
        if len(sumContent[0]) > 1:
            sumContent[0] = [sum(sumContent[0])]
            rowTag = self._getDeepArrayMultiplyTag(sumContent)
            return rowTag, sumContent
        
        if len(sumContent[1]) > 1:
            sumContent[1] = [sum(sumContent[1])]
            rowTag = self._getDeepArrayMultiplyTag(sumContent)
            return rowTag, sumContent
        
        
        tempContent = [sumContent[0] + sumContent[1]]
        if len(sumContent) > 2:
            tempContent.extend(sumContent[2:])
            
        rowTag = self._getDeepArrayMultiplyTag(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
        
        arrPos = {k: v 
                for k, v in sorted(arrPos.items(), 
                                   key=lambda item: 0-abs(item[1]) )
                }
        
        return arrPos
        pass

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


        if args[1] > args[0]:
            args = list(args)
            args.sort(reverse = True)
            addTag.append(self._getMultiplyTag(*args))
        

        shapeImage = self._defaultImageName
        strHtml = ''
        tot = 0
        num = args[0]
        for i in range(args[1]):
            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
        
        mulTag = self._soup.new_tag(ArithmeticHelper._DEFAULT_ROW_TAG)
        result = self._multiplyLargeSeries(*args)
        mulTag.string = "= " + str(result)
        addTag.append(mulTag)

        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
    
