#!/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/BlockPower.py"             |
# +-----------------------------------------------------------------+


import sys
from   Hurricane import DbU
from   Hurricane import Point
from   Hurricane import Transformation
from   Hurricane import Box
from   Hurricane import Interval
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
from   Hurricane import Query
import CRL
import helpers
from   helpers   import trace
from   helpers   import ErrorMessage
from   helpers   import WarningMessage
import chip.Configuration


class Side ( object ):

  def __init__ ( self, block, side, net, metal ):
    self.block      = block
    self.side       = side
    self.net        = net
    self.metal      = metal
    self.deltaWidth = metal.getExtentionWidth()*2
    self.terminals  = [ ]
    return

  def addTerminal ( self, position, width ):
    toMerge = Interval( position-width/2, position+width/2 )

    length = len(self.terminals)
    imerge = length
    ichunk = 0

    while ichunk < length:
      if imerge == length:
        if toMerge.getVMax() < self.terminals[ichunk][0].getVMin():
          self.terminals.insert( ichunk, [ toMerge, None ] )
          imerge  = ichunk
          length += 1
          break

        if toMerge.intersect(self.terminals[ichunk][0]):
          imerge = ichunk
          self.terminals[ichunk][0].merge( toMerge )
      else:
        if toMerge.getVMax() >= self.terminals[ichunk][0].getVMin():
          self.terminals[imerge][0].merge( self.terminals[ichunk][0] )
          del self.terminals[ichunk]
          length -= 1
          continue
        else:
          break
      ichunk += 1

    if ichunk == length:
      self.terminals.append( [ toMerge, None ] )

    return

  def doLayout ( self ):
    if self.side == chip.West:
      width = 0
      x     = self.block.bb.getXMin()
    elif self.side == chip.East:
      width = 0
      x     = self.block.bb.getXMax()
    elif self.side == chip.South:
      height = 0
      y      = self.block.bb.getYMin()
    elif self.side == chip.North:
      height = 0
      y      = self.block.bb.getYMax()

    minWidth = DbU.fromLambda( 6.0 )
    for terminal in self.terminals:
      if self.side == chip.West or self.side == chip.East:
        center = Point( x, terminal[0].getCenter() )
        height = terminal[0].getSize() - self.deltaWidth
        if height < minWidth: height = minWidth
      elif self.side == chip.North or self.side == chip.South:
        center = Point( terminal[0].getCenter(), y )
        width = terminal[0].getSize() - self.deltaWidth
        if width < minWidth: width = minWidth

      self.block.path.getTransformation().applyOn( center )

      contact = Contact.create( self.net, self.metal, center.getX(), center.getY(), width, height )
      terminal[ 1 ] = contact
    return


class Plane ( object ):

  Horizontal = 0001
  Vertical   = 0002

  def __init__ ( self, block, metal ):
    self.block = block
    self.metal = metal
    self.sides = { }
    return

  def addTerminal ( self, net, direction, bb ):
    if not self.sides.has_key(net):
      self.sides[ net ] = [ Side(self.block,chip.North,net,self.metal)
                          , Side(self.block,chip.South,net,self.metal)
                          , Side(self.block,chip.East ,net,self.metal)
                          , Side(self.block,chip.West ,net,self.metal)
                          ]
    sides = self.sides[ net ]

    if direction == Plane.Horizontal:
      if bb.getXMin() <= self.block.bb.getXMin():
        sides[chip.West].addTerminal( bb.getCenter().getY(), bb.getHeight() )
      if bb.getXMax() >= self.block.bb.getXMax():
        sides[chip.East].addTerminal( bb.getCenter().getY(), bb.getHeight() )

    if direction == Plane.Vertical:
      if bb.getYMin() <= self.block.bb.getYMin():
        sides[chip.South].addTerminal( bb.getCenter().getX(), bb.getWidth() )
      if bb.getYMax() >= self.block.bb.getYMax():
        sides[chip.North].addTerminal( bb.getCenter().getX(), bb.getWidth() )
    return

  def doLayout ( self ):
    for sidesOfNet in self.sides.values():
      for side in sidesOfNet:
        side.doLayout()
    return
    


class GoCb ( object ):

  def __init__ ( self, block ):
    self.block = block
    return

  def __call__ ( self, query, go ):
    direction = None
    if isinstance(go,Horizontal): direction = Plane.Horizontal
    if isinstance(go,Vertical):   direction = Plane.Vertical
    if not direction: return

    rootNet = None
    if go.getNet().getType() == Net.Type.POWER:  rootNet = self.block.vddi
    if go.getNet().getType() == Net.Type.GROUND: rootNet = self.block.vssi
    if not rootNet: return

    if self.block.activePlane:
      bb = go.getBoundingBox( self.block.activePlane.metal.getBasicLayer() )
      query.getPath().getTransformation().applyOn( bb )
      self.block.activePlane.addTerminal( rootNet, direction, bb )
    else:
      print WarningMessage( 'BlockPower.GoCb() callback called without an active plane.' )
    return


class Block ( chip.Configuration.ChipConfWrapper ):

  def __init__ ( self, conf ):
    chip.Configuration.ChipConfWrapper.__init__( self, conf.gaugeConf, conf.chipConf )
    self.path        = Path( self.cores[0] )
    self.block       = self.path.getTailInstance().getMasterCell()
    self.bb          = self.block.getAbutmentBox()
    self.planes      = { }
    self.activePlane = None

    routingGauge = CRL.AllianceFramework.get().getRoutingGauge()
    for layerGauge in routingGauge.getLayerGauges():
      self.planes[ layerGauge.getLayer().getName() ] = Plane( self, layerGauge.getLayer() )
    
    return


  def connectPower ( self ):
    if not self.vddi or not self.vssi:
      print ErrorMessage( 1, 'Cannot build block power terminals as vddi and/or vss are not known.' )
      return

    goCb = GoCb( self )
    query = Query()
    query.setGoCallback( goCb )
    query.setCell( self.block )
    query.setArea( self.bb )
    query.setFilter( Query.DoComponents|Query.DoTerminalCells )

    routingGauge = CRL.AllianceFramework.get().getRoutingGauge()
    for layerGauge in routingGauge.getLayerGauges():
      self.activePlane = self.planes[ layerGauge.getLayer().getName() ]
      query.setBasicLayer( layerGauge.getLayer().getBasicLayer() )
      query.doQuery()
    self.activePlane = None
    return


  def connectClock ( self ):
    if not self.useClockTree:
      print WarningMessage( "Clock tree generation has been disabled ('chip.clockTree':False)." )
      return

    if not self.cko:
      print ErrorMessage( 1, 'Cannot build clock terminal as ck is not known.' )
      return

    blockCk = None
    for plug in self.path.getTailInstance().getPlugs():
      if plug.getNet() == self.cko:
        blockCk = plug.getMasterNet()

    if not blockCk:
      print ErrorMessage( 1, 'Block <%s> has no net connected to the clock <%s>.'
                             % (self.path.getTailInstance().getName(),self.ck.getName()) )
      return

    htPlugs = []
    ffPlugs = []
    for plug in blockCk.getPlugs():
      if plug.getInstance().getName() == 'ck_htree':
        htPlugs.append( plug )
      else:
        if plug.getInstance().getMasterCell().isTerminal():
          ffPlugs.append( plug )

    if len(ffPlugs) > 0:
      message = 'Clock <%s> of block <%s> is not organized as a H-Tree.' \
                % (blockCk.getName(),self.path.getTailInstance().getName())
      print ErrorMessage( 1, message )
      return

    if len(htPlugs) > 1:
      message = 'Block <%s> has not exactly one H-Tree connecteds to the clock <%s>:' \
                % (self.path.getTailInstance().getName(),blockCk.getName())
      for plug in htPlugs:
        message += '\n        - %s' % plug
      print ErrorMessage( 1, message )
      return

    UpdateSession.open()
    bufferRp = self.rpAccessByOccurrence( Occurrence(htPlugs[0], self.path), self.cko )
    blockAb  = self.block.getAbutmentBox()
    self.path.getTransformation().applyOn( blockAb )
    layerGauge = self.routingGauge.getLayerGauge(self.verticalDepth)
    
    contact  = Contact.create( self.cko
                             , self.routingGauge.getRoutingLayer(self.verticalDepth)
                             , bufferRp.getX()
                             , blockAb.getYMax() 
                             , layerGauge.getViaWidth()
                             , layerGauge.getViaWidth()
                             )
    segment = self.createVertical( bufferRp, contact, bufferRp.getX() )

    self.activePlane = self.planes[ layerGauge.getLayer().getName() ]
    bb = segment.getBoundingBox( self.activePlane.metal.getBasicLayer() )
    self.path.getTransformation().getInvert().applyOn( bb )
    self.activePlane.addTerminal( self.cko, Plane.Vertical, bb )
    
    UpdateSession.close()

    return

  def doLayout ( self ):
    UpdateSession.open()
    for plane in self.planes.values():
      plane.doLayout()
    UpdateSession.close()
    return
