#!/usr/bin/env python
#
# This file is part of the Coriolis Software.
# Copyright (c) UPMC 2014-2015, All Rights Reserved
#
# +-----------------------------------------------------------------+
# |                   C O R I O L I S                               |
# |      C u m u l u s  -  P y t h o n   T o o l s                  |
# |                                                                 |
# |  Author      :                    Jean-Paul CHAPUT              |
# |  E-mail      :       Jean-Paul.Chaput@asim.lip6.fr              |
# | =============================================================== |
# |  Python      :       "./plugins/chip/BlockCorona.py"            |
# +-----------------------------------------------------------------+


import bisect
from   operator  import methodcaller  
import Cfg
from   Hurricane import DbU
from   Hurricane import Point
from   Hurricane import Transformation
from   Hurricane import Box
from   Hurricane import Path
from   Hurricane import Occurrence
from   Hurricane import UpdateSession
from   Hurricane import Net
from   Hurricane import Contact
from   Hurricane import Horizontal
from   Hurricane import Vertical
import CRL
from   CRL import RoutingLayerGauge
from   helpers   import trace
from   helpers   import ErrorMessage
from   helpers   import WarningMessage
import plugins
from   plugins   import StackedVia
import chip.BlockPower


class Rail ( object ):

    def __init__ ( self, side, order, axis ):
        self.side  = side
        self.order = order
        self.axis  = axis
        self.vias  = { }    # Key:pos Element: [pos,railContact,blockContact]
        return

    @property
    def net ( self ): return self.side.getRailNet(self.order)


class HorizontalRail ( Rail ):

    def __init__ ( self, side, order, axis ):
        Rail.__init__( self, side, order, axis )
        return

    def connect ( self, contact ):
        contactBb = contact.getBoundingBox()
        if    contactBb.getXMin() < self.side.innerBb.getXMin() \
           or contactBb.getXMax() > self.side.innerBb.getXMax():
            raise ErrorMessage( 1, [ '%s is outside rail/corona X range,' % str(contact)
                                   , 'power pad is likely to be to far off west or east.'
                                   , '(corona:%s)'                        % str(self.side.innerBb) ] ) 

       #print '  HorizontalRail.connect() net:%s contact:%s' % (self.net.getName(),str(contact))
       #if self.net != contact.getNet(): return False
        if self.vias.has_key(contact.getX()): return False
        
        keys        = self.vias.keys()
        keys.sort()
        insertIndex = bisect.bisect_left( keys, contact.getX() )

        if len(keys) > 0:
            if insertIndex < len(keys):
                insertPosition = keys[ insertIndex ]
                if contactBb.getXMax() >= self.vias[insertPosition][2].getBoundingBox().getXMin():
                   #print 'Reject contact %s intersect NEXT' % str(contact)
                    return False
            if insertIndex > 0:
                if self.vias[keys[insertIndex-1]][2].getBoundingBox().getXMax() >= contactBb.getXMin():
                   #print 'Reject contact %s intersect PREVIOUS' % str(contact)
                    return False

        self.vias[ contact.getX() ] = [ contact.getX()
                                      , StackedVia( self.net
                                                  , self.side.getLayerDepth(self.side.getHLayer())
                                                  , contact.getX()
                                                  , self.axis
                                                  , contact.getWidth()   - DbU.fromLambda(1.0)
                                                  , self.side.hRailWidth - DbU.fromLambda(1.0)
                                                  )
                                      , contact ]
       #print '    ADD contact @ [%d %d]' % (DbU.toLambda(contact.getX()), DbU.toLambda(self.axis))
        self.vias[ contact.getX() ][1].mergeDepth( self.side.getLayerDepth(contact.getLayer()) )
        return True

    def doLayout ( self ):
       #print 'HorizontalRail[%s] @%d' % (self.order,DbU.toLambda(self.axis))

        railVias = [ self.side.corner0(self.order)
                   , self.side.corner1(self.order) ]

        for via in self.vias.values():
          if via[1].getNet() != via[2].getNet(): continue

          via[1].doLayout()
         #print '  Connect:', via[2], via[1].getVia( via[2].getLayer() )
          Vertical.create( via[1].getVia( via[2].getLayer() )
                         , via[2]
                         , via[2].getLayer()
                         , via[2].getX()
                         , via[2].getWidth()
                         )
          railVias.append( via[1].getVia( self.side.getVLayer()) )

        railVias.sort( key=methodcaller('getY') )

        for i in range(1,len(railVias)):
          Horizontal.create( railVias[i-1]
                           , railVias[i]
                           , self.side.getHLayer()
                           , self.axis
                           , self.side.hRailWidth
                           )
        return


class VerticalRail ( Rail ):

    def __init__ ( self, side, order, axis ):
        Rail.__init__( self, side, order, axis )
        return

    def doLayout ( self ):
       #print 'VerticalRail[%s] @%d' % (self.order,DbU.toLambda(self.axis))

        railVias = [ self.side.corner0(self.order)
                   , self.side.corner1(self.order) ]

        for via in self.vias.values():
          if via[1].getNet() != via[2].getNet(): continue

          via[1].doLayout()
          Horizontal.create( via[1].getVia( via[2].getLayer() )
                           , via[2]
                           , via[2].getLayer()
                           , via[2].getY()
                           , via[2].getHeight()
                           )
          railVias.append( via[1].getVia(self.side.getVLayer()) )

        railVias.sort( key=methodcaller('getY') )

        for i in range(1,len(railVias)):
          Vertical.create( railVias[i-1]
                         , railVias[i]
                         , self.side.getVLayer()
                         , self.axis
                         , self.side.vRailWidth
                         )

        routingGauge = CRL.AllianceFramework.get().getRoutingGauge()
        for depth in range(self.side.verticalDepth-2,self.vias.values()[0][1]._bottomDepth,-2):
          blockageLayer = routingGauge.getRoutingLayer(depth).getBlockageLayer()
          pitch         = routingGauge.getLayerPitch(depth)

          for i in range(1,len(railVias)):
            Vertical.create( self.side.blockageNet
                           , blockageLayer
                           , self.axis
                           , self.side.vRailWidth
                           , railVias[i-1].getBoundingBox().getYMax() + pitch
                           , railVias[i  ].getBoundingBox().getYMin() - pitch
                           )

        return

    def connect ( self, contact ):
        contactBb = contact.getBoundingBox()
        if    contactBb.getYMin() < self.side.innerBb.getYMin() \
           or contactBb.getYMax() > self.side.innerBb.getYMax():
            raise ErrorMessage( 1, [ '%s is outside rail/corona Y range' % str(contact)
                                   , 'power pad is likely to be to far off north or south.'
                                   , '(corona:%s)'                       % str(self.side.innerBb) ] ) 

       #if self.net != contact.getNet(): return False
        if self.vias.has_key(contact.getY()): return False

        trace( 550, ',+', '\tVerticalRail.connect() [%s] @%d\n' % (self.order,DbU.toLambda(self.axis)) )
        trace( 550, contact )
        
        keys        = self.vias.keys()
        keys.sort()
        insertIndex = bisect.bisect_left( keys, contact.getY() )
        trace( 550, ',+', '\tkeys:' )
        for key in keys:
            trace( 550, ' %d' % DbU.toLambda(key) )
        trace( 550, '\n' )

        if len(keys) > 0:
            if insertIndex < len(keys):
                insertPosition = keys[ insertIndex ]
                trace( 550, '\tinsertIndex:%d' % insertIndex )
                trace( 550, '\tCheck NEXT contactBb:%s via:%s\n' \
                    % ( contactBb
                      , self.vias[insertPosition][2].getBoundingBox()) )
                if contactBb.getYMax() >= self.vias[insertPosition][2].getBoundingBox().getYMin():
                    trace( 550, ',--', '\tReject %s intersect NEXT\n' % contact )
                    return False
            if insertIndex > 0:
                trace( 550, '\tcheck PREVIOUS contactBb:%s via:%s\n' \
                    % ( contactBb
                      , self.vias[keys[insertIndex-1]][2].getBoundingBox()) )
                if self.vias[keys[insertIndex-1]][2].getBoundingBox().getYMax() >= contactBb.getYMin():
                    trace( 550, ',--', '\tReject %s intersect PREVIOUS\n' % contact )
                    return False

        self.vias[ contact.getY() ] = [ contact.getY()
                                      , StackedVia( self.net
                                                  , self.side.getLayerDepth(self.side.getVLayer())
                                                  , self.axis
                                                  , contact.getY()
                                                  , self.side.vRailWidth - DbU.fromLambda(1.0)
                                                  , contact.getHeight()  - DbU.fromLambda(1.0)
                                                  )
                                      , contact ]
        trace(550, ',--' '\tADD %s\n' % contact )
        self.vias[ contact.getY() ][1].mergeDepth( self.side.getLayerDepth(contact.getLayer()) )
        return True


class Side ( object ):

    def __init__ ( self, corona ):
        self._corona = corona
        return

    @property
    def railsNb         ( self ): return self._corona._railsNb
    @property           
    def innerBb         ( self ): return self._corona._innerBb
    @property           
    def hRailWidth      ( self ): return self._corona._hRailWidth
    @property           
    def hRailSpace      ( self ): return self._corona._hRailSpace
    @property           
    def vRailWidth      ( self ): return self._corona._vRailWidth
    @property           
    def vRailSpace      ( self ): return self._corona._vRailSpace
    @property           
    def corners         ( self ): return self._corona._corners
    @property
    def horizontalDepth ( self ): return self._corona.horizontalDepth
    @property
    def verticalDepth   ( self ): return self._corona.verticalDepth
    @property
    def blockageNet     ( self ): return self._corona.blockageNet

    def getLayerDepth   ( self, metal ): return self._corona.getLayerDepth(metal)
    def getRail         ( self, i ):     return self._rails[i]
    def getRailNet      ( self, i ):     return self._corona.getRailNet(i)
    def getHLayer       ( self ):        return self._corona.getHLayer()
    def getVLayer       ( self ):        return self._corona.getVLayer()

    def getRailAxis ( self, i ):
        raise ErrorMessage( 1, 'Side.getRailAxis(): Must never be called on base class.' )

    def getInnerRail ( self, i ):
        if i >= len(self._rails):
            raise ErrorMessage( 1, 'Side.getInnerRail(): no rail %d (only: %d).' % (i,len(self._rails)) )
        return self._rails[i]

    def getOuterRail ( self, i ):
        if i >= len(self._rails):
            raise ErrorMessage( 1, 'Side.getOuterRail(): no rail %d (only: %d).' % (i,len(self._rails)) )
        return self._rails[-(i+1)]

    def connect ( self, blockSide ):
        for terminal in blockSide.terminals:
          for rail in self._rails:
            rail.connect( terminal[1] )
        return

    def connectPads ( self, padSide ):
        for terminal in padSide._powerContacts:
         #print '  Connect to [-%i] @%d' % (0, DbU.toLambda(self.getOuterRail(0).axis))
          self.getOuterRail( 0 ).connect( terminal )

        halfRails = (len(self._rails)-1)/2
        trace( 550, 'halfRails:%i' % halfRails )
        for terminal in padSide._powerContacts:
          trace( 550, ',+', '\tConnect pad terminal %s\n' % terminal )
          for i in range(halfRails):
            trace( 550, '\tConnect to [-%i] @%d\n' % (i+1, DbU.toLambda(self.getOuterRail(i+1).axis)) )
            self.getOuterRail(i+1).connect( terminal )
          trace( 550, '-' )
        return

    def doLayout ( self ):
        for rail in self._rails: rail.doLayout()
        return


class HorizontalSide ( Side ):

    def __init__ ( self, corona ):
       #print 'HorizontalSide.__init__()'
        Side.__init__( self, corona )

        self._rails = []
        for i in range(self.railsNb):
            self._rails.append( HorizontalRail(self,i,self.getRailAxis(i)) )
           #print '  Rail [%i] @%d' % (i,DbU.toLambda(self._rails[-1].axis))
        return


class SouthSide ( HorizontalSide ):

    def __init__ ( self, corona ):
        HorizontalSide.__init__( self, corona )
        return

    def getRailAxis ( self, i ):
        return self.innerBb.getYMin() -    self.hRailWidth/2 - self.hRailSpace \
                                      - i*(self.hRailWidth   + self.hRailSpace)

    def corner0 ( self, i ): return self.corners[chip.SouthWest][i]
    def corner1 ( self, i ): return self.corners[chip.SouthEast][i]


class NorthSide ( HorizontalSide ):

    def __init__ ( self, corona ):
        HorizontalSide.__init__( self, corona )
        return

    def getRailAxis ( self, i ):
        return self.innerBb.getYMax() +    self.hRailWidth/2 + self.hRailSpace \
                                      + i*(self.hRailWidth   + self.hRailSpace)

    def corner0 ( self, i ): return self.corners[chip.NorthWest ][i]
    def corner1 ( self, i ): return self.corners[chip.NorthEast][i]


class VerticalSide ( Side ):

    def __init__ ( self, corona ):
        Side.__init__( self, corona )

        self._rails = []
        for i in range(self.railsNb):
            self._rails.append( VerticalRail(self,i,self.getRailAxis(i)) )
        return


class WestSide ( VerticalSide ):

    def __init__ ( self, corona ):
        VerticalSide.__init__( self, corona )
        return

    def getRailAxis ( self, i ):
        return self.innerBb.getXMin() -    self.vRailWidth/2 - self.vRailSpace \
                                      - i*(self.vRailWidth   + self.vRailSpace)

    def corner0 ( self, i ): return self.corners[chip.SouthWest][i]
    def corner1 ( self, i ): return self.corners[chip.NorthWest   ][i]


class EastSide ( VerticalSide ):

    def __init__ ( self, corona ):
        VerticalSide.__init__( self, corona )
        return

    def getRailAxis ( self, i ):
        return self.innerBb.getXMax() +    self.vRailWidth/2 + self.vRailSpace \
                                      + i*(self.vRailWidth   + self.vRailSpace)

    def corner0 ( self, i ): return self.corners[chip.SouthEast][i]
    def corner1 ( self, i ): return self.corners[chip.NorthEast   ][i]


class Corona ( object ):

    def __init__ ( self, block ):
       #if not isinstance(block,plugins.chip.BlockPower.Block):
       #    raise ErrorMessage( 1, 'Attempt to create a Corona on a non-Block object.' )

        self._railsNb    = Cfg.getParamInt('chip.block.rails.count').asInt()
        self._hRailWidth = DbU.fromLambda( Cfg.getParamInt('chip.block.rails.hWidth'  ).asInt() )
        self._vRailWidth = DbU.fromLambda( Cfg.getParamInt('chip.block.rails.vWidth'  ).asInt() )
        self._hRailSpace = DbU.fromLambda( Cfg.getParamInt('chip.block.rails.hSpacing').asInt() )
        self._vRailSpace = DbU.fromLambda( Cfg.getParamInt('chip.block.rails.vSpacing').asInt() )

        self._block      = block
        self._innerBb    = self._block.bb
        self._block.path.getTransformation().applyOn( self._innerBb )
        self._innerBb.inflate( self._hRailSpace/2, self._vRailSpace/2 )
        
        if not self.useClockTree: self._railsNb -= 1

        self._southSide  = SouthSide( self )
        self._northSide  = NorthSide( self )
        self._westSide   = WestSide ( self )
        self._eastSide   = EastSide ( self )

        return

    @property
    def useClockTree    ( self ): return self._block.useClockTree
    @property
    def routingGauge    ( self ): return self._block.routingGauge
    @property
    def topLayerDepth   ( self ): return self._block.topLayerDepth
    @property
    def horizontalDepth ( self ): return self._block.horizontalDepth
    @property
    def verticalDepth   ( self ): return self._block.verticalDepth
    @property
    def blockageNet     ( self ): return self._block.blockageNet

    def getLayerDepth ( self, metal ):
        return self.routingGauge.getLayerDepth( metal )

    def getRailNet ( self, i ):
        if self.useClockTree and i == self._railsNb-1: return self._block.cko
        if i % 2: return self._block.vssi
        return self._block.vddi

    def getHLayer ( self ):
        return self.routingGauge.getLayerGauge( self.horizontalDepth ).getLayer()

    def getVLayer ( self ):
        return self.routingGauge.getLayerGauge( self.verticalDepth ).getLayer()

    def connectBlock ( self ):
        for plane in self._block.planes.values():
          for side in plane.sides.values():
            self._southSide.connect( side[chip.South] )
            self._northSide.connect( side[chip.North] )
            self._westSide .connect( side[chip.West ] )
            self._eastSide .connect( side[chip.East ] )
        return

    def connectPads ( self, padsCorona ):
        self._southSide.connectPads( padsCorona.southSide )
        self._northSide.connectPads( padsCorona.northSide )
        self._eastSide.connectPads( padsCorona.eastSide )
        self._westSide.connectPads( padsCorona.westSide )
        return

    def doLayout ( self ):
        self._corners = { chip.SouthWest : []
                        , chip.SouthEast : []
                        , chip.NorthWest : []
                        , chip.NorthEast : []
                        }

        contactDepth = self.horizontalDepth
        if self.horizontalDepth > self.verticalDepth:
            contactDepth = self.verticalDepth

        for i in range(self._railsNb):
            xBL = self._westSide .getRail(i).axis
            yBL = self._southSide.getRail(i).axis
            xTR = self._eastSide .getRail(i).axis
            yTR = self._northSide.getRail(i).axis
            net = self.getRailNet( i )

            self.routingGauge.getContactLayer(contactDepth)
            self._corners[chip.SouthWest].append( 
                Contact.create( net
                              , self.routingGauge.getContactLayer(contactDepth)
                              , xBL, yBL
                              , self._hRailWidth
                              , self._vRailWidth
                              ) )
            self._corners[chip.NorthWest].append( 
                Contact.create( net
                              , self.routingGauge.getContactLayer(contactDepth)
                              , xBL, yTR
                              , self._hRailWidth
                              , self._vRailWidth
                              ) )
            self._corners[chip.SouthEast].append( 
                Contact.create( net
                              , self.routingGauge.getContactLayer(contactDepth)
                              , xTR, yBL
                              , self._hRailWidth
                              , self._vRailWidth
                              ) )
            self._corners[chip.NorthEast].append( 
                Contact.create( net
                              , self.routingGauge.getContactLayer(contactDepth)
                              , xTR, yTR
                              , self._hRailWidth
                              , self._vRailWidth
                              ) )

        self._southSide.doLayout()
        self._northSide.doLayout()
        self._westSide .doLayout()
        self._eastSide .doLayout()
        return
