// -*- C++ -*-
//
// This file is part of the Coriolis Software.
// Copyright (c) UPMC 2014-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  :       "./SymmetricRoute.cpp"                     |
// +-----------------------------------------------------------------+


#include <map>
#include <list>
#include "hurricane/Error.h"
#include "hurricane/Warning.h"
#include "hurricane/DebugSession.h"
#include "hurricane/NetRoutingProperty.h"
#include "hurricane/RoutingPad.h"
#include "anabatic/AutoContactTerminal.h"
#include "katana/RoutingPlane.h"
#include "katana/TrackFixedSegment.h"
#include "katana/Track.h"
#include "katana/KatanaEngine.h"


namespace {

  using namespace std;
  using Hurricane::Error;
  using Hurricane::DebugSession;
  using Hurricane::DbU;
  using Hurricane::Point;
  using Hurricane::Net;
  using Hurricane::NetRoutingState;
  using Hurricane::NetRoutingExtension;
  using Hurricane::Component;
  using Hurricane::Contact;
  using Hurricane::Segment;
  using Hurricane::Horizontal;
  using Hurricane::Vertical;
  using Hurricane::RoutingPad;
  using Anabatic::Flags;
  using Anabatic::GCell;
  using Anabatic::AutoContact;
  using Anabatic::AutoContactTerminal;
  using Anabatic::AutoSegment;
  using Katana::TrackElement;
  using Katana::DataSymmetric;
  using Katana::KatanaEngine;
  using Katana::Session;


  class Message {
    public:
      inline                Message   ( size_t, string header="" );
      inline size_t         size      () const;
      inline ostringstream& newline   ();
      inline ostringstream& line      ();
      inline void           setHeader ( string );
             void           print     ( ostream& );
    private:
      size_t         _indent;
      string         _header;
      vector<string> _lines;
      ostringstream  _current;
  };


  inline  Message::Message   ( size_t indent, string header )
    : _indent (indent)
    , _header (header)
    , _lines  ()
    , _current()
  { }

  inline size_t         Message::size      () const { return _lines.size(); }
  inline void           Message::setHeader ( string header ) { _header = header; }
  inline ostringstream& Message::line      () { return _current; }

  inline ostringstream& Message::newline ()
  {
    if (_current.str().size())
      _lines.push_back(_current.str());
    _current.str("");
    return _current;
  }

  void Message::print ( ostream& o )
  {
    if (not _header.empty()) _indent = _header.size()+1;

    string head ( _indent, ' ' );
    for ( size_t i=0 ; i<_lines.size() ; ++i ) {
      if ((i == 0) and not _header.empty()) o << _header << " ";
      else                                  o << head;
      o << _lines[i] << endl;
    }
  }


  class TopologicalPairing {
    public:
                           TopologicalPairing    ( KatanaEngine*, Net* );
      bool                 doPairing             ();
      void                 _doSelfPairing        ();
      void                 _doDualPairing        ();
      AutoContactTerminal* _getSymmetricTerminal ( AutoContactTerminal* masterContact );
      Component*           _findMiddleComponent  ();
      void                 _associate            ();
    private:
      KatanaEngine*         _katana;
      AutoSegment*          _seed;
      DataSymmetric*        _data;
  };


  TopologicalPairing::TopologicalPairing ( KatanaEngine* katana, Net* net )
    : _katana      (katana)
    , _seed        (NULL)
    , _data        (NULL)
  {
    _data = _katana->getDataSymmetric( net );
    if (not _data)
      _data = _katana->addDataSymmetric( net );

    if (_data and (_data->getNet() != net) ) _data = NULL;
  }


  bool  TopologicalPairing::doPairing ()
  {
    if (not _data) return false;
    if (not _data->isValid()) return _data->isValid();

    DebugSession::open( _data->getNet(), 145, 146 );

  // Temporary.
  //_data->setSymAxis( _katana->getCell()->getAbutmentBox().getCenter().getX() );
    _data->setSymAxis( NetRoutingExtension::getSymAxis(_data->getNet()) );

    cmess2 << "     - Net: \"" << _data->getNet()->getName() << "\" ";
    cmess2 << "@" << DbU::getValueString(_data->getSymAxis()) << " ";
    cmess2 << (_data->isSymVertical() ? "Vertical" : "Horizontal") << " ";
    if (_data->getSymNet()) cmess2 << "(paired: \"" << _data->getSymNet()->getName() << "\")";
    else         cmess2 << "(self symmetric)";
    cmess2 << endl;
    
    if (_data->getSymNet()) _doDualPairing();
    else                    _doSelfPairing();

    if (_data->isValid()) _data->checkPairing();
    _associate();

    DebugSession::close();

    return _data->isValid();
  }


  void  TopologicalPairing::_doSelfPairing ()
  {
    Component* middle = _findMiddleComponent();

    AutoSegment* _seed = Session::base()->lookup( dynamic_cast<Segment*>( middle ) );
    if (_seed) {
      for ( AutoSegment* segment : _seed->getConnecteds(Flags::Source) ) 
        _data->addReference( segment );

      for ( AutoSegment* segment : _seed->getConnecteds(Flags::Target) )
        _data->addSymmetrical( segment );
    }
  }


  void  TopologicalPairing::_doDualPairing ()
  {
    for ( Contact* terminal : _data->getNet()->getContacts() ) {
      AutoContactTerminal* autoTerminal = dynamic_cast<AutoContactTerminal*>(Session::lookup( terminal ));
      if (autoTerminal) {
        if (not autoTerminal->isEndPoint()) continue;

        _seed = autoTerminal->getSegment();
        Flags flags = (_seed->getAutoSource() == autoTerminal) ? Flags::Target : Flags::Source;
        for ( AutoSegment* segment : _seed->getConnecteds(flags) )
          _data->addReference( segment );

        AutoContactTerminal* symTerminal = _getSymmetricTerminal( autoTerminal );
        if (not symTerminal) { _data->setValid( false ); break; }

        AutoSegment* symSeed = symTerminal->getSegment();
        if (not symSeed) { _data->setValid( false ); break; }

        flags = (symSeed->getAutoSource() == symTerminal) ? Flags::Target : Flags::Source;
        for ( AutoSegment* segment : symSeed->getConnecteds(flags) ) {
          _data->addSymmetrical( segment );
        }
        
        break;
      }
    }
  }


  AutoContactTerminal* TopologicalPairing::_getSymmetricTerminal ( AutoContactTerminal* masterContact )
  {
    Point  mirror      = _data->getSymmetrical( masterContact->getCenter() );
    GCell* mirrorGCell = _katana->getGCellUnder( mirror );
    if (not mirrorGCell) {
      cerr << Error( "getSymmetricTerminal() No GCell under symmetric position (%s,%s)."
                   , DbU::getValueString(mirror.getX()).c_str()
                   , DbU::getValueString(mirror.getY()).c_str()
                   ) << endl;
      _data->setValid( false );
      return NULL;
    }

    for ( AutoContact* mirrorContact : mirrorGCell->getContacts() ) {
      if (mirrorContact->getNet() == _data->getSymNet()) {
        AutoContactTerminal* symContact = dynamic_cast<AutoContactTerminal*>( mirrorContact );
        if (symContact) return symContact;
      }
    }

    cerr << Error( "getSymmetricTerminal() Missing terminal contact in symmetric GCell.\n"
                   "        master:%s\n"
                   "        mirror:%s"
                 , getString(masterContact).c_str(), getString(mirrorGCell).c_str() 
                 ) << endl;

    _data->setValid( false );
    return NULL;
  }


  Component* TopologicalPairing::_findMiddleComponent ()
  {
    DbU::Unit        axis   = _data->getSymAxis();
    Component*       middle = NULL;

    if (_data->isSymVertical()) {
      for ( Component* component : _data->getNet()->getComponents() ) {
        Horizontal* horizontal = dynamic_cast<Horizontal*>(component);
        if (horizontal) {
          if ( (horizontal->getSourceX() < axis) and (axis < horizontal->getTargetX()) ) {
            if (not middle) middle = horizontal;
            else {
              cerr << Error( "::findMiddleComponent(): Multiple middle Horizontal candidates on \"%s\"\n"
                             "  %s\n"
                             "  %s"
                           , getString(_data->getNet()->getName()).c_str()
                           , getString(middle).c_str()
                           , getString(horizontal).c_str()
                           ) << endl;
              _data->setValid( false );
              break;
            }
          }
        }

        Vertical* vertical = dynamic_cast<Vertical*>(component);
        if (vertical) {
          if (vertical->getSourceX() == axis) {
            if (not middle) middle = vertical;
            else {
              cerr << Error( "::findMiddleComponent(): Multiple middle Vertical candidates on \"%s\"\n"
                             "  %s\n"
                             "  %s"
                           , getString(_data->getNet()->getName()).c_str()
                           , getString(middle).c_str()
                           , getString(vertical).c_str()
                           ) << endl;
              _data->setValid( false );
              break;
            }
          }
        }

        RoutingPad* rp = dynamic_cast<RoutingPad*>(component);
        if (rp) {
          if ( (rp->getSourcePosition().getX() < axis) and (axis < rp->getTargetPosition().getX()) ) {
            if (not middle) middle = rp;
            else {
              cerr << Error( "::findMiddleComponent(): Multiple middle Horizontal candidates on \"%s\"\n"
                             "  %s\n"
                             "  %s"
                           , getString(_data->getNet()->getName()).c_str()
                           , getString(middle).c_str()
                           , getString(rp).c_str()
                           ) << endl;
              _data->setValid( false );
              break;
            }
          }
        }
      }
    } else {
      for ( Component* component : _data->getNet()->getComponents() ) {
        Vertical* vertical = dynamic_cast<Vertical*>(component);
        if (vertical) {
          if ( (vertical->getSourceY() < axis) and (axis > vertical->getTargetY()) ) {
            if (not middle) middle = vertical;
            else {
              cerr << Error( "::findMiddleComponent(): Multiple middle Vertical candidates on \"%s\"\n"
                             "  %s\n"
                             "  %s"
                           , getString(_data->getNet()->getName()).c_str()
                           , getString(middle).c_str()
                           , getString(vertical).c_str()
                           ) << endl;
              _data->setValid( false );
              break;
            }
          }
        }

        Horizontal* horizontal = dynamic_cast<Horizontal*>(component);
        if (horizontal) {
          if (horizontal->getSourceY() == axis) {
            if (not middle) middle = horizontal;
            else {
              cerr << Error( "::findMiddleComponent(): Multiple middle Horizontal candidates on \"%s\"\n"
                             "  %s\n"
                             "  %s"
                           , getString(_data->getNet()->getName()).c_str()
                           , getString(middle).c_str()
                           , getString(horizontal).c_str()
                           ) << endl;
              _data->setValid( false );
              break;
            }
          }
        }

        RoutingPad* rp = dynamic_cast<RoutingPad*>(component);
        if (rp) {
          if ( (rp->getSourcePosition().getY() < axis) and (axis > rp->getTargetPosition().getY()) ) {
            if (not middle) middle = rp;
            else {
              cerr << Error( "::findMiddleComponent(): Multiple middle Horizontal candidates on \"%s\"\n"
                             "  %s\n"
                             "  %s"
                           , getString(_data->getNet()->getName()).c_str()
                           , getString(middle).c_str()
                           , getString(rp).c_str()
                           ) << endl;
              _data->setValid( false );
              break;
            }
          }
        }
      }
    }

    return middle;
  }


  void  TopologicalPairing::_associate ()
  {
    cdebug_log(144,1) << "TopologicalPairing::_associate()" << endl;

  //cmess1 << "     - Associating symmetrics." << endl;

    if (not _data->isValid()) return;

    const DataSymmetric::Paireds& paireds = _data->getPaireds();
    for ( auto sympair : paireds ) {
      if (not sympair[0]->isCanonical() or not sympair[1]->isCanonical()) continue;

      TrackElement* trackSegment0 = Session::lookup( sympair[0] );
      TrackElement* trackSegment1 = Session::lookup( sympair[1] );

      if (not trackSegment0 or not trackSegment1) continue;

      trackSegment0->setSymmetric( trackSegment1 );
      trackSegment1->setSymmetric( trackSegment0 );
    }

    cdebug_tabw(144,-1);
  }

} // Anonymous namespace.


namespace Katana {

  using namespace std;


  void  KatanaEngine::pairSymmetrics ()
  {
    for ( Net* net : getCell()->getNets() ) {
      if (not NetRoutingExtension::isSymmetric(net)) continue;
      TopologicalPairing(this,net).doPairing();
    }
  }



} // Katana namespace.