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


#include <cstdlib>
#include <sstream>
#include "hurricane/Error.h"
#include "hurricane/Horizontal.h"
#include "hurricane/Vertical.h"
#include "hurricane/Cell.h"
#include "hurricane/UpdateSession.h"
#include "crlcore/RoutingGauge.h"
#include "crlcore/RoutingLayerGauge.h"
#include "anabatic/Configuration.h"
#include "anabatic/Session.h"
#include "anabatic/AutoContact.h"
#include "anabatic/AutoSegment.h"
#include "anabatic/AnabaticEngine.h"


namespace {


  const char* reopenSession    = "Anabatic::Session::_open(): Session already open for %s (internal error).";
  const char* openSessionError = "%s: Session has not been opened (internal error).";


} // End of local namespace.




namespace Anabatic {

  using namespace std;
  using Hurricane::tab;
  using Hurricane::Error;
  using Hurricane::ForEachIterator;
  using Hurricane::UpdateSession;
  using Hurricane::Horizontal;
  using Hurricane::Vertical;
  using Hurricane::Cell;
  using CRL::RoutingLayerGauge;


// -------------------------------------------------------------------
// Class  :  "Anabatic::Session".

  Session*  Session::_session = NULL;


  Session* Session::get ( const char* message )
  {
    if (not _session and message)
      throw Error( openSessionError, message );
    return _session;
  }


  Session::Session ( AnabaticEngine* anbt )
    : _anabatic           (anbt)
    , _technology         (anbt->getConfiguration()->getRoutingGauge()->getTechnology())
    , _cellGauge          (anbt->getConfiguration()->getCellGauge())
    , _routingGauge       (anbt->getConfiguration()->getRoutingGauge())
    , _autoContacts       ()
    , _doglegs            ()
    , _segmentInvalidateds()
    , _segmentRevalidateds()
    , _netInvalidateds    ()
    , _netRevalidateds    ()
  { }


  void  Session::_postCreate ()
  {
    UpdateSession::open();
    _session = this;
  }


  Session::~Session ()
  { }


  void  Session::_preDestroy ()
  {
    if (_anabatic->getState() <= EngineActive) {
      _revalidate ();
      _anabatic->updateDensity();
    }
    UpdateSession::close();
  }


  bool           Session::_doDestroyBaseContact () { return _anabatic->doDestroyBaseContact(); }
  bool           Session::_doDestroyBaseSegment () { return _anabatic->doDestroyBaseSegment(); }
  bool           Session::_doDestroyTool        () { return _anabatic->doDestroyTool(); }
  Configuration* Session::_getConfiguration     () { return _anabatic->getConfiguration(); }


  void  Session::_invalidate ( Net* net )
  {
    cdebug_log(149,0) << "Session::invalidate(Net*) - " << net << endl;
    _netInvalidateds.insert(net);
  }


  void  Session::_canonize ()
  {
    cdebug_log(145,1) << "Anabatic::Session::_canonize()" << endl;

    if (_segmentInvalidateds.empty()) {
      cdebug_log(145,0) << "Invalidated AutoSegment collection <_segmentInvalidateds> is empty." << endl;
      cdebug_tabw(145,-1);
      return;
    }

    set<Segment*>        exploredSegments;
    vector<AutoSegment*> aligneds;

  // Should no longer be necessary to ensure determinism.
  //sort( _segmentInvalidateds.begin(), _segmentInvalidateds.end(), AutoSegment::CompareId() );

    for ( size_t i=0 ; i<_segmentInvalidateds.size() ; i++ ) {
      AutoSegment* seedSegment = _segmentInvalidateds[i];
      AutoSegment* canonical   = seedSegment;

      if (exploredSegments.find(seedSegment->base()) == exploredSegments.end()) {
        cdebug_log(145,0) << "New chunk from: " << seedSegment << endl;
        aligneds.push_back( seedSegment );

        bool isWeakGlobal = seedSegment->isGlobal();
        if (not seedSegment->isNotAligned()) {
          forEach ( AutoSegment*, aligned, seedSegment->getAligneds() ) {
            cdebug_log(145,0) << "Aligned: " << *aligned << endl;
            aligneds.push_back ( *aligned );
            exploredSegments.insert ( aligned->base() );

            isWeakGlobal = isWeakGlobal or aligned->isGlobal();
            if (AutoSegment::CompareId()( *aligned, canonical ))
              canonical = *aligned;
          }
        }

        cdebug_tabw(145,1);

        canonical->setFlags( AutoSegment::SegCanonical );
        cdebug_log(145,0) << "Canonical: " << canonical << endl;

        for ( size_t j=0 ; j<aligneds.size() ; j++ ) {
          if (isWeakGlobal and not aligneds[j]->isGlobal()) aligneds[j]->setFlags  ( AutoSegment::SegWeakGlobal );
          else                                              aligneds[j]->unsetFlags( AutoSegment::SegWeakGlobal );

          if (aligneds[j] == canonical) continue;
          if (aligneds[j]->isCanonical()) {
            cerr << Error("Session::_canonize(): On %s\n"
                          "        Segment is no longer the canonical one, this must not happens."
                         ,getString(aligneds[j]).c_str()) << endl;
          }
          aligneds[j]->unsetFlags( AutoSegment::SegCanonical );
          cdebug_log(145,0) << "Secondary: " << aligneds[j] << endl;
        }
        if (aligneds.empty()) canonical->setFlags( AutoSegment::SegNotAligned );

        cdebug_log(149,0) << "Align on canonical:" << canonical << endl;

      //canonical->setAxis( canonical->getAxis(), Flags::Realignate );
        if (canonical->isUnsetAxis()) canonical->toOptimalAxis( Flags::Realignate|Flags::Propagate );
        else                          canonical->setAxis( canonical->getAxis(), Flags::Realignate|Flags::Propagate );
        aligneds.clear();
        cdebug_tabw(145,-1);
      }
    }

    cdebug_tabw(145,-1);
  }


  void  Session::_revalidateTopology ()
  {
    cdebug_log(145,1) << "Anabatic::Session::_revalidateTopology()" << endl;

    set<Net*>::iterator inet = _netInvalidateds.begin();

    for ( ; inet != _netInvalidateds.end() ; inet++ ) {
      cdebug_log(145,0) << "Anabatic::Session::_revalidateTopology(Net*)" << *inet << endl;
      _anabatic->updateNetTopology    ( *inet );
      _anabatic->computeNetConstraints( *inet );
      _anabatic->_computeNetOptimals  ( *inet );
      _anabatic->_computeNetTerminals ( *inet );
    }
    _canonize ();

    for ( size_t i=0 ; i<_segmentInvalidateds.size() ; ++i ) {
      if (_segmentInvalidateds[i]->isCanonical()) {
        if (_segmentInvalidateds[i]->isUnsetAxis()) _segmentInvalidateds[i]->toOptimalAxis();
        else                                        _segmentInvalidateds[i]->toConstraintAxis();
      }
    }
    
    _netRevalidateds.clear();
    _netRevalidateds.swap( _netInvalidateds );

    cdebug_tabw(145,-1);
  }


  size_t  Session::_revalidate ()
  {
    cdebug_log(145,1) << "Anabatic::Session::revalidate()" << endl;
    cdebug_log(145,0)   << "_segmentInvalidateds.size(): " << _segmentInvalidateds.size() << endl;
    cdebug_log(145,0)   << "_autoContacts.size(): " << _autoContacts.size() << endl;

    size_t count = 0;

    if (not _netInvalidateds.empty()) _revalidateTopology();

    cdebug_log(145,0) << "AutoContacts Revalidate (after _revalidateTopology())." << endl;
    for ( size_t i=0 ; i < _autoContacts.size() ; i++, count++ )
      _autoContacts[i]->updateGeometry();
    _autoContacts.clear();

    cdebug_log(145,0) << "AutoSegments Revalidate (after AutoContact::updateGeometry())." << endl;
    cdebug_log(145,0) << "_segmentInvalidateds.size(): " << _segmentInvalidateds.size() << endl;

    _segmentRevalidateds.clear();
    for ( size_t i=0 ; i < _segmentInvalidateds.size() ; ++i, ++count ) {
      _segmentInvalidateds[i]->revalidate();
      if ( not _destroyedSegments.empty()
         and (_destroyedSegments.find(_segmentInvalidateds[i]) != _destroyedSegments.end()) )
        continue;

      _segmentRevalidateds.push_back( _segmentInvalidateds[i] );
    }
    _segmentInvalidateds.clear();

    cdebug_log(145,0) << "AutoSegments/AutoContacts queued deletion." << endl;
    Flags flags = _anabatic->flags() & Flags::DestroyMask;
    _anabatic->flags() = Flags::DestroyMask;
    set<AutoSegment*>::iterator  isegment = _destroyedSegments.begin();
    for ( ; isegment != _destroyedSegments.end() ; isegment++ ) {
      AutoContact* source = (*isegment)->getAutoSource();
      AutoContact* target = (*isegment)->getAutoTarget();
      (*isegment)->destroy();
      if (source and source->canDestroy(true)) source->destroy();
      if (target and target->canDestroy(true)) target->destroy();
    }
    _anabatic->flags() = flags;
    set<AutoSegment*>().swap( _destroyedSegments );

    cdebug_tabw(145,-1);

    return count;
  }


  Session*  Session::_open ( AnabaticEngine* anbt )
  {
    cdebug_log(145,0) << "Anabatic::Session::_open()" << endl;

    if (_session) {
      if (_session->_anabatic != anbt)
        throw Error( reopenSession, getString(_session->getAnabatic()).c_str() );

      return _session;
    }

    Session* session = new Session ( anbt );
    session->_postCreate();

    return session;
  }


  void  Session::close ()
  {
    cdebug_log(145,1) << "Session::close()" << endl;

    if (not _session) throw Error( openSessionError, "Session::Close()" );

    _session->_preDestroy();

    delete _session;
    _session = NULL;

    cdebug_tabw(145,-1);
  }


  Flags  Session::getDirection ( size_t depth )
  {
    RoutingGauge* rg = get("getDirection()")->_routingGauge;
    switch ( rg->getLayerDirection(depth) ) {
      case Constant::Horizontal: return Flags::Horizontal;
      case Constant::Vertical:   return Flags::Vertical;
    }
    return Flags::NoFlags;
  }


  DbU::Unit  Session::_getPitch ( size_t depth, Flags flags ) const
  {
    if (flags == Flags::NoFlags) return _routingGauge->getLayerPitch(depth);

    if (flags & Flags::AboveLayer) {
      if (depth < getAllowedDepth()) 
        return _routingGauge->getLayerPitch( depth + 1 );
      else {
        if ( (depth > 0) and (_routingGauge->getLayerType(depth-1) != Constant::PinOnly) )
          return _routingGauge->getLayerPitch( depth - 1 );
      }
    }

    if (flags & Flags::BelowLayer) {
      if ( (depth > 0) and (_routingGauge->getLayerType(depth-1) != Constant::PinOnly) )
        return _routingGauge->getLayerPitch( depth - 1 );
      else {
        if (depth < getAllowedDepth())
          return _routingGauge->getLayerPitch( depth + 1 );
      }
    }

  // Should issue at least a warning here.
    return _routingGauge->getLayerPitch(depth);
  }


  Point  Session::_getNearestGridPoint ( Point p, Box constraint )
  {
    Box ab = _anabatic->getCell()->getAbutmentBox();

    RoutingLayerGauge* lg = _routingGauge->getVerticalGauge();
    DbU::Unit x = lg->getTrackPosition( ab.getXMin()
                                      , lg->getTrackIndex( ab.getXMin()
                                                         , ab.getXMax()
                                                         , p.getX()
                                                         , Constant::Nearest ) );
    if (x < constraint.getXMin()) x += lg->getPitch();
    if (x > constraint.getXMax()) x -= lg->getPitch();

    lg = _routingGauge->getHorizontalGauge();
    DbU::Unit y = lg->getTrackPosition( ab.getYMin()
                                      , lg->getTrackIndex( ab.getYMin()
                                                         , ab.getYMax()
                                                         , p.getY()
                                                         , Constant::Nearest ) );
    if (y < constraint.getYMin()) y += lg->getPitch();
    if (y > constraint.getYMax()) y -= lg->getPitch();

    return Point(x,y);
  }


  bool  Session::isInDemoMode ()
  { return get("isInDemoMode()")->_anabatic->isInDemoMode(); }


  float  Session::getSaturateRatio ()
  { return get("getSaturateRatio()")->_anabatic->getSaturateRatio(); }


  size_t  Session::getSaturateRp ()
  { return get("getSaturateRp()")->_anabatic->getSaturateRp(); }


  bool  Session::doWarnGCellOverload ()
  { return get("doWarnGCellOverload()")->_anabatic->doWarnOnGCellOverload(); }


  void  Session::setAnabaticFlags ( Flags flags )
  { get("setKabaticFlags()")->_anabatic->flags() = flags; }


  void  Session::link ( AutoContact* autoContact )
  { return get("link(AutoContact*)")->_anabatic->_link( autoContact ); }


  void  Session::link ( AutoSegment* autoSegment )
  { return get("link(AutoSegment*)")->_anabatic->_link( autoSegment ); }


  void  Session::unlink ( AutoContact* autoContact )
  { return get("unlink(AutoContact*)")->_anabatic->_unlink( autoContact ); }


  void  Session::unlink ( AutoSegment* autoSegment )
  { return get("unlink(AutoSegment*)")->_anabatic->_unlink( autoSegment ); }


  AutoContact* Session::lookup ( Contact* contact )
  { return get("lookup(Contact*)")->_anabatic->_lookup( contact ); }


  AutoSegment* Session::lookup ( Segment* segment )
  { return get("lookup(Segment*)")->_anabatic->_lookup( segment ); }


  string  Session::_getString () const
  {
    return "<" + _getTypeName() + " "
               + getString(_anabatic->getCell()->getName())
               + ">";
  }


  Record* Session::_getRecord () const
  {
    Record* record = new Record ( _getString() );
    record->add ( getSlot ( "_anabatic"    , _anabatic      ) );
    record->add ( getSlot ( "_autoContacts", &_autoContacts ) );
  //record->add ( getSlot ( "_autoSegments", &_autoSegments ) );
    return record;
  }


} // End of Anabatic namespace.