// -*- C++ -*-
//
// This file is part of the Coriolis Software.
// Copyright (c) UPMC 2015-2018, All Rights Reserved
//
// +-----------------------------------------------------------------+ 
// |                   C O R I O L I S                               |
// |  B o r a  -  A n a l o g   S l i c i n g   T r e e              |
// |                                                                 |
// |  Authors     :                            Eric LAO              |
// |  E-mail      :            Jean-Paul.Chaput@lip6.fr              |
// | =============================================================== |
// |  C++ Module  :  "./HVSlicingNode.cpp"                           |
// +-----------------------------------------------------------------+


#include "hurricane/Error.h"
#include "hurricane/Warning.h"
#include "crlcore/RoutingGauge.h"
#include "bora/HVSetState.h"
#include "bora/HSlicingNode.h"
#include "bora/RHSlicingNode.h"


namespace Bora {

  using namespace std;
  using Hurricane::Error;
  using Hurricane::Warning;


// -------------------------------------------------------------------
// Class  :  "Bora::HSlicingNode".

  
  int  HSlicingNode::_count    = 0;
  int  HSlicingNode::_countAll = 0;


  HSlicingNode::HSlicingNode ( unsigned int type, unsigned int alignment )
    : HVSlicingNode(type,alignment)
  { ++_count; }


  HSlicingNode::~HSlicingNode ()
  { --_count; }


  HSlicingNode* HSlicingNode::create ( unsigned int alignment )
  {
    --_countAll;
    return new HSlicingNode( HorizontalSNode, alignment );
  }


  void  HSlicingNode::createRouting( DbU::Unit space )
  {
    push_back( RHSlicingNode::create( space ) ); 
    resetSlicingTree();
  } 


  void  HSlicingNode::print () const
  {
    cerr << "- Print from Slicing Node - " << endl;
    cerr << "SlicingType: Horizontal Node" << endl; 
    if      (isAlignLeft  ()) cerr << "Alignment  : Left"    << endl;
    else if (isAlignCenter()) cerr << "Alignment  : Middle"  << endl;
    else if (isAlignRight ()) cerr << "Alignment  : Right"   << endl;
    else                      cerr << "Alignment  : Unknown" << endl;
    cerr << "Tolerances : RatioH: " << DbU::getPhysical(_toleranceRatioH,DbU::Micro)
         << ", RatioW: " << DbU::getPhysical(_toleranceRatioW,DbU::Micro)
         << ", BandH: "  << DbU::getPhysical(_toleranceBandH ,DbU::Micro)
         << ", BandW: "  << DbU::getPhysical(_toleranceBandW ,DbU::Micro) << endl; 

    HVSlicingNode::print();
  }


  HSlicingNode* HSlicingNode::clone ( unsigned int tr )
  {
    HSlicingNode* node = HSlicingNode::create( getAlignment() );
    node->setTolerances( getToleranceRatioH()
                       , getToleranceRatioW()
                       , getToleranceBandH ()
                       , getToleranceBandW ()
                       );
    node->setBoxSet    ( getBoxSet()        );
    node->setNodeSets  ( _nodeSets->clone() );
    node->setPreset    ( getPreset()        );
    node->setSet       ( getSet()           );
    node->setPlaced    ( getPlaced()        );
    node->setSymmetries( getSymmetries()    );

    for ( SlicingNode* child : _children ) {
      if (tr == MY) node->push_front( child->clone(tr) );
      else          node->push_back ( child->clone(tr) );
    }
    return node;
  }


  void HSlicingNode::place ( DbU::Unit x, DbU::Unit y )
  {
    if (recursiveCheckSet()) { 
      if (not _slicingRouting.empty()) {
        destroySlicingRouting();
        resetSlicingRouting();
      }
      _place(x,y); 

      if (_slicingRouting.empty())
        createSlicingRouting();

      updateCellAbutmentBox();
    } else
      cerr << Warning( "HSlicingNode::place(): The SlicingTree is not completely set." ) << endl;
  }


  void  HSlicingNode::replace ( DbU::Unit x, DbU::Unit y )
  {
  // WARNING: This will change GCell edges.

    if (recursiveCheckSet()) { 
      _place( x, y, true ); 
      updateCellAbutmentBox();
      updateGCellPosition  ();
      updateGContacts      ();
    } else {
      cerr << Warning( "HSlicingNode::replace(): The SlicingTree is not completely set." ) << endl;
    }
  }


  void HSlicingNode::_place ( DbU::Unit x, DbU::Unit y, bool replace )
  {
    cdebug_log(536,1) << "HSlicingNode::_place(DbU::Unit,DbU::Unit,bool)" << endl;

    vector<RHVSlicingNode*>::iterator itspace = _slicingRouting.begin();
    DbU::Unit xref = x;
    DbU::Unit yref = y;
  
    if (isRoutingEstimated()) {
      (*itspace)->_place( xref, yref, replace );
      yref += (*itspace)->getHeight();
      itspace++;
    }

    for ( SlicingNode* child : _children ) {
      if ( (child->isHorizontal()) or (child->isVertical()) ) { 
        if (child->isAlignLeft()) {
          child->setX( xref );
          child->setY( yref );
        } else if (child->isAlignCenter()) {
          child->setX( xref + (getWidth()/2) - (child->getWidth()/2) );
          child->setY( yref );
        } else if (child->isAlignRight()) {
          child->setX( xref + getWidth() - child->getWidth() );
          child->setY( yref );
        }
      }
      
      if      (child->isAlignLeft()  ) child->_place( xref                                         , yref, replace);
      else if (child->isAlignCenter()) child->_place( xref + (getWidth()/2) - (child->getWidth()/2), yref, replace);
      else if (child->isAlignRight() ) child->_place( xref +  getWidth()    -  child->getWidth()   , yref, replace);
      else if (child->isRouting()    ) child->_place( xref                                         , yref, replace);
      else { 
        cerr << Warning( "HSlicingNode::_place(): Unknown Alignment in SlicingTree." ) << endl; 
        child->print();
      }
      
      xref  = x;
      yref += child->getHeight();
      if (isRoutingEstimated()) {
        (*itspace)->_place( xref, yref, replace );
        yref += (*itspace)->getHeight();
        itspace++;
      }
    }

    setPlaced( Placed );

    cdebug_tabw(536,-1);
  }


  void  HSlicingNode::updateGlobalSize ()
  {
    cdebug_log(535,1) << "HSlicingNode::updateGlobalsize() - " << this << endl;

    for ( SlicingNode* child : _children ) {
      cdebug_log(535,0) << "child: " << child << endl;
      child->updateGlobalSize(); 
    }

    if (not getMaster()) {
      if (getNbChild() == 1) {
        _nodeSets->clear();
        NodeSets* node = _children[0]->getNodeSets();

        for ( BoxSet* bs : node->getBoxSets() ) {
          vector<BoxSet*> bss;

          bss.push_back( bs );
          _nodeSets->push_back( bss, bs->getHeight(), bs->getWidth(), HorizontalSNode );
        }
      } else if ( not hasEmptyChildrenNodeSets() and _nodeSets->empty() ) {
        HSetState state = HSetState( this );
        while ( not state.end() ) state.next();

        _nodeSets = state.getNodeSets();
      }
      if (_nodeSets->empty())
        cerr << Warning( "HSlicingNode::updateGlobalSize(): No solution has been found, try to set larger tolerances." ) << endl;
    } else {
      _nodeSets = _master->getNodeSets();
    }

    cdebug_log(535,0) << "Found " << _nodeSets->size() << " choices" << endl;
    cdebug_tabw(535,-1);
  }


  void  HSlicingNode::preDestroy ()
  {
    HVSlicingNode::preDestroy();
  }


  void  HSlicingNode::destroy ()
  {
    HSlicingNode::preDestroy();
    delete this;
  }


  void  HSlicingNode::preRecursiveDestroy ()
  {
    HVSlicingNode::preRecursiveDestroy();
  }


  void  HSlicingNode::recursiveDestroy ()
  {
    HSlicingNode::preRecursiveDestroy();
    delete this;
  }


  void  HSlicingNode::createSlicingRouting ()
  {
    if (not _boxSet) {
      cerr << Warning( "HSlicingNode::createSlicingRouting(): SlicingTree needs to be placed first." ) << endl; 
      return;
    }
    
    size_t    childrenCount = getNbChild();
    size_t    ichild        = 0;
    DbU::Unit x             = getX();
    DbU::Unit y             = getY();
    DbU::Unit heightValue   = 0;

    if (_parent) {
      if (_parent->getType() == VerticalSNode) {
        if ( (getAlignment() == AlignBottom) or (getAlignment() == AlignTop) ) {
          heightValue = _parent->getHeight() - getHeight(); 
        } else if (getAlignment() == AlignCenter) {
          heightValue = (_parent->getHeight() - getHeight()) / 2; 
        }
      }
    }
      
    DbU::Unit hpitch = _rg->getHorizontalPitch();
    if (heightValue % hpitch)
      cerr << Warning( "HSlicingNode::createSlicingRouting(): On %s, height is not pitched (%s, pitch:%s)."
                     , getString(this).c_str()
                     , DbU::getValueString(heightValue).c_str()
                     , DbU::getValueString(hpitch).c_str()
                     ) << endl;
      
    for ( size_t inode=0; inode<childrenCount+1; ++inode ) {
      RHSlicingNode* node = NULL;
      if (inode == 0) {
        if (  (getAlignment() == AlignTop   )
           or (getAlignment() == AlignCenter) ) node = RHSlicingNode::create( heightValue );
        else                                    node = RHSlicingNode::create();
      } else if (inode == childrenCount) {
        if (  (getAlignment() == AlignBottom)
           or (getAlignment() == AlignCenter) ) node = RHSlicingNode::create( heightValue );
        else                                    node = RHSlicingNode::create();
      } else
        node = RHSlicingNode::create();

      node->setParent( this );
        
      if (inode == 0) {
        if (getAlignment() == AlignBottom)        node->place( x, y );
        else if (  (getAlignment() == AlignCenter)
                or (getAlignment() == AlignTop) ) node->place( x, y-heightValue );
      } else
        node->place( x, y );

      if (_master) node->setMaster( _master->getSlicingRouting(inode) );
      _slicingRouting.push_back( node );

      if (inode < childrenCount) y += getChild( ichild++ )->getHeight();
    }
    
    if (_master) {
      if (isHSymmetry()) {
        for ( size_t i=0; i<_slicingRouting.size(); ++i )
          getSlicingRouting( i )->setMaster( _master->getSlicingRouting( _slicingRouting.size()-1-i ) );
      } else {
        for ( size_t i=0; i<_slicingRouting.size(); ++i )
          getSlicingRouting( i )->setMaster( _master->getSlicingRouting( i ) );
      }
    } else if ( not _symmetries.empty() and isAlignCenter() ) {
      for ( size_t i=0; i<(_slicingRouting.size()/2); ++i ) {
        getSlicingRouting( _slicingRouting.size()-1-i )->setMaster( getSlicingRouting(i) );
      }
    }
    
    for ( SlicingNode* child : _children ) {
      if ( child->isHorizontal() or child->isVertical() ) child->createSlicingRouting();
    }   
    
    setRoutingCreated( RoutingCreated ); 
  }


  DbU::Unit  HSlicingNode::getHeight () const 
  {
    DbU::Unit hpitch = _rg->getHorizontalPitch();
    DbU::Unit height = 0;

    if (isRoutingEstimated()){
      for ( SlicingNode*    child : _children       ) height += child->getHeight();
      for ( RHVSlicingNode* node  : _slicingRouting ) height += node ->getHeight();
    } else {
      if (_boxSet != NULL) height = _boxSet->getHeight();
    }

    if (height % hpitch)
      cerr << Warning( "HSlicingNode::getHeight(): On %s, height is not pitched (%s, pitch:%s)."
                     , getString(this).c_str()
                     , DbU::getValueString(height).c_str()
                     , DbU::getValueString(hpitch).c_str()
                     ) << endl;
    return height;
  }


  DbU::Unit  HSlicingNode::getWidth () const 
  {
    cdebug_log(536,0) << "HSlicingNode::getWidth()" << endl;

    DbU::Unit vpitch = _rg->getVerticalPitch();
    DbU::Unit width  = 0;

    if (isRoutingEstimated()) {
      SlicingNode* m = NULL;
      for ( SlicingNode* node : _children ) {
        if ( (node->getType() != RoutingSNode) and (node->getWidth() > width) ) {
          width = node->getWidth();
          m = node;
        } 
      }
      if (m->isDevice()) width += vpitch*2;
    } else {
      if (_boxSet) width = _boxSet->getWidth();
    }

    if (width % vpitch)
      cerr << Warning( "HSlicingNode::getWidth(): On %s, width is not pitched (%s, pitch:%s)."
                     , getString(this).c_str()
                     , DbU::getValueString(width).c_str()
                     , DbU::getValueString(vpitch).c_str()
                     ) << endl;
    return width;
  }


  void  HSlicingNode::setGCell ( Anabatic::GCell* gcell )
  {
    cdebug_log(535,1) << "HSlicingNode::setGCell(), start Y: "
                      << _slicingRouting[0]->getY() << ", GCell:" << gcell << endl; 

    Anabatic::GCell* childGCell  = gcell;
    Anabatic::GCell* remainGCell = gcell;
    DbU::Unit        y           = _slicingRouting[0]->getY();
    
    for ( size_t ichild=0 ; ichild<_children.size() ; ++ichild ) {
    // Setting up the GCell for the channel *before* the current child.
      cdebug_log(535,0) << "node[" << ichild << "] height:"
                        << DbU::getValueString(_slicingRouting[ ichild ]->getHeight()) << endl;

      y += _slicingRouting[ ichild ]->getHeight();
      remainGCell = childGCell->hcut( y );
      _slicingRouting[ ichild ]->setGCell( childGCell );
      childGCell = remainGCell;

    // Setting up the GCell for the current child.
      cdebug_log(535,0) << "children[" << ichild << "] height:"
                        << DbU::getValueString(_children[ ichild ]->getHeight()) << endl;

      y += _children[ ichild ]->getHeight();
      remainGCell = childGCell->hcut( y );
      _children[ ichild ]->setGCell( childGCell );
      childGCell = remainGCell;
    }

  // Setting up the GCell for the channel *after* the last child.
    _slicingRouting.back()->setGCell( childGCell );
    
    cdebug_tabw(535,-1);
  }


  void  HSlicingNode::adjustBorderChannels ()
  {
    if (_parent) {
      if (_parent->getHeight() > getHeight()) {
        DbU::Unit space = _parent->getHeight() - getHeight();

        if (getAlignment() == AlignTop) {
          RHVSlicingNode* channel = _slicingRouting.front();
          channel->setHeight( channel->getHeight() + space );
        } else if (getAlignment() == AlignCenter) {
          RHVSlicingNode* firstChannel = _slicingRouting.front();
          RHVSlicingNode*  lastChannel = _slicingRouting.back ();
          firstChannel->setHeight( firstChannel->getHeight() + space/2 );
          lastChannel ->setHeight( lastChannel ->getHeight() + space/2 );
        } else if (getAlignment() == AlignBottom) {
          RHVSlicingNode* channel = _slicingRouting.back();
          channel->setHeight( channel->getHeight() + space );
        }
      }
    }

    for ( SlicingNode* child : _children ) child->adjustBorderChannels();
  }


  string  HSlicingNode::_getString () const
  {
    string s = Super::_getString();
    return s;
  }


  string  HSlicingNode::_getTypeName () const
  { return "HSlicingNode"; }


}  // Bora namespace.