// -*- mode: C++; explicit-buffer-name: "GCell.cpp<anabatic>" -*-
//
// This file is part of the Coriolis Software.
// Copyright (c) UPMC 2016-2016, All Rights Reserved
//
// +-----------------------------------------------------------------+
// |                   C O R I O L I S                               |
// |     A n a b a t i c  -  Global Routing Toolbox                  |
// |                                                                 |
// |  Author      :                    Jean-Paul CHAPUT              |
// |  E-mail      :            Jean-Paul.Chaput@lip6.fr              |
// | =============================================================== |
// |  C++ Module  :  "./GCell.cpp"                                   |
// +-----------------------------------------------------------------+


#include <iostream>
#include "hurricane/Contact.h"
#include "hurricane/UpdateSession.h"
#include "anabatic/GCell.h"
#include "anabatic/AnabaticEngine.h"


namespace Anabatic {

  using std::cerr;
  using std::endl;
  using Hurricane::Error;
  using Hurricane::UpdateSession;


  Name  GCell::_extensionName = "Anabatic::GCell";


  GCell::GCell ( AnabaticEngine* anabatic, DbU::Unit xmin, DbU::Unit ymin )
    : Super(anabatic->getCell())
    , _observable()
    , _anabatic  (anabatic)
    , _flags     (Flags::NoFlags)
    , _westEdges ()
    , _eastEdges ()
    , _southEdges()
    , _northEdges()
    , _xmin      (xmin)
    , _ymin      (ymin)
    , _contacts  ()
  { }


  void  GCell::_postCreate ()
  {
    Super::_postCreate();
    _anabatic->_add( this );
  }


  GCell* GCell::create ( AnabaticEngine* anabatic )
  {
    if (not anabatic)            throw Error( "GCell::create(): NULL anabatic argument." );
    if (not anabatic->getCell()) throw Error( "GCell::create(): AnabaticEngine has no Cell loaded." );

    GCell* gcell = new GCell ( anabatic
                             , anabatic->getCell()->getAbutmentBox().getXMin()
                             , anabatic->getCell()->getAbutmentBox().getYMin() );
    gcell->_postCreate();
    gcell->_revalidate();
    return gcell;
  }


  GCell* GCell::_create ( DbU::Unit xmin, DbU::Unit ymin )
  {
    GCell* gcell = new GCell ( getAnabatic(), xmin, ymin );
    gcell->_postCreate();
    return gcell;
  }


  GCell::~GCell ()
  { }


  void  GCell::_destroyEdges ()
  {
    while (not  _westEdges.empty()) (* _westEdges.rbegin())->destroy();
    while (not  _eastEdges.empty()) (* _eastEdges.rbegin())->destroy();
    while (not _southEdges.empty()) (*_southEdges.rbegin())->destroy();
    while (not _northEdges.empty()) (*_northEdges.rbegin())->destroy();
  }


  void  GCell::_preDestroy ()
  {
    _destroyEdges();
    _anabatic->_remove( this );
    Super::_preDestroy();
  }


  void  GCell::_remove ( Edge* edge, Flags side )
  {
    if (side.contains(Flags::WestSide )) erase_element(  _westEdges, edge );
    if (side.contains(Flags::EastSide )) erase_element(  _eastEdges, edge );
    if (side.contains(Flags::SouthSide)) erase_element( _southEdges, edge );
    if (side.contains(Flags::NorthSide)) erase_element( _northEdges, edge );
  }


  void  GCell::_add ( Edge* edge, Flags side )
  {
    cdebug.log(110,1) << "GCell::_add(side): side:" << side << " " << edge << endl;
    if (side.contains(Flags::WestSide)) {
      cdebug.log(110) << "Adding to West side of " << this << endl;
      for ( auto iedge=_westEdges.begin() ; iedge != _westEdges.end() ; ++iedge )
        if ((*iedge)->getAxisMin() > edge->getAxisMin()) {
          _westEdges.insert( iedge, edge );
          cdebug.tabw(110,-1);
          return;
        }
      _westEdges.push_back( edge );
    }

    if (side.contains(Flags::EastSide)) {
      cdebug.log(110) << "Adding to East side of " << this << endl;
      for ( auto iedge=_eastEdges.begin() ; iedge != _eastEdges.end() ; ++iedge )
        if ((*iedge)->getAxisMin() > edge->getAxisMin()) {
          _eastEdges.insert( iedge, edge );
          cdebug.tabw(110,-1);
          return;
        }
      _eastEdges.push_back( edge );
    }

    if (side.contains(Flags::SouthSide)) {
      cdebug.log(110) << "Adding to South side of " << this << endl;
      for ( auto iedge=_southEdges.begin() ; iedge != _southEdges.end() ; ++iedge )
        cdebug.log(110) << "| @" << DbU::getValueString((*iedge)->getAxisMin()) << " " << *iedge << endl;

      for ( auto iedge=_southEdges.begin() ; iedge != _southEdges.end() ; ++iedge )
        if ((*iedge)->getAxisMin() > edge->getAxisMin()) {
          cdebug.log(110) << "Insert *before* " << *iedge << endl;

          _southEdges.insert( iedge, edge );
          for ( auto iedge2=_southEdges.begin() ; iedge2 != _southEdges.end() ; ++iedge2 )
            cdebug.log(110) << "| @" <<  DbU::getValueString((*iedge2)->getAxisMin()) << " " << *iedge2 << endl;
          cdebug.tabw(110,-1);
          return;
        }
      _southEdges.push_back( edge );
    }

    if (side.contains(Flags::NorthSide)) {
      cdebug.log(110) << "Adding to North side of " << this << endl;
      for ( auto iedge=_northEdges.begin() ; iedge != _northEdges.end() ; ++iedge )
        if ((*iedge)->getAxisMin() > edge->getAxisMin()) {
          _northEdges.insert( iedge, edge );
          cdebug.tabw(110,-1);
          return;
        }
      _northEdges.push_back( edge );
    }

    cdebug.tabw(110,-1);
  }


  GCell* GCell::getWest  ( DbU::Unit y ) const
  {
    for ( Edge* edge : _westEdges ) {
      GCell* side = edge->getOpposite(this);
      if (y < side->getYMax()) return side;
    }
    return NULL;
  }


  GCell* GCell::getEast  ( DbU::Unit y ) const
  {
    for ( Edge* edge : _eastEdges ) {
      GCell* side = edge->getOpposite(this);
      cerr << "east @Y: " << DbU::getValueString(y) << " " << side << endl;
      if (y < side->getYMax()) return side;
    }
    return NULL;
  }


  GCell* GCell::getSouth ( DbU::Unit x ) const
  {
    for ( Edge* edge : _southEdges ) {
      GCell* side = edge->getOpposite(this);
      if (x < side->getXMax()) return side;
    }
    return NULL;
  }


  GCell* GCell::getNorth ( DbU::Unit x ) const
  {
    for ( Edge* edge : _northEdges ) {
      GCell* side = edge->getOpposite(this);
      if (x < side->getXMax()) return side;
    }
    return NULL;
  }


  GCell* GCell::getUnder ( DbU::Unit x, DbU::Unit y ) const
  {
    const GCell* current = this;

    while ( current ) {
      if (not current->isFlat() and current->getBoundingBox().contains(x,y)) break;

      if (x >= current->getXMax()) { current = current->getEast (); continue; }
      if (y >= current->getYMax()) { current = current->getNorth(); continue; }

      cerr << Error( "GCell::getUnder(): No GCell under (%s,%s), this must *never* happen."
                   , DbU::getValueString(x).c_str()
                   , DbU::getValueString(y).c_str()
                   ) << endl;
      current = NULL; break;
    }
    
    return const_cast<GCell*>( current );
  }


  Box  GCell::getBorder ( const GCell* s, const GCell* t )
  {
    Flags flags = Flags::NoFlags;
    flags |= (s->getXMax() == t->getXMin()) ? Flags::EastSide  : 0;
    flags |= (t->getXMax() == s->getXMin()) ? Flags::WestSide  : 0;
    flags |= (s->getYMax() == t->getYMin()) ? Flags::NorthSide : 0;
    flags |= (t->getYMax() == s->getYMin()) ? Flags::SouthSide : 0;

    if (flags & Flags::Vertical) {
      if (flags & Flags::Horizontal) return Box();
      if (flags & Flags::WestSide)
        return Box( s->getXMin(), std::max( s->getYMin(), t->getYMin() )
                  , s->getXMin(), std::min( s->getYMax(), t->getYMax() ) );
      else
        return Box( t->getXMin(), std::max( s->getYMin(), t->getYMin() )
                  , t->getXMin(), std::min( s->getYMax(), t->getYMax() ) );
    }

    if (flags & Flags::Horizontal) {
      if (flags & Flags::Vertical) return Box();
      if (flags & Flags::NorthSide)
        return Box( std::max( s->getXMin(), t->getXMin() ), t->getYMin()
                  , std::min( s->getXMax(), t->getXMax() ), t->getYMin() );
      else
        return Box( std::max( s->getXMin(), t->getXMin() ), s->getYMin()
                  , std::min( s->getXMax(), t->getXMax() ), s->getYMin() );
    }

    return Box();
  }


  GCell*  GCell::vcut ( DbU::Unit x )
  {
    cdebug.log(119,1) << "GCell::vcut() @x:" << DbU::getValueString(x) << " " << this << endl;

    if ( (x < getXMin()) or (x > getXMax()) )
      throw Error( "GCell::vcut(): Vertical cut axis at %s is outside GCell box,\n"
                   "        in %s."
                 , DbU::getValueString(x).c_str()
                 , getString(this).c_str()
                 );

    GCell* chunk = _create( x, getYMin() );
    cdebug.log(119) << "New chunk:" << chunk << endl;

    _moveEdges( chunk, 0, Flags::EastSide );
    Edge::create( this, chunk, Flags::Horizontal );

    if (not _southEdges.empty()) {
      cdebug.log(110) << "Split/create south edges." << endl;

      size_t iedge = 0;
      for ( ; (iedge < _southEdges.size()) ; ++iedge ) {
        cdebug.log(110) << "[" << iedge << "] xmax of:"
                        << _southEdges[iedge]->getOpposite(this)
                        << " " << _southEdges[iedge] << endl;
        if (x <= _southEdges[iedge]->getOpposite(this)->getXMax()) break;
      }

      if (      (x <  _southEdges[iedge]->getOpposite(this)->getXMax())
         or (   (x == _southEdges[iedge]->getOpposite(this)->getXMax())
            and (chunk->getXMax() == getXMax())) )
        Edge::create( _southEdges[iedge]->getOpposite(this), chunk, Flags::Vertical );

      _moveEdges( chunk, iedge+1, Flags::SouthSide );
    }

    if (not _northEdges.empty()) {
      cdebug.log(110) << "Split/create north edges." << endl;

      size_t iedge = 0;
      for ( ; (iedge < _northEdges.size()) ; ++iedge )
        if (x <= _northEdges[iedge]->getOpposite(this)->getXMax()) break;

      if (      (x <  _northEdges[iedge]->getOpposite(this)->getXMax())
         or (   (x == _northEdges[iedge]->getOpposite(this)->getXMax())
            and (chunk->getXMax() == getXMax())) )
        Edge::create( chunk, _northEdges[iedge]->getOpposite(this), Flags::Vertical );

      _moveEdges( chunk, iedge+1, Flags::NorthSide );
    }

    _revalidate();
    chunk->_revalidate();

    cdebug.tabw(119,-1);

    return chunk;
  }


  GCell* GCell::hcut ( DbU::Unit y )
  {
    cdebug.log(119,1) << "GCell::hcut() @y:" << DbU::getValueString(y) << " " << this << endl;

    if ( (y < getYMin()) or (y > getYMax()) )
      throw Error( "GCell::hcut(): Horizontal cut axis at %s is outside GCell box,\n"
                   "        in %s."
                 , DbU::getValueString(y).c_str()
                 , getString(this).c_str()
                 );

    GCell* chunk = _create( getXMin(), y );
    cdebug.log(119) << "New chunk:" << chunk << endl;

    _moveEdges( chunk, 0, Flags::NorthSide );
    Edge::create( this, chunk, Flags::Vertical );

    if (not _westEdges.empty()) {
      size_t iedge = 0;
      for ( ; (iedge < _westEdges.size()) ; ++iedge )
        if (y <= _westEdges[iedge]->getOpposite(this)->getYMax()) break;

      if (      (y <  _westEdges[iedge]->getOpposite(this)->getYMax())
         or (   (y == _westEdges[iedge]->getOpposite(this)->getYMax())
            and (chunk->getYMax() == getYMax())) )
        Edge::create( _westEdges[iedge]->getOpposite(this), chunk, Flags::Horizontal );

      _moveEdges( chunk, iedge+1, Flags::WestSide );
    }

    if (not _eastEdges.empty()) {
      size_t iedge = 0;
      for ( ; (iedge < _eastEdges.size()) ; ++iedge )
        if (y <= _eastEdges[iedge]->getOpposite(this)->getYMax()) break;

      if (      (y <  _eastEdges[iedge]->getOpposite(this)->getYMax())
         or (   (y == _eastEdges[iedge]->getOpposite(this)->getYMax())
            and (chunk->getYMax() == getYMax())) )
        Edge::create( chunk, _eastEdges[iedge]->getOpposite(this), Flags::Horizontal );

      _moveEdges( chunk, iedge+1, Flags::EastSide );
    }

    _revalidate();
    chunk->_revalidate();

    cdebug.tabw(119,-1);

    return chunk;
  }


  bool  GCell::doGrid ()
  {
    DbU::Unit  side = getAnabatic()->getConfiguration()->getSliceHeight();

    Interval hspan = getSide( Flags::Horizontal );
    Interval vspan = getSide( Flags::Vertical );

    if (hspan.getSize() < 3*side) {
      cerr << Error( "GCell::doGrid(): GCell is too narrow (dx:%s) to build a grid.\n"
                     "        (%s)"
                   , DbU::getValueString(hspan.getSize()).c_str()
                   , getString(this).c_str()
                   ) << endl;
      return false;
    }

    if (vspan.getSize() < 3*side) {
      cerr << Error( "GCell::doGrid(): GCell is too narrow (dy:%s) to build a grid.\n"
                     "        (%s)"
                   , DbU::getValueString(vspan.getSize()).c_str()
                   , getString(this).c_str()
                   ) << endl;
      return false;
    }

  //UpdateSession::open();

    GCell*    row    = this;
    GCell*    column = NULL;
    DbU::Unit ycut   = vspan.getVMin()+side;
    for ( ; ycut < vspan.getVMax() ; ycut += side ) {
      column = row;
      row    = row->hcut( ycut );

      for ( DbU::Unit xcut = hspan.getVMin()+side ; xcut < hspan.getVMax() ; xcut += side ) {
        column = column->vcut( xcut );
      }
    }

    column = row;
    for ( DbU::Unit xcut = hspan.getVMin()+side ; xcut < hspan.getVMax() ; xcut += side ) {
      column = column->vcut( xcut );
    }

  //UpdateSession::close();

    return true;
  }


  void  GCell::_revalidate ()
  {
    cdebug.log(110,1) << "GCell::revalidate() " << this << endl;
    cdebug.log(110,1) << "West side."  << endl; for ( Edge* edge : _westEdges  ) edge->revalidate(); cdebug.tabw(110,-1);
    cdebug.log(110,1) << "East side."  << endl; for ( Edge* edge : _eastEdges  ) edge->revalidate(); cdebug.tabw(110,-1);
    cdebug.log(110,1) << "South side." << endl; for ( Edge* edge : _southEdges ) edge->revalidate(); cdebug.tabw(110,-1);
    cdebug.log(110,1) << "North side." << endl; for ( Edge* edge : _northEdges ) edge->revalidate(); cdebug.tabw(110,-1);

    if (_xmin > getXMax()+1)
      cerr << Error( "GCell::_revalidate(): %s, X Min is greater than Max.", getString(this).c_str() );
    if (_ymin > getYMax()+1)
      cerr << Error( "GCell::_revalidate(): %s, Y Min is greater than Max.", getString(this).c_str() );

    _anabatic->_updateLookup( this );
    _anabatic->getMatrix()->show();
    cdebug.tabw(110,-1);
  }


  void  GCell::_moveEdges ( GCell* dest, size_t ibegin, Flags flags )
  {
    cdebug.log(110,1) << "GCell::_moveEdges() " << this << endl;
    cdebug.log(110)   << "           toward " << dest << endl;
    cdebug.log(110)   << "           ibegin: " << ibegin << " flags:" << flags << endl;

    size_t iclear = ibegin;

    if (flags.contains(Flags::SouthSide) and not _southEdges.empty()) {
      cdebug.log(110) << "South side." << endl;

      if (iclear < _southEdges.size()) {
        for ( size_t iedge=ibegin ; (iedge < _southEdges.size()) ; ++iedge ) {
          _southEdges[iedge]->_setTarget( dest );
          dest->_southEdges.push_back( _southEdges[iedge] );
        }
        _southEdges.resize( iclear );
      } else {
        if (iclear > _southEdges.size())
          cerr << Error("GCell::_moveEdges(): On south side, iclear=%u is greater than size()-1=%u\n"
                        "        (%s)"
                       , iclear
                       , _southEdges.size()
                       , getString(this).c_str()
                       ) << endl;
      }
    }

    if (flags.contains(Flags::NorthSide) and not _northEdges.empty()) {
      cdebug.log(110) << "North side." << endl;

      if (iclear < _northEdges.size()) {
        for ( size_t iedge=ibegin ; (iedge < _northEdges.size()) ; ++iedge ) {
          _northEdges[iedge]->_setSource( dest );
          dest->_northEdges.push_back( _northEdges[iedge] );
        }
        _northEdges.resize( iclear );
      } else {
        if (iclear > _northEdges.size())
          cerr << Error("GCell::_moveEdges(): On north side, iclear=%u is greater than size()-1=%u\n"
                        "        (%s)"
                       , iclear
                       , _northEdges.size()
                       , getString(this).c_str()
                       ) << endl;
      }
    }

    if (flags.contains(Flags::WestSide) and not _westEdges.empty()) {
      cdebug.log(110) << "West side." << endl;

      if (iclear < _westEdges.size()) {
        for ( size_t iedge=ibegin ; (iedge < _westEdges.size()) ; ++iedge ) {
          _westEdges[iedge]->_setTarget( dest );
          dest->_westEdges.push_back( _westEdges[iedge] );
        }
        _westEdges.resize( iclear );
      } else {
        if (iclear > _westEdges.size())
          cerr << Error("GCell::_moveEdges(): On west side, iclear=%u is greater than size()-1=%u\n"
                        "        (%s)"
                       , iclear
                       , _westEdges.size()
                       , getString(this).c_str()
                       ) << endl;
      }
    }

    if (flags.contains(Flags::EastSide) and not _eastEdges.empty()) {
      cdebug.log(110) << "East side." << endl;

      if (iclear < _eastEdges.size()) {
        for ( size_t iedge=ibegin ; (iedge < _eastEdges.size()) ; ++iedge ) {
          _eastEdges[iedge]->_setSource( dest );
          dest->_eastEdges.push_back( _eastEdges[iedge] );
        }
        _eastEdges.resize( iclear );
      } else {
        if (iclear > _eastEdges.size())
           cerr << Error("GCell::_moveEdges(): On east side, iclear=%u is greater than size()-1=%u\n"
                        "        (%s)"
                        , iclear
                        , _eastEdges.size()
                        , getString(this).c_str()
                        ) << endl;
      }
    }

    cdebug.tabw(110,-1);
  }


  Contact* GCell::getGContact ( Net* net )
  {

    for ( Contact* contact : _contacts ) {
      if (contact->getNet() == net) {
        cdebug.log(111) << "GCell::getGContact(): " << contact << endl;
        return contact;
      }
    }

    Point    center  = getBoundingBox().getCenter();
    Contact* contact = Contact::create( net
                                      , _anabatic->getConfiguration()->getGContactLayer()
                                      , center.getX()
                                      , center.getY()
                                      , DbU::fromLambda(2.0)
                                      , DbU::fromLambda(2.0)
                                      );
    cdebug.log(111) << "GCell::getGContact(): " << contact << endl;
    return contact;
  }


  const Name& GCell::getName () const
  { return _extensionName; }


  Box  GCell::getBoundingBox () const
  {
    return Box( getXMin(), getYMin(), getXMax(1), getYMax(1) );
  }


  void  GCell::translate ( const DbU::Unit&, const DbU::Unit& )
  {
    cerr << Error( "GCell::translate(): On %s,\n"
                   "        Must never be called on a GCell object (ignored)."
                 , getString(this).c_str()
                 ) << endl;
  }


  string  GCell::_getTypeName () const
  { return getString(_extensionName); }


  string  GCell::_getString () const
  {
    string s = Super::_getString();
    s.insert( s.size()-1, " "+getString(getBoundingBox()) );
    s.insert( s.size()-1, " "+getString(_flags) );
    return s;
  }


  Record* GCell::_getRecord () const
  {
    Record* record = Super::_getRecord();
    record->add( getSlot("_flags"     , &_flags     ) );
    record->add( getSlot("_westEdges" , &_westEdges ) );
    record->add( getSlot("_eastEdges" , &_eastEdges ) );
    record->add( getSlot("_southEdges", &_southEdges) );
    record->add( getSlot("_northEdges", &_northEdges) );
    record->add( DbU::getValueSlot("_xmin", &_xmin) );
    record->add( DbU::getValueSlot("_ymin", &_ymin) );
    return record;
  }



}  // Anabatic namespace.
