
// This file is part of the Coriolis Project.
// Copyright (C) Laboratoire LIP6 - Departement ASIM
// Universite Pierre et Marie Curie
//
// Main contributors :
//   Christophe Alexandre   <Christophe.Alexandre@lip6.fr>
//   Hugo Clément                   <Hugo.Clement@lip6.fr>
//   Jean-Paul Chaput           <Jean-Paul.Chaput@lip6.fr>
//   Christian Masson           <Christian.Masson@lip6.fr>
// 
// The Coriolis Project  is free software;  you can  redistribute it and/or
// modify  it  under the  terms  of  the  GNU  General  Public License  as
// published by  the Free  Software Foundation; either  version 2  of  the
// License, or (at your option) any later version.
// 
// The Coriolis Project  is distributed in the hope that it will be useful,
// but  WITHOUT  ANY  WARRANTY;  without  even  the  implied  warranty  of
// MERCHANTABILITY  or  FITNESS  FOR A  PARTICULAR PURPOSE.   See  the GNU
// General Public License for more details.
// 
// You should have received a copy  of  the  GNU  General  Public  License
// along with  the Coriolis Project;  if  not,  write to the  Free Software
// Foundation, inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//
// License-Tag
//
// Date   : 29/01/2004
// Author : Christophe Alexandre  <Christophe.Alexandre@lip6.fr>
//
// Authors-Tag 


#include <cmath>
using namespace std;

#include "vlsisapd/configuration/Configuration.h"
#include "hurricane/Warning.h"
#include "hurricane/Net.h"
#include "hurricane/Instance.h"
#include "hurricane/Plug.h"
#include "hurricane/Timer.h"
#include "crlcore/ToolBox.h"
#include "crlcore/AllianceFramework.h"
using namespace CRL;

#include "nimbus/NimbusEngine.h"
using namespace Nimbus;

#ifdef HAVE_HMETIS_LIB
#include "metis/MetisGraph.h"
#endif
#include "metis/MetisEngine.h"


namespace Metis {


  const Name MetisEngine::_toolName = "Metis";

    
  MetisEngine::MetisEngine ( Cell* cell )
    : Inherit       (cell)
    , _configuration(new Configuration())
    , _step         (0)
#ifdef HAVE_HMETIS_LIB
    , _actualGraphs ()
    , _newGraphs    ()
#endif
    , _globalEdgeCut(0)
  {
#ifdef HAVE_HMETIS_LIB

    NimbusEngine* nimbus = NimbusEngine::get ( getCell() );
    if ( nimbus == NULL )
      throw Error ("Nimbus must be created before Metis, on cell <%s>"
                  ,getString(getCell()->getName()).c_str());

    _actualGraphs = new MetisGraphs();
    _newGraphs    = new MetisGraphs();
    _actualGraphs->push_back ( new MetisGraph(this,nimbus->getGrid()->getRoot()) );

    // What the fuck does this loop do?
    // forEach ( Net*, inet, getCell()->getNets() ) {
    //   if ( inet->isGlobal() ) continue;
    //   if ( inet->isSupply() ) continue;
    // }
#endif // HAVE_HMETIS_LIB
  }

    
  MetisEngine::~MetisEngine ()
  { delete _configuration; }


  MetisEngine* MetisEngine::create ( Cell* cell )
  {
    MetisEngine* metis = new MetisEngine(cell);

    metis->_postCreate();
    return metis;
  }


  void MetisEngine::_preDestroy ()
  {
#ifdef HAVE_HMETIS_LIB
    for (MetisGraphs::iterator mgit = _actualGraphs->begin(); mgit != _actualGraphs->end(); ++mgit)
      delete *mgit;
    for (MetisGraphs::iterator mgit = _newGraphs->begin(); mgit != _newGraphs->end(); ++mgit)
      delete *mgit;

    delete _actualGraphs;
    delete _newGraphs;
#endif // HAVE_HMETIS_LIB
    Inherit::_preDestroy();
  }


  bool MetisEngine::isHMetisCapable ()
  {
#ifdef HAVE_HMETIS_LIB
    return false;
#else
    return false;
#endif // HAVE_HMETIS_LIB
  }


  Record* MetisEngine::_getRecord () const
  {
    Record* record = Inherit::_getRecord();
    if (record) {
      record->add ( getSlot("step", _step) );
    }
    return record;
  }


  void MetisEngine::run ()
  {
#ifdef HAVE_HMETIS_LIB
    cmess2 << "     o  hMetis quadri-partition step." << endl;

    Timer timer;
    timer.start();

    NimbusEngine* nimbus = NimbusEngine::get ( getCell() );

    forEach ( GCell*, igcell, nimbus->getPlacementLeaves() ) {
      if ( not igcell->hasSubGCells() )
        throw Warning("You must first progress in Nimbus before calling Metis");

      if ( igcell->getSubGCells().getSize() < 2 )
        throw Error("Metis needs at least 2 Nimbus subboxes");
    }

    _globalEdgeCut = 0;
    
    linefill  output ("        ",cmess2);

    for ( MetisGraphs::iterator mgit = _actualGraphs->begin(); mgit != _actualGraphs->end(); ++mgit ) {
      MetisGraph* graph = *mgit;

      try {
        _globalEdgeCut += graph->part ( output );
      } 
      catch ( MetisGraph::TooLowNVTXSException& e ) {
        if ( cmess2.enabled() ) cerr << "\n";
        cerr << Warning("Impossible to part graph, only %d nodes",e._nvtxs);
      }
        
      for ( size_t id=0; id < graph->_partResultVector.size(); ++id ) {
        _newGraphs->push_back
          ( new MetisGraph(this
                          ,graph
                          ,graph->_partResultVector[id]->first
                          ,graph->_partResultVector[id]->second
                          ) );
      }
    }

    output << "\n";
    
    for ( MetisGraphs::iterator mgit=_actualGraphs->begin(); mgit != _actualGraphs->end(); ++mgit )
      delete *mgit;
    delete _actualGraphs;

    _actualGraphs = _newGraphs;
    _newGraphs    = new MetisGraphs ();
    
    save ( ++_step );
       
    timer.stop();    
    cmess2 << "        - Refine placement done in " << getString(timer.getUserTime()) << "s." << endl;

    if ( getRefreshCb() != NULL ) getRefreshCb() ();
#else
    throw Warning ( "hmetis library is needed to use Metis ToolEngine" );
#endif  // HAVE_HMETIS_LIB
  }


  void MetisEngine::save ( unsigned step )
  {
#ifdef HAVE_HMETIS_LIB
  //for ( MetisGraphs::iterator mgit=_actualGraphs->begin(); mgit != _actualGraphs->end(); ++mgit)
  //  (*mgit)->save ( step );
    grabPlacementModificationFlag ();
#else
    throw Warning ( "hmetis library is needed to use Metis ToolEngine" );
#endif /* HAVE_HMETIS_LIB */
  }


  int MetisEngine::getGlobalEdgeCut () const
  { return _globalEdgeCut; }


  const Name& MetisEngine::staticGetName ()
  { return _toolName; }


  const Name& MetisEngine::getName () const
  { return _toolName; }


  MetisEngine* MetisEngine::get ( Cell* cell )
  { return dynamic_cast<MetisEngine*> ( ToolEngine::get(cell,_toolName) ); }


  bool MetisEngine::_reInit()
  {
#if 0
#ifdef HAVE_HMETIS_LIB
    for (MetisGraphs::iterator mgit=_actualGraphs->begin(); mgit != _actualGraphs->end(); ++mgit)
      delete *mgit;
    for (MetisGraphs::iterator mgit=_newGraphs->begin(); mgit != _newGraphs->end(); ++mgit)
      delete *mgit;

    delete _actualGraphs;
    delete _newGraphs;

    _actualGraphs = new MetisGraphs();
    _newGraphs    = new MetisGraphs();

    NimbusEngine* nimbus       = NimbusEngine::get ( getCell() );
    bool          partToBeDone = false;

    forEach ( GCell*, igcell, nimbus->getPlacementLeaves() ) {
      Instances instances = getCell()->getInstancesUnder(**igcell);

      if ( instances.getSize() >= getNumberOfInstancesStopCriterion() ) {
        if ( not partToBeDone ) {
          partToBeDone = true;
          ++_step;
        }
        _actualGraphs->push_back ( new MetisGraph(this,instances,igcell) );
      }
    }

    if ( not partToBeDone )
      throw Warning("Not enough instances to part, minimum is %d",getNumberOfInstancesStopCriterion());

    return true;
#else
    throw Warning ( "hmetis library is needed to use Metis ToolEngine" );
#endif  // HAVE_HMETIS_LIB
#else
    return false;
#endif
}


  unsigned int  MetisEngine::computeQuadriPartitions ( Cell* cell, int& xsplits, int& ysplits )
  {
    size_t gates       = getInstancesCount ( cell );
    Box    ab          = cell->getAbutmentBox();
    double aspectRatio = (double)(ab.getWidth()) / (double)(ab.getHeight());

    if ( aspectRatio >= 1.0 ) {
      xsplits = ((int)(trunc (      aspectRatio +0.5 ) )) * 2;
      ysplits = 2;
    } else {
      xsplits = 2;
      ysplits = ((int)(trunc ( (1.0/aspectRatio)+0.5 ) )) * 2;
    }


    size_t partitionSizeStop = Cfg::getParamInt("metis.numberOfInstancesStopCriterion",45)->asInt();
    if ( gates / (xsplits*ysplits) < partitionSizeStop )
      throw Error("Design &lt;%s&gt; is too small to be quadri-partionned, only %d gates."
                 ,getString(cell->getName()).c_str(),gates);

    double quadPartitions = log((double)gates / (double)(partitionSizeStop*xsplits*ysplits) ) / log(4.0) + 1.0;

    return (unsigned int)(quadPartitions);
  }


  void  MetisEngine::doQuadriPart ( Cell* cell )
  {
    if ( not isHMetisCapable() )
      throw Error ("Metis::doQuadriPart(): HMetis wasn't found at compile time <%s>"
                  ,getString(cell->getName()).c_str());

    // MetisEngine* metis = MetisEngine::get ( cell );
    // if ( metis != NULL )
    //   throw Error ("Metis::doQuadriPart(): Metis already exists on <%s>"
    //               ,getString(cell->getName()).c_str());

    // NimbusEngine* nimbus = NimbusEngine::get ( cell );
    // if ( nimbus != NULL )
    //   throw Error ("Metis::doQuadriPart(): Nimbus already exists on <%s>"
    //               ,getString(cell->getName()).c_str());

    NimbusEngine* nimbus = NimbusEngine::get ( cell );
    if ( nimbus == NULL )
      nimbus = NimbusEngine::create ( cell );

    MetisEngine* metis = MetisEngine::get ( cell );
    if ( metis == NULL )
      metis  = MetisEngine ::create ( cell );

    int    xsplits;
    int    ysplits;
    size_t partitions = computeQuadriPartitions ( cell, xsplits, ysplits );

    for ( size_t part=0 ; part<partitions ; ++part ) {
      nimbus->progress ( xsplits, ysplits );
      metis-> run ();

      xsplits = 2;
      ysplits = 2;
    }
  }


} // End of Metis namespace.
