import odb
import os
import datetime
import math
import namemap
from math import gcd


class OdbToBookshelf:
    def __init__(
            self,
            opendbpy,
            opendb,
            cellPadding,
            modeFormat,
            layerCapacity):
        self.odbpy = opendbpy
        self.odb = opendb
        self.year = datetime.datetime.now().year
        self.month = datetime.datetime.now().strftime("%b")
        self.day = datetime.datetime.now().day
        self.user = 'Seungwon Kim at University of California, San Diego (sek006@ucsd.edu)'

        # This target Scale is required because academic placer uses smaller size of numbers than commercial benchmark.
        # Scale down relatively so that siteHeight is to be a scale factor
        # value.

        self.modeFormat = modeFormat

        # This input file is for global routing layer adjustment
        # By default, the fastroute.tcl.
        self.fileGR = layerCapacity

        self.chip = self.odb.getChip()
        self.block = self.chip.getBlock()
        self.units = self.block.getDefUnits()
        self.nets = self.block.getNets()
        self.tech = self.odb.getTech()
        self.insts = self.block.getInsts()
        self.BTerms = self.block.getBTerms()
        self.blocks = self.block.getBlockages()
        self.numInsts = len(self.insts)
        self.numBTerms = len(self.BTerms)
        # for bterm in self.BTerms:
        #  pins = bterm.getBPins()
        #  if len(pins)==0:
        #    self.numBTerms = self.numBTerms-1
        self.numNodes = self.numInsts + self.numBTerms

        # Suppose that the core's LL and UR corners are not fragmented rows.
        self.xMinCore = self.block.getRows()[0].getBBox().xMin()
        self.xMaxCore = self.block.getRows()[-1].getBBox().xMax()
        self.yMinCore = self.block.getRows()[0].getBBox().yMin()
        self.yMaxCore = self.block.getRows()[-1].getBBox().yMax()

        self.siteWidth = self.block.getRows()[0].getSite().getWidth()
        self.siteHeight = self.block.getRows()[0].getSite().getHeight()
        self.siteSpacing = self.block.getRows()[0].getSpacing()
        print("core area (llx lly urx ury): %s %s  %s %s" %
              (self.xMinCore, self.yMinCore, self.xMaxCore, self.yMaxCore))
        print("siteWidth: %s" % self.siteWidth)
        print("siteHeight: %s" % self.siteHeight)
        print("GCD: %s" % gcd(self.siteWidth, self.siteHeight))
        #self.scaleFactor = self.targetScale / self.siteHeight
        self.scaleFactor = 1 / gcd(self.siteWidth, self.siteHeight)
        self.targetScale = self.scaleFactor * self.siteHeight
        print("Target siteHeight Scale: %s" % self.targetScale)
        print(
            "Scale Factor (Target siteHeight Scale / siteHeight): %s" %
            self.scaleFactor)
        print(
            "scaled core area (llx lly urx ury): %s %s  %s %s" %
            (self.xMinCore *
             self.scaleFactor,
             self.yMinCore *
             self.scaleFactor,
             self.xMaxCore *
             self.scaleFactor,
             self.yMaxCore *
             self.scaleFactor))
        print("scaled siteWidth: %s" % (self.siteWidth * self.scaleFactor))
        print("scaled siteHeight: %s" % (self.siteHeight * self.scaleFactor))
        self.xMinCore = self.xMinCore * self.scaleFactor
        self.xMaxCore = self.xMaxCore * self.scaleFactor
        self.yMinCore = self.yMinCore * self.scaleFactor
        self.yMaxCore = self.yMaxCore * self.scaleFactor
        self.siteWidth = self.siteWidth * self.scaleFactor
        self.siteHeight = self.siteHeight * self.scaleFactor
        self.siteSpacing = self.siteSpacing * self.scaleFactor
        self.cellPad = int(self.siteWidth) * cellPadding

        self.offsetX = self.siteHeight - (self.xMinCore % self.siteHeight)
        self.offsetY = self.siteHeight - (self.yMinCore % self.siteHeight)
        print('OffsetCoordi : %s %s' % (self.offsetX, self.offsetY))
        self.unitY = self.siteHeight / self.targetScale
        self.unitX = self.unitY
        print('unit X Y : %s %s' % (self.unitX, self.unitY))

        self.signalLayers = []
        self.layerName = []
        self.layerPitch = []
        self.layerMinWireWidth = []
        self.layerMinWireSpacing = []
        self.viaSpacing = []
        for self.layer in self.tech.getLayers():
            if self.layer.getType() == 'ROUTING':
                self.signalLayers.append(self.layer)
                self.layerName.append(self.layer.getName())
                self.layerPitch.append(self.layer.getPitch())
                # Currently, bookshelf always use '1' MinWireWidth, '0'
                # MinWireSpacing and ViaSpacing
                self.layerMinWireWidth.append(100)
                # self.layerMinWireWidth.append(self.layer.getPitch())
                self.layerMinWireSpacing.append(0)
                self.viaSpacing.append(0)

    def WriteAux(self, bsName):
        print("Writing .aux")
        f = open('./output/%s/%s.aux' % (bsName, bsName), 'w')
        f.write(
            'RowBasedPlacement : %s.nodes %s.nets %s.wts %s.pl %s.scl %s.shapes %s.route' %
            (bsName, bsName, bsName, bsName, bsName, bsName, bsName))
        f.close()

    def WriteNodes(self, bsName):
        print("Writing .nodes")
        f = open('./output/%s/%s.nodes' % (bsName, bsName), 'w')
        f.write('UCLA nodes 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n' % self.user)
        f.write('\n')
        cntTerms = 0
        for inst in self.insts:
            if inst.isFixed():
                cntTerms = cntTerms + 1
        numTerms = self.numBTerms + cntTerms
        f.write('NumNodes      :  %s\n' %
                (self.numNodes + len(self.dictFakeCells)))
        f.write('NumTerminals  :  %s\n' % (numTerms + len(self.dictFakeCells)))
        f.write('\n')
        for inst in self.insts:
            instWidth = inst.getBBox().getDX()
            instHeight = inst.getBBox().getDY()
            if inst.isFixed():
                #f.write('%s%s%s        terminal\n'%(inst.getName().rjust(15), str('%0.4f'%(instWidth * self.scaleFactor)).rjust(15), str('%0.4f'%(instHeight * self.scaleFactor)).rjust(15)))
                f.write('%s%s%s        terminal\n' %
                        (inst.getName().rjust(15), str('%i' %
                                                       (instWidth * self.scaleFactor)).rjust(15), str('%i' %
                                                                                                      (instHeight * self.scaleFactor)).rjust(15)))
            else:
                #f.write('%s%s%s\n'%(inst.getName().rjust(15), str('%0.4f'%(instWidth * self.scaleFactor)).rjust(15), str('%0.4f'%(instHeight * self.scaleFactor)).rjust(15)))
                f.write('%s%s%s\n' %
                        (inst.getName().rjust(15), str('%i' %
                                                       ((instWidth *
                                                         self.scaleFactor) +
                                                        (self.cellPad *
                                                         2))).rjust(15), str('%i' %
                                                                             (instHeight *
                                                                              self.scaleFactor)).rjust(15)))
        for bterm in self.BTerms:
            pins = bterm.getBPins()
            # TODO: do not support multiple pins for one terminal
            #assert(len(pins) == 1)
            for pin in pins:
                boxes = pin.getBoxes()
                # TODO: do not support multiple boxes
                assert(len(boxes) == 1)
                for box in boxes:
                    btermWidth = box.xMax() - box.xMin()
                    btermHeight = box.yMax() - box.yMin()
                    #f.write('%s%s%s        terminal_NI\n'%(bterm.getName().rjust(15), str('%0.4f'%(btermWidth * self.scaleFactor)).rjust(15), str('%0.4f'%(btermHeight * self.scaleFactor)).rjust(15)))
                    #f.write('%s%s%s        terminal_NI\n'%(bterm.getName().rjust(15), str('%i'%(btermWidth * self.scaleFactor)).rjust(15), str('%i'%(btermHeight * self.scaleFactor)).rjust(15)))
                    if self.modeFormat == 'ISPD11':
                        f.write(
                            '%s%s%s        terminal_NI\n' %
                            (bterm.getName().rjust(15),
                             str('1').rjust(15),
                                str('1').rjust(15)))
                    else:
                        f.write(
                            '%s%s%s        terminal\n' %
                            (bterm.getName().rjust(15),
                             str('0').rjust(15),
                                str('0').rjust(15)))
            if len(pins) == 0:
                #f.write('%s%s%s        terminal_NI\n'%(bterm.getName().rjust(15), str('0.000000').rjust(15), str('0.000000').rjust(15)))
                if self.modeFormat == 'ISPD11':
                    f.write(
                        '%s%s%s        terminal_NI\n' %
                        (bterm.getName().rjust(15),
                         str('1').rjust(15),
                            str('1').rjust(15)))
                else:
                    f.write(
                        '%s%s%s        terminal\n' %
                        (bterm.getName().rjust(15),
                         str('0').rjust(15),
                            str('0').rjust(15)))

        for key, values in self.dictFakeCells.items():
            f.write(
                '%s%s%s        terminal\n' %
                (key.rjust(15), str(
                    '%i' %
                    (values[2])).rjust(15), str(
                    '%i' %
                    (self.siteHeight)).rjust(15)))

        f.close()

    def WriteRoute(self, bsName):
        print("Writing .route")
        f = open('./output/%s/%s.route' % (bsName, bsName), 'w')
        f.write('UCLA route 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n' % self.user)

        layerVcap = []
        layerHcap = []

        # Grid should be calculated with M2 Pitch x 15
        tileSize = self.layerPitch[1] * 15
        #tileSize = self.layerPitch[1]*20
        for i, layer in enumerate(self.signalLayers):
            if layer.getType() == 'ROUTING':
                if layer.getDirection() == 'VERTICAL':
                    layerVcap.append(
                        round(
                            tileSize /
                            self.layerPitch[i] *
                            self.layerMinWireWidth[i]))
                    layerHcap.append(0)
                elif layer.getDirection() == 'HORIZONTAL':
                    layerHcap.append(
                        round(
                            tileSize /
                            self.layerPitch[i] *
                            self.layerMinWireWidth[i]))
                    layerVcap.append(0)
                else:
                    print('Routing direction error: neither VERTICAL or HORIZONTAL')
                    exit()

        # Layer Capacity Adjustment
        # Currently, it supports only the capability change of the entire
        # region of each layers.
        fgr = open(self.fileGR, 'r')
        lines = fgr.readlines()
        fgr.close()
        layerAdjustNames, layerAdjustValues = [], []
        for line in lines:
            if line.startswith('set_global_routing_layer_adjustment'):
                layerRange = line.split()[1]
                if '-' in layerRange:
                    bottomLayerAdjust = layerRange.split('-')[0]
                    topLayerAdjust = layerRange.split('-')[1]
                    for i in range(
                            self.layerName.index(bottomLayerAdjust),
                            self.layerName.index(topLayerAdjust)):
                        layerAdjustNames.append(self.layerName[i])
                        layerAdjustValues.append(line.split()[2])
                else:
                    layerAdjustNames.append(layerRange)
                    layerAdjustValues.append(line.split()[2])
        print(layerAdjustNames)
        print(layerAdjustValues)

        strLayerVcap = ''
        strLayerHcap = ''
        strLayerMinWireWidth = ''
        strLayerMinWireSpacing = ''
        strViaSpacing = ''
        for i, layer in enumerate(self.signalLayers):
            if layer.getName() in layerAdjustNames:
                layerIdx = layerAdjustNames.index(layer.getName())
                reducedVcap = int(
                    float(
                        layerVcap[i]) *
                    float(
                        layerAdjustValues[layerIdx]))
                reducedHcap = int(
                    float(
                        layerHcap[i]) *
                    float(
                        layerAdjustValues[layerIdx]))
            else:
                reducedVcap = layerVcap[i]
                reducedHcap = layerHcap[i]

            strLayerVcap += str(reducedVcap).rjust(8)
            strLayerHcap += str(reducedHcap).rjust(8)
            strLayerMinWireWidth += str(self.layerMinWireWidth[i]).rjust(8)
            strLayerMinWireSpacing += str(self.layerMinWireSpacing[i]).rjust(8)
            strViaSpacing += str(self.viaSpacing[i]).rjust(8)

        print(layerHcap)
        print(layerVcap)
        print(self.layerName)
        print(self.layerPitch)

        gridOrigX = (0 + self.offsetX) / (self.unitX)
        gridOrigY = (0 + self.offsetY) / (self.unitY)
        print('orig (float): %s %s' % (gridOrigX, gridOrigY))
        gridOrigX = int(gridOrigX + 0.5)
        gridOrigY = int(gridOrigY + 0.5)
        print('orig (int) : %s %s' % (gridOrigX, gridOrigY))

        widthCore = (self.xMaxCore - self.xMinCore) / self.scaleFactor
        heightCore = (self.yMaxCore - self.yMinCore) / self.scaleFactor
        widthDie = (self.block.getBBox().xMax() - self.block.getBBox().xMin())
        heightDie = (self.block.getBBox().yMax() - self.block.getBBox().yMin())
        gridX = math.ceil(widthDie / tileSize)
        gridY = math.ceil(heightDie / tileSize)
        #gridX = math.ceil(widthCore / tileSize)
        #gridY = math.ceil(heightCore / tileSize)
        numLayers = len(self.layerName)

        # Porosity for routing blockages
        # Zero implies the blockage completely blocks
        # overlapping routing tracks. Default = 0
        blockagePorosity = 0

        f.write('Grid'.ljust(19) + ': %s %s %s\n' % (gridX, gridY, numLayers))

        f.write('VerticalCapacity'.ljust(19) + ': %s\n' % strLayerVcap)
        f.write('HorizontalCapacity'.ljust(19) + ': %s\n' % strLayerHcap)
        f.write('MinWireWidth'.ljust(19) + ': %s\n' % strLayerMinWireWidth)
        f.write('MinWireSpacing'.ljust(19) + ': %s\n' % strLayerMinWireSpacing)
        f.write('ViaSpacing'.ljust(19) + ': %s\n' % strViaSpacing)
        f.write('GridOrigin'.ljust(19) + ': %s %s\n' % (int(gridOrigX *
                self.scaleFactor), int(gridOrigY * self.scaleFactor)))
        f.write('TileSize'.ljust(19) + ': %s %s\n' % (int(math.ceil(tileSize *
                self.scaleFactor)), int(math.ceil(tileSize * self.scaleFactor))))
        f.write('BlockagePorosity'.ljust(19) + ': %s\n\n' % (blockagePorosity))

        numNiTerminals = self.numBTerms
        for bterm in self.BTerms:
            pins = bterm.getBPins()
            if len(pins) == 0:
                numNiTerminals = numNiTerminals - 1
        f.write('NumNiTerminals'.ljust(19) + ': %s\n' % numNiTerminals)
        for bterm in self.BTerms:
            pins = bterm.getBPins()
            for pin in pins:
                boxes = pin.getBoxes()
                for box in boxes:
                    terminalNiLayer = int(
                        self.layerName.index(
                            box.getTechLayer().getName())) + 1
                    f.write(
                        '  %s' %
                        (bterm.getName()) +
                        ' %s\n' %
                        (terminalNiLayer))
            if len(pins) == 0:
                pass
                #f.write('  %s'%(bterm.getName())+' %s\n'%(terminalNiLayer))

        cntBlockages = 0

        strListBlocks = []
        for inst in self.insts:
            if inst.isFixed():
                blockName = inst.getName()
                listLayer = []
                for obs in inst.getMaster().getObstructions():
                    curLayer = obs.getTechLayer()
                    if curLayer.getType() == 'ROUTING':
                        listLayer.append(
                            self.layerName.index(
                                curLayer.getName()))
                listLayer = sorted(list(set(listLayer)))
                if len(listLayer) == 0:
                    # Do not write if the OBS is in M1 only.
                    continue
                if len(listLayer) == 1 and listLayer[0] == 0:
                    # Do not write if the OBS is in M1 only.
                    continue
                    #listLayer = [1]
                    #strBlockLayers = ' 1'
                cntBlockages = cntBlockages + 1
                strBlockLayers = ''
                for i in listLayer:
                    strBlockLayers = strBlockLayers + ' ' + str(i + 1)
                strListBlocks.append(
                    '  %s  %s%s\n' %
                    (blockName, len(listLayer), strBlockLayers))

        for block in self.blocks:
            if block.getInstance() is not None:
                # Do not write if the OBS is in M1 only.
                continue
                #blockName = block.getInstance().getName()
                #topBlockLayer = str(1)
                #blockLayers = ' '+str(1)
                #f.write('  %s  %s%s\n'%(blockName, topBlockLayer, blockLayers))

        numBlockages = len(self.block.getBlockages()) + cntBlockages
        # below three lines are commented out because .shape has a scaling mismatch, overlapping problem with bookshelf grid.
        # only rectangular boundary box can be considered for the shape.
        f.write('NumBlockageNodes'.ljust(19) + ': 0\n')
        #f.write('NumBlockageNodes'.ljust(19)+': %s\n'%cntBlockages)
        # for string in strListBlocks:
        #  f.write('%s'%string)

        f.write('NumEdgeCapacityAdjustments  : 0\n')

        #numEdgeCapacity = len(layerAdjustNames) * (gridX * gridY * 2 - (gridX+gridY))
        #f.write('NumEdgeCapacityAdjustments  : %s\n'%numEdgeCapacity)
        # for i in range(0,len(layerAdjustNames)):
        #  layerIdx = self.layerName.index(layerAdjustNames[i])
        #  curVcap = float(layerAdjustValues[i])*float(layerVcap[layerIdx])
        #  curHcap = float(layerAdjustValues[i])*float(layerHcap[layerIdx])
        #  curAdjustValue = int(max(curVcap, curHcap))
        #  for j in range(0,gridX):
        #    for k in range(0,gridY):
        #      if k+1 < gridY and j+1 < gridX:
        #        strLayerAdjust = '  %s  %s  %s  %s  %s  %s  %s\n'%(j,k,layerIdx+1, j, k+1, layerIdx+1, curAdjustValue)
        #        f.write(strLayerAdjust)
        #        strLayerAdjust = '  %s  %s  %s  %s  %s  %s  %s\n'%(j,k,layerIdx+1, j+1, k, layerIdx+1, curAdjustValue)
        #        f.write(strLayerAdjust)
        #      elif k+1 == gridY and j+1 < gridX:
        #        strLayerAdjust = '  %s  %s  %s  %s  %s  %s  %s\n'%(j,k,layerIdx+1, j+1, k, layerIdx+1, curAdjustValue)
        #        f.write(strLayerAdjust)
        #      elif k+1 < gridY and j+1 == gridX:
        #        strLayerAdjust = '  %s  %s  %s  %s  %s  %s  %s\n'%(j,k,layerIdx+1, j, k+1, layerIdx+1, curAdjustValue)
        #        f.write(strLayerAdjust)
        f.close()

    def WriteWts(self, bsName):
        print("Writing .wts")
        f = open('./output/%s/%s.wts' % (bsName, bsName), 'w')
        f.write('UCLA wts 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n\n' % self.user)
        f.close()

    def WriteNets(self, bsName):
        print("Writing .nets")
        f = open('./output/%s/%s.nets' % (bsName, bsName), 'w')
        f.write('UCLA nets 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n\n' % self.user)

        # NumNets and NumPins
        cntNets = 0
        numPins = 0
        for net in self.nets:
            netDegree = net.getTermCount()
            if net.isSpecial() != 1 and netDegree > 1:
                cntNets = cntNets + 1
                numPins = numPins + net.getTermCount()

        f.write('NumNets  :  %s\n' % cntNets)
        f.write('NumPins  :  %s\n\n' % numPins)

        for net in self.nets:
            if net.isSpecial() == 1:
                continue
            netDegree = net.getTermCount()
            if netDegree < 2:
                continue
            netName = net.getName()
            f.write('NetDegree  :  %s    %s\n' % (netDegree, netName))
            # for boundary pins (PI, PO, others)
            for bPin in net.getBTerms():
                instName = bPin.getName()
                bPinDirection = bPin.getIoType()
                if bPinDirection == 'INPUT':
                    bPinDirection = 'I'
                elif bPinDirection == 'OUTPUT':
                    bPinDirection = 'O'
                else:
                    iPinDirection = 'B'

                # Pin offset. X,Y offset for PI, PO is 0,0.
                bPinXOffset = 0
                bPinYOffset = 0

                #f.write('%s   %s  :%s%s\n'%(str(instName).rjust(15), bPinDirection, str('%0.4f'%(bPinXOffset * self.scaleFactor)).rjust(12), str('%0.4f'%(bPinYOffset * self.scaleFactor)).rjust(12)))
                f.write('%s   %s  :%s%s\n' %
                        (str(instName).rjust(15), bPinDirection, str('%i' %
                                                                     (bPinXOffset * self.scaleFactor)).rjust(12), str('%i' %
                                                                                                                      (bPinYOffset * self.scaleFactor)).rjust(12)))

            # for instance pins
            for iPin in net.getITerms():
                instName = iPin.getInst().getName()
                iPinDirection = iPin.getIoType()
                if iPinDirection == 'INPUT':
                    iPinDirection = 'I'
                elif iPinDirection == 'OUTPUT':
                    iPinDirection = 'O'
                else:
                    iPinDirection = 'B'

                instXLoc, instYLoc = iPin.getInst().getLocation()
                instXCen = float(
                    instXLoc) + float(iPin.getInst().getMaster().getWidth()) / float(2)
                instYCen = float(
                    instYLoc) + float(iPin.getInst().getMaster().getHeight()) / float(2)
                isShape, iPinXCen, iPinYCen, = iPin.getAvgXY()
                xx = 0.0000
                yy = 0.0000
                tt = 0
                nn = 0
                instOrigX, instOrigY = iPin.getInst().getOrigin()
                instOrig = odb.Point(instOrigX, instOrigY)
                instOrient = iPin.getInst().getOrient()
                t = odb.dbTransform(instOrient, instOrig)
                tx = 0
                ty = 0
                for mPin in iPin.getMTerm().getMPins():
                    for box in mPin.getGeometry():
                        ## Shape ########
                        # Example:
                        #   a----------b
                        #   |          |
                        #   |          |
                        #   |          |
                        #   c----------d
                        #
                        # "getGeomShape().getPoints()" --> a, b, c, d, a
                        #
                        rr = odb.Rect(box.xMin(), box.yMin(), box.xMax(), box.yMax())
                        
                        # Calculate center of pin
                        for pp in rr.getPoints():
                            t.apply(pp)
                            tt += 1
                            tx += float(pp.getX())
                            ty += float(pp.getY())


                iPinXCen = float(tx) / float(tt)
                iPinYCen = float(ty) / float(tt)

                #print("iPinCens: %s %s"%(iPinXCen, iPinYCen))
                #print("instCens: %s %s"%(instXCen, instYCen))
                iPinXOffset = (float(iPinXCen) - instXCen)
                iPinYOffset = (float(iPinYCen) - instYCen)
                #print(iPinXOffset, iPinYOffset)

                #f.write('%s   %s  :%s%s\n'%(str(instName).rjust(15), iPinDirection, str('%0.4f'%(iPinXOffset * self.scaleFactor)).rjust(12), str('%0.4f'%(iPinYOffset * self.scaleFactor)).rjust(12)))
                f.write('%s   %s  :%s%s\n' %
                        (str(instName).rjust(15), iPinDirection, str('%i' %
                                                                     (iPinXOffset * self.scaleFactor)).rjust(12), str('%i' %
                                                                                                                      (iPinYOffset * self.scaleFactor)).rjust(12)))

        f.close()

    def WritePl(self, bsName):
        print("Writing .pl")
        f = open('./output/%s/%s.pl' % (bsName, bsName), 'w')
        f.write('UCLA pl 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n\n' % self.user)

        # Orientation of all the nodes will always be N (default)
        for inst in self.insts:
            instLocX, instLocY = inst.getLocation()
            scaledInstLocX = instLocX
            scaledInstLocY = instLocY
            if inst.isFixed() == 0:
                #f.write('%s'%inst.getName() + '\t%0.4f\t%0.4f : N\n'%((scaledInstLocX * self.scaleFactor)-self.cellPad,scaledInstLocY * self.scaleFactor))
                f.write(
                    '%s' %
                    inst.getName() +
                    '\t%i\t%i : N\n' %
                    (scaledInstLocX *
                     self.scaleFactor,
                     scaledInstLocY *
                     self.scaleFactor))
            else:
                #f.write('%s'%inst.getName() + '\t%0.4f\t%0.4f : N /FIXED\n'%(scaledInstLocX * self.scaleFactor,scaledInstLocY * self.scaleFactor))
                f.write(
                    '%s' %
                    inst.getName() +
                    '\t%i\t%i : N /FIXED\n' %
                    (scaledInstLocX *
                     self.scaleFactor,
                     scaledInstLocY *
                     self.scaleFactor))
        for bterm in self.BTerms:
            pins = bterm.getBPins()
            for pin in pins:
                pinLocX = pin.getBBox().xMin()
                pinLocY = pin.getBBox().yMin()
                #f.write('%s'%bterm.getName() + '\t%0.4f\t%0.4f : N /FIXED_NI\n'%(pinLocX * self.scaleFactor,pinLocY * self.scaleFactor))
                f.write(
                    '%s' %
                    bterm.getName() +
                    '\t%i\t%i : N /FIXED_NI\n' %
                    (pinLocX *
                     self.scaleFactor,
                     pinLocY *
                     self.scaleFactor))
            if len(pins) == 0:
                #f.write('%s'%bterm.getName() + '\t0.0000\t0.0000 : N /FIXED_NI\n')
                f.write('%s' % bterm.getName() + '\t0\t0 : N /FIXED_NI\n')

        for key, values in self.dictFakeCells.items():
            f.write(
                '%s' %
                key +
                '\t%i\t%i : N /FIXED\n' %
                (values[0],
                 values[1]))

        f.close()

    def WriteScl(self, bsName):
        print("Writing .scl")
        f = open('./output/%s/%s.scl' % (bsName, bsName), 'w')
        f.write('UCLA scl 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n\n' % self.user)

        self.numRows = int((self.yMaxCore - self.yMinCore) / self.siteHeight)
        self.numSites = int((self.xMaxCore - self.xMinCore) / self.siteWidth)
        f.write('NumRows :  \t%s\n\n' % self.numRows)
        #f.write('NumRows :  \t%s\n\n'%len(self.block.getRows()))
        for curRow in range(self.numRows):
            f.write('CoreRow Horizontal\n')
            f.write('  Coordinate     :   %i\n' %
                    ((curRow) * self.siteHeight + self.yMinCore))
            f.write('  Height         :   %i\n' % (self.siteHeight))
            f.write('  Sitewidth      :    %i\n' % (self.siteWidth))
            f.write('  Sitespacing    :    %i\n' % (self.siteSpacing))
            if (curRow + 1) % 2 == 0:
                f.write('  Siteorient     :    MX\n')
            else:
                f.write('  Siteorient     :    R0\n')
            f.write('  Sitesymmetry   :    True\n')
            f.write(
                '  SubrowOrigin   :    %i\tNumSites  :  %s\n' %
                (self.xMinCore, self.numSites))
            f.write('End\n')
        f.close()

        # Store the fixed fake cells for fragmented rows.
        xMaxCandi = {}
        xMinCandi = {}
        for rowIdx, row in enumerate(self.block.getRows()):
            # if ((row.getBBox().xMin() * self.scaleFactor) != self.xMinCore) and (self.block.getRows()[rowIdx-1].getBBox().yMin() != row.getBBox().yMin()):
            #    xMinFakeCell = self.xMinCore
            #    xMaxFakeCell = self.block.getRows()[rowIdx+1].getBBox().xMin() * self.scaleFactor
            #    if self.block.getRows()[rowIdx+1].getBBox().yMin() != row.getBBox().yMin():
            #        print("1")
            #        xMaxFakeCell = self.xMaxCore
            #    print(xMinFakeCell,  xMaxFakeCell)
            #    continue

            # if (row.getBBox().xMax() * self.scaleFactor) != self.xMaxCore:
            #    xMinFakeCell = row.getBBox().xMax()*self.scaleFactor
            #    xMaxFakeCell = self.block.getRows()[rowIdx+1].getBBox().xMin() * self.scaleFactor
            #    if self.block.getRows()[rowIdx+1].getBBox().yMin() != row.getBBox().yMin():
            #        print(self.block.getRows()[rowIdx+1].getBBox().yMin(), row.getBBox().yMin())
            #        xMaxFakeCell = self.xMaxCore
            #    print(xMinFakeCell,  xMaxFakeCell)
            #    continue
            if (row.getBBox().xMin() *
                self.scaleFactor == self.xMinCore) and (row.getBBox().xMax() *
                                                        self.scaleFactor == self.xMaxCore):
                continue
            if row.getBBox().xMin() * self.scaleFactor != self.xMinCore:
                xMaxCandi.setdefault(
                    row.getBBox().yMin() * self.scaleFactor, [])
                xMaxCandi[row.getBBox().yMin() *
                          self.scaleFactor].append(row.getBBox().xMin() *
                                                   self.scaleFactor)
                pass
            if row.getBBox().xMax() * self.scaleFactor != self.xMaxCore:
                xMinCandi.setdefault(
                    row.getBBox().yMin() * self.scaleFactor, [])
                xMinCandi[row.getBBox().yMin() *
                          self.scaleFactor].append(row.getBBox().xMax() *
                                                   self.scaleFactor)
                pass

        #print("xMax candi: %s"%xMaxCandi)
        #print("xMin candi: %s"%xMinCandi)
        # self.dictFakeCells key: fakecell name, values: [llx, lly, width]
        self.dictFakeCells = {}
        countFakeCells = 0
        for key, values in xMinCandi.items():
            xMinCandi[key].sort()
            xMaxCandi[key].sort()
            #print(key, values, xMaxCandi[key])
            if values[0] > xMaxCandi[key][0]:
                xMinCandi[key].append(self.xMinCore)
            if values[-1] > xMaxCandi[key][-1]:
                xMaxCandi[key].append(self.xMaxCore)
            for idx, value in enumerate(values):
                #print(value, xMaxCandi[key][idx])

                fakeCellName = "fk%s" % countFakeCells
                countFakeCells = countFakeCells + 1
                self.dictFakeCells.setdefault(fakeCellName, [])
                fakeCellWidth = xMaxCandi[key][idx] - value
                self.dictFakeCells[fakeCellName] = [value, key, fakeCellWidth]
        # print(self.dictFakeCells)

    def WriteShapes(self, bsName):
        print("Writing .shapes")
        f = open('./output/%s/%s.shapes' % (bsName, bsName), 'w')
        f.write('UCLA shapes 1.0\n')
        f.write(
            '# Created  :  %s  %s %s\n' %
            (self.month, self.day, self.year))
        f.write('# User     :  %s\n\n' % self.user)

        # it is commented out because .shape has a scaling mismatch, overlapping problem with bookshelf grid.
        # only rectangular boundary box can be considered for the shape.
        #cntNonRectNodes = 0
        #nestedListBlockShapes = []
        # for inst in self.insts:
        #  if inst.isFixed():
        #    listStrBlockShapes = []
        #    listBlockShapes = []
        #    blockName = inst.getName()
        #    instObstructions = inst.getMaster().getObstructions()
        #    listLayer = []
        #    for obs in instObstructions:
        #      curLayer = obs.getTechLayer()
        #      if curLayer.getType() == 'ROUTING':
        #        listLayer.append(self.layerName.index(curLayer.getName()))
        #    listLayer = sorted(list(set(listLayer)))
        #    if len(listLayer)==0:
        #      #Do not write if the OBS is in M1 only.
        #      continue
        #    if len(listLayer)==1 and listLayer[0]==0:
        #      #Do not write if the OBS is in M1 only.
        #      continue
        #    if len(instObstructions)!=0:
        #      cntNonRectNodes = cntNonRectNodes+1
        #      listStrBlockShapes.append(blockName)
        #      for i, obs in enumerate(instObstructions):
        #        curLayer = obs.getTechLayer()
        #        if curLayer.getType() == 'ROUTING':
        #          #f.write('  Shape_%s'%i+' %s %s\t%s %s\n'%(obs.xMin(),obs.yMin(),obs.getDX(),obs.getDY()))
        #          #listStrBlockShapes.append(str('  Shape_%s'%i+' %0.4f %0.4f\t%0.4f %0.4f\n'%(obs.xMin()* self.scaleFactor,obs.yMin()* self.scaleFactor,obs.getDX()* self.scaleFactor,obs.getDY()* self.scaleFactor)))
        #          #listStrBlockShapes.append(str('  Shape_%s'%i+' %i %i\t%i %i\n'%(math.ceil(obs.xMin() * self.scaleFactor),math.ceil(obs.yMin() * self.scaleFactor),math.ceil(obs.getDX() * self.scaleFactor),math.ceil(obs.getDY() * self.scaleFactor))))
        #          #listStrBlockShapes.append(str('  Shape_%s'%i+' %i %i\t%i %i\n'%(math.ceil(obs.xMin() * self.scaleFactor),math.ceil(obs.yMin() * self.scaleFactor),math.floor(obs.getDX() * self.scaleFactor),math.floor(obs.getDY() * self.scaleFactor))))
        #          #listStrBlockShapes.append(str('  Shape_%s'%i+' %0.4f %0.4f\t%0.4f %0.4f\n'%(obs.xMin()* self.scaleFactor,obs.yMin()* self.scaleFactor,obs.getDX()* self.scaleFactor,obs.getDY()* self.scaleFactor)))
        #          #listBlockShapes.append(str('%i %i\t%i %i\n'%(math.ceil(obs.xMin() * self.scaleFactor),math.ceil(obs.yMin() * self.scaleFactor),math.floor(obs.getDX() * self.scaleFactor),math.floor(obs.getDY() * self.scaleFactor))))
        #          listBlockShapes.append(str('%i %i\t%i %i\n'%((obs.xMin() * self.scaleFactor),(obs.yMin() * self.scaleFactor),(obs.getDX() * self.scaleFactor),(obs.getDY() * self.scaleFactor))))
        #
        #      listBlockShapes = list(set(listBlockShapes))
        #      for i, strBlockShape in enumerate(listBlockShapes):
        #        listStrBlockShapes.append(str('  Shape_%s'%i+' %s'%strBlockShape))
        #      nestedListBlockShapes.append(listStrBlockShapes)

        #    #f.write('%s  :  %s\n'%(blockName,len(instObstructions))
        #f.write('NumNonRectangularNodes  :  %s\n\n'%cntNonRectNodes)
        # for curListBlockShapes in nestedListBlockShapes:
        #  f.write('%s  :  %s\n'%(curListBlockShapes[0], len(curListBlockShapes)-1))
        #  for idxStrShape in range(1,len(curListBlockShapes)):
        #    f.write('%s'%curListBlockShapes[idxStrShape])
        ####################

        f.close()

    def WriteBookshelf(self, bsName):
        odb.write_def(self.block, '%s_origin.def' % (bsName))
        if not os.path.exists('output'):
            os.makedirs('output')
        if not os.path.exists('output/%s' % bsName):
            os.makedirs('output/%s' % bsName)
        self.WriteAux(bsName)
        self.WriteScl(bsName)
        self.WriteNodes(bsName)
        self.WriteRoute(bsName)
        self.WriteWts(bsName)
        self.WriteNets(bsName)
        self.WritePl(bsName)
        self.WriteShapes(bsName)
        print('Writing done')

        # debug
        self.show(bsName)

        # convert for mapping
        self.convert(bsName)

    def convert(self, bsName):
        os.chdir('output/%s' % bsName)
        namemap.main('%s.aux' % bsName)
        os.chdir('../../')

    def show(self, bsName):
        # print(self.chip)
        # print(self.block)
        # print(self.nets)
        # print(self.tech)
        print(self.numInsts)
        print(self.numBTerms)
        print(self.numNodes)


if __name__ == "__main__":

    ################ Settings #################
    odbPath = './odbFiles/'

    # The number of sites for cell padding (+left, +right)
    cellPaddings = [0, 1, 2, 3, 4]
    modeFormats = ['ISPD04', 'ISPD11']

    odbList = [
        'sky130hd_ISPD2006_adaptec1',
    ]

    # Layer capacity adjustment tcl file for global routing
    #layerCapacity = 'layeradjust_sky130hd.tcl'
    layerCapacity = 'layeradjust_empty.tcl'

    ###########################################

    for modeFormat in modeFormats:
        for cellPadding in cellPaddings:
            for odbName in odbList:
                db = odb.dbDatabase.create()
                print(odb)
                odb.read_db(db, '%s/%s.odb' % (odbPath, odbName))
                bs = OdbToBookshelf(
                    opendbpy=odb,
                    opendb=db,
                    cellPadding=cellPadding,
                    modeFormat=modeFormat,
                    layerCapacity=layerCapacity)
                bs.WriteBookshelf(
                    '%s_pad%s_%s' %
                    (odbName, cellPadding, modeFormat))