// -*- C++ -*-
//
// This file is part of the Coriolis Software.
// Copyright (c) UPMC 2008-2018, All Rights Reserved
//
// +-----------------------------------------------------------------+
// |                   C O R I O L I S                               |
// |      K i t e  -  D e t a i l e d   R o u t e r                  |
// |                                                                 |
// |  Author      :                    Jean-Paul CHAPUT              |
// |  E-mail      :       Jean-Paul.Chaput@asim.lip6.fr              |
// | =============================================================== |
// |  C++ Module  :       "./TrackCost.cpp"                          |
// +-----------------------------------------------------------------+


#include <cstdlib>
#include <sstream>
#include <iostream>
#include "katana/Track.h"
#include "katana/TrackCost.h"
#include "katana/TrackElement.h"
#include "katana/Session.h"


namespace Katana {

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


// -------------------------------------------------------------------
// Class  :  "TrackCost".

  TrackCost::TrackCost ( TrackElement* refSegment
                       , TrackElement* symSegment
                       , Track*        refTrack
                       , Track*        symTrack
                       , DbU::Unit     refCandidateAxis
                       , DbU::Unit     symCandidateAxis
                       )
    : _flags           ((symSegment) ? Symmetric : NoFlags)
    , _span            (refSegment->getTrackSpan())
    , _refCandidateAxis(refCandidateAxis)
    , _symCandidateAxis(symCandidateAxis)
    , _tracks          ( _span * ((symSegment) ? 2 : 1)
                       , std::tuple<Track*,size_t,size_t>(NULL,Track::npos,Track::npos) )
    , _segment1        (refSegment)
    , _segment2        (symSegment)
    , _interval1       (refSegment->getCanonicalInterval())
    , _interval2       ((symSegment) ? symSegment->getCanonicalInterval() : Interval())
    , _terminals       (0)
    , _delta           (-_interval1.getSize() -_interval2.getSize())
    , _deltaShared     (0)
    , _deltaPerpand    (0)
    , _axisWeight      (0)
    , _distanceToFixed (2*Session::getSliceHeight())
    , _longuestOverlap (0)
    , _freeLength      (DbU::Max)
    , _dataState       (0)
    , _ripupCount      (0)
    , _selectFlags     (NoFlags)
    , _selectIndex     (0)
  {
    if (Session::getStage() == StageRealign) _flags |= IgnoreShort;
    
    if (refSegment->isNonPref()) {
      DbU::Unit axisShift = getRefCandidateAxis() - refSegment->getAxis();
      _interval1.translate( axisShift );

      if (symSegment) {
        throw Error( "TrackCost::TrackCost(): Symmetric management is not implemented for Non-preferred routing segments.\n"
                     "        %s", getString(refSegment).c_str() );
      }
    }
    if (not _span) {
        throw Error( "TrackCost::TrackCost(): Zero track span is not allowed.\n"
                     "        %s", getString(refSegment).c_str() );
    }
    
    cdebug_log(159,1) << "TrackCost::TrackCost() - " << refSegment << endl;
    cdebug_log(159,0) << "  interval1: " << _interval1 << endl;
    
    std::get<0>( _tracks[0] ) = refTrack;
    _segment1->addOverlapCost( *this );

    if (symTrack) {
      cdebug_log(159,0) << "  _tracks.size(): " << _tracks.size() << " _span:" << _span << endl;

      std::get<0>( _tracks[_span] ) = symTrack;
      select( 0, Symmetric );
      _segment2->addOverlapCost( *this );
    }

    consolidate();
    cdebug_tabw(159,-1);
  }


  TrackCost::~TrackCost ()
  { }


  bool  TrackCost::isFree () const
  {
    return /*(not _terminals) and*/ (not isOverlap()) and (not isInfinite());
  }


  bool  TrackCost::Compare::operator() ( const TrackCost* lhs, const TrackCost* rhs )
  {
    if ( lhs->isInfinite    () xor rhs->isInfinite    () ) return rhs->isInfinite();
    if ( lhs->isAtRipupLimit() xor rhs->isAtRipupLimit() ) return rhs->isAtRipupLimit();

    if (   (_flags & TrackCost::DiscardGlobals)
       and (lhs->isOverlapGlobal() xor rhs->isOverlapGlobal()) )
      return rhs->isOverlapGlobal();

    if ( lhs->isHardOverlap() xor rhs->isHardOverlap() ) return rhs->isHardOverlap();

    if ( lhs->_ripupCount + (int)Session::getRipupCost() < rhs->_ripupCount ) return true;
    if ( lhs->_ripupCount > (int)Session::getRipupCost() + rhs->_ripupCount ) return false;

  //int lhsRipupCost = (lhs->_dataState<<2) + lhs->_ripupCount;
  //int rhsRipupCost = (rhs->_dataState<<2) + rhs->_ripupCount;
  //if ( lhsRipupCost + (int)Session::getRipupCost() < rhsRipupCost ) return true;
  //if ( lhsRipupCost > (int)Session::getRipupCost() + rhsRipupCost ) return false;

  //if ( _flags & TrackCost::DiscardGlobals ) {
  //  if ( lhs->_longuestOverlap < rhs->_longuestOverlap ) return true;
  //  if ( lhs->_longuestOverlap > rhs->_longuestOverlap ) return false;
  //}

    if ( lhs->isOverlap() xor rhs->isOverlap() ) return rhs->isOverlap();

    if (not (_flags & TrackCost::IgnoreTerminals)) {
      if ( lhs->_terminals < rhs->_terminals ) return true;
      if ( lhs->_terminals > rhs->_terminals ) return false;
    }

    if (lhs->_delta != rhs->_delta) {
    //cdebug_log(155,0) << "TrackCost::Compare() lhs->_delta:" << lhs->_delta << " rhs->_delta:" << rhs->_delta << endl;
    //if ( not (_flags & TrackCost::IgnoreSharedLength) or (lhs->_delta > 0) or (rhs->_delta > 0) ) {
    //if ( (lhs->_delta > 0) or (rhs->_delta > 0) ) {
        if (lhs->_delta < rhs->_delta) return true;
        if (lhs->_delta > rhs->_delta) return false;
    //}

    // Both delta should be negative, chose the least one.
    //return lhs->_delta > rhs->_delta;
      return lhs->_delta < rhs->_delta;
    }

#if 0
    DbU::Unit lhsMixedWeight = 0.5*lhs->_deltaPerpand;
    DbU::Unit rhsMixedWeight = 0.5*rhs->_deltaPerpand;

    if ( not (_flags & TrackCost::IgnoreAxisWeight) ) {
      lhsMixedWeight += lhsMixedWeight;
      rhsMixedWeight += rhsMixedWeight;
    }

    if (lhsMixedWeight < rhsMixedWeight) return true;
    if (lhsMixedWeight > rhsMixedWeight) return false;
#endif

    if ( not (_flags & TrackCost::IgnoreAxisWeight) ) {
      if ( lhs->_axisWeight < rhs->_axisWeight ) return true;
      if ( lhs->_axisWeight > rhs->_axisWeight ) return false;
    }

    if ( lhs->_deltaPerpand < rhs->_deltaPerpand ) return true;
    if ( lhs->_deltaPerpand > rhs->_deltaPerpand ) return false;

    if ( lhs->_distanceToFixed > rhs->_distanceToFixed ) return true;
    if ( lhs->_distanceToFixed < rhs->_distanceToFixed ) return false;

    return lhs->getTrack(0)->getAxis() < rhs->getTrack(0)->getAxis();
  }


  bool  TrackCost::CompareByDelta::operator() ( const TrackCost* lhs, const TrackCost* rhs )
  {
    return lhs->getDelta() < rhs->getDelta();
  }


  Net*       TrackCost::getNet1          () const { return (_segment1) ? _segment1->getNet() : NULL; }
  Net*       TrackCost::getNet2          () const { return (_segment2) ? _segment2->getNet() : NULL; }


  size_t  TrackCost::getBegin ( size_t i, uint32_t flags ) const
  {
    if (i >= _span) return Track::npos;
    return std::get<1>( _tracks[i + ((flags & Symmetric) ? _span : 0)] );
  }


  size_t  TrackCost::getEnd ( size_t i, uint32_t flags ) const
  {
    if (i >= _span) return Track::npos;
    return std::get<2>( _tracks[i + ((flags & Symmetric) ? _span : 0)] );
  }


  void  TrackCost::consolidate ()
  {
    if ( not isInfinite() and not isHardOverlap() ) {
      cdebug_log(159,0) << "TrackCost::consolidate() " << DbU::getValueString(_delta)
                        << " - " << DbU::getValueString(_deltaShared) << endl;
    //_deltaPerpand += - (_deltaShared << 1);
      _delta -= _deltaShared;
    //if (_delta > 0) _delta -= _deltaShared;
    //else            _delta += _deltaShared;
    }
  }


  void  TrackCost::setDistanceToFixed ()
  {
    if (_flags & Analog) return;

    // This is the GCell side (it is *one* cell height from the gauge).
    DbU::Unit  cellHeight = Session::getSliceHeight();

    cdebug_log(159,0) << "TrackCost::setDistanceToFixed() begin:" << getBegin(0) << endl;

    TrackElement* neighbor;
    if (getBegin(0) != Track::npos) {
      neighbor = getTrack(0)->getSegment(getBegin(0));
      if ( neighbor and (neighbor->getNet() != getNet()) ) {
        DbU::Unit distance = getInterval().getVMin() - neighbor->getTargetU();
        if ( distance < cellHeight )
          _distanceToFixed = distance;
      }
    // if ( neighbor and neighbor->isFixed() ) {
    //   if ( _distanceToFixed == DbU::Max ) _distanceToFixed = 0;
    //   _distanceToFixed += _interval.getVMin() - neighbor->getTargetU();
    // }
    }
    if (getEnd(0) != Track::npos) {
      neighbor = getTrack(0)->getSegment(getEnd(0));
      if ( neighbor and (neighbor->getNet() != getNet()) ) {
        DbU::Unit distance = neighbor->getSourceU() - getInterval().getVMax();
        if ( _distanceToFixed == 2*cellHeight ) _distanceToFixed = 0;
        if ( distance < cellHeight )
          _distanceToFixed += distance;
      }
    // if ( neighbor and neighbor->isFixed() ) {
    //   if ( _distanceToFixed == DbU::Max ) _distanceToFixed = 0;
    //   _distanceToFixed += neighbor->getSourceU() - _interval.getVMax();
    // }
    }
  }


  string  TrackCost::_getString () const
  {
    string s = "<" + _getTypeName();

    s += " @"   + DbU::getValueString(getRefCandidateAxis());
    s += " "    + getString(getTrack(0)->getLayer()->getName());
    s += " "    + getString(_dataState);
    s += "+"    + getString(_ripupCount);
    s += ":"    + getString((_dataState<<2)+_ripupCount);
    s += " "    + string ( (isInfinite()      )?"I":"-" );
    s +=          string ( (isBlockage()      )?"b":"-" );
    s +=          string ( (isFixed()         )?"f":"-" );
    s +=          string ( (isHardOverlap()   )?"h":"-" );
    s +=          string ( (isOverlap()       )?"o":"-" );
    s +=          string ( (isOverlapGlobal() )?"g":"-" );
    s +=          string ( (isGlobalEnclosed())?"e":"-" );
    s +=          string ( (isAtRipupLimit  ())?"R":"-" );
    s +=          string ( (isAnalog        ())?"a":"-" );
    s +=          string ( (isShortNet      ())?"N":"-" );
    s += " t:"  + getString(_terminals);
    s += "/d:"  + /*DbU::getValueString(_delta)*/       DbU::getValueString(_delta);
    s += "-"    + /*DbU::getValueString(_deltaShared)*/ DbU::getValueString(_deltaShared);
    s += "/aw:" + DbU::getValueString(_axisWeight);
    s += "/dp:" + DbU::getValueString(_deltaPerpand);
    s += "/df:" + DbU::getValueString(_distanceToFixed);
    s += "/ov:" + DbU::getValueString(_longuestOverlap);
    s += " "    + getString(_dataState);
    s += ">";

    return s;
  }


  Record* TrackCost::_getRecord () const
  {
    Record* record = new Record ( _getString() );
    record->add( getSlot          ( "_flags"          ,  _flags           ) );
    record->add( getSlot          ( "_tracks"         ,  _tracks          ) );
    record->add( getSlot          ( "_interval1"      , &_interval1       ) );
    record->add( getSlot          ( "_interval2"      , &_interval2       ) );
    record->add( getSlot          ( "_terminals"      ,  _terminals       ) );
    record->add( DbU::getValueSlot( "_delta"          , &_delta           ) );
    record->add( DbU::getValueSlot( "_deltaShared"    , &_deltaShared     ) );
    record->add( DbU::getValueSlot( "_deltaPerpand"   , &_deltaPerpand    ) );
    record->add( DbU::getValueSlot( "_axisWeight"     , &_axisWeight      ) );
    record->add( DbU::getValueSlot( "_distanceToFixed", &_distanceToFixed ) );
    record->add( DbU::getValueSlot( "_longuestOverlap", &_longuestOverlap ) );
                                     
    return record;
  }


} // Katana namespace.
