// -*- C++ -*-
//
// This file is part of the Coriolis Software.
// Copyright (c) Sorbonne Université 2022-2022, 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  :       "./NetBuilderHybridVH.cpp"                 |
// +-----------------------------------------------------------------+


#include <cstdlib>
#include <sstream>
#include "hurricane/Bug.h"
#include "hurricane/Breakpoint.h"
#include "hurricane/Error.h"
#include "hurricane/Warning.h"
#include "hurricane/DebugSession.h"
#include "hurricane/Layer.h"
#include "hurricane/BasicLayer.h"
#include "hurricane/RegularLayer.h"
#include "hurricane/Technology.h"
#include "hurricane/DataBase.h"
#include "hurricane/Net.h"
#include "hurricane/NetExternalComponents.h"
#include "hurricane/NetRoutingProperty.h"
#include "hurricane/RoutingPad.h"
#include "hurricane/RoutingPads.h"
#include "hurricane/Pin.h"
#include "hurricane/Pad.h"
#include "hurricane/Plug.h"
#include "hurricane/Cell.h"
#include "hurricane/Instance.h"
#include "hurricane/Vertical.h"
#include "hurricane/Horizontal.h"
#include "crlcore/AllianceFramework.h"
#include "crlcore/RoutingGauge.h"
#include "crlcore/Measures.h"
#include "anabatic/AutoContactTerminal.h"
#include "anabatic/AutoContactTurn.h"
#include "anabatic/AutoContactHTee.h"
#include "anabatic/AutoContactVTee.h"
#include "anabatic/AutoSegment.h"
#include "anabatic/NetBuilderHybridVH.h"
#include "anabatic/AnabaticEngine.h"


namespace Anabatic {

  using std::swap;
  using Hurricane::Error;
  using Hurricane::Pin;

  
  NetBuilderHybridVH::NetBuilderHybridVH ()
    : NetBuilder()
  { }


  NetBuilderHybridVH::~NetBuilderHybridVH () { }


  string  NetBuilderHybridVH::getStyle ()
  { return "VH,2RL"; }

  
  void  NetBuilderHybridVH::doRp_AutoContacts ( GCell*        gcell
                                              , Component*    rp
                                              , AutoContact*& source
                                              , AutoContact*& target
                                              , uint64_t      flags
                                              )
  {
    throw Error ( "%s::dpRp_AutoContacts() method must never be called.", getTypeName().c_str() );
  }


  AutoContact* NetBuilderHybridVH::doRp_Access ( GCell* gcell, Component* rp, uint64_t flags )
  {
    cdebug_log(145,1) << getTypeName() << "::doRp_Access()" << endl;
    cdebug_log(145,0) << rp << endl;

    const Layer* rpLayer   = rp->getLayer();
    const Layer* viaLayer  = Session::getDContactLayer();
    DbU::Unit    viaSide   = Session::getDContactWidth();
    Point        position  = rp->getCenter();
    AutoContact* rpContact = AutoContactTerminal::create( gcell, rp, rpLayer, position, viaSide, viaSide );

    cdebug_tabw(145,-1);
    return rpContact;
  }


  AutoContact* NetBuilderHybridVH::doRp_AccessNorthSouthPin ( GCell* gcell, RoutingPad* rp )
  {
    cdebug_log(145,1) << getTypeName() << "::doRp_AccessNorthSouthPin() " << rp << endl;

    AutoContact* rpContact = doRp_Access( gcell, rp, NoFlags );
    AutoContact* turn      = NULL;

    Net* net = rp->getNet();
    Pin* pin = dynamic_cast<Pin*>( rp->getOccurrence().getEntity() );
    Pin::AccessDirection pinDir = pin->getAccessDirection();
    if (pinDir == Pin::AccessDirection::NORTH) {
      turn = AutoContactTurn::create( gcell, net, Session::getRoutingLayer(0) );
      AutoSegment* segment = AutoSegment::create( rpContact, turn, Flags::Vertical );
      segment->setAxis( rp->getX(), Flags::Force );
      segment->setFlags( AutoSegment::SegFixed|AutoSegment::SegFixedAxis );
      rpContact = turn;

      turn    = AutoContactTurn::create( gcell, net, Session::getContactLayer(0) );
      segment = AutoSegment::create( rpContact, turn, Flags::Horizontal );
      rpContact = turn;

      DbU::Unit axis = gcell->getYMax() - Session::getDHorizontalPitch();
      cdebug_log(145,0) << "axis:" << DbU::getValueString(axis) << endl;
      
      segment->setAxis( axis, Flags::Force );
    //segment->setFlags( AutoSegment::SegFixed|AutoSegment::SegFixedAxis );
      cdebug_log(145,0) << segment << endl;
    } else {
      turn = rpContact;
    }

    cdebug_tabw(145,-1);
    return turn;
  }


  AutoContact* NetBuilderHybridVH::doRp_AccessEastWestPin ( GCell* gcell, RoutingPad* rp )
  {
    cdebug_log(145,1) << getTypeName() << "::doRp_AccessEastWestPin() " << rp << endl;

    Net*         net            = rp->getNet();
    Pin*         pin            = dynamic_cast<Pin*>( rp->getOccurrence().getEntity() );
    Pin::AccessDirection pinDir = pin->getAccessDirection();
    AutoContact* rpContact      = doRp_Access( gcell, rp, NoFlags );
    AutoContact* turn           = NULL;
    AutoSegment* segment        = NULL;

    turn    = AutoContactTurn::create( gcell, net, Session::getContactLayer(0) );
    segment = AutoSegment::create( rpContact, turn, Flags::Horizontal );
    segment->setAxis( pin->getCenter().getY(), Flags::Force );

    rpContact = turn;
    turn    = AutoContactTurn::create( gcell, net, Session::getContactLayer(0) );
    segment = AutoSegment::create( rpContact, turn, Flags::Vertical );

    DbU::Unit axis = 0;
    if (pinDir == Pin::AccessDirection::EAST)
      axis = gcell->getXMax() - Session::getDVerticalPitch();
    else
      axis = gcell->getXMin() + Session::getDVerticalPitch();
    segment->setAxis( axis );

    cdebug_tabw(145,-1);
    return turn;
  }


  bool  NetBuilderHybridVH::doRp_xG_1M1 ( RoutingPad* rp )
  {
    cdebug_log(145,1) << getTypeName() << "::doRp_xG_1M1()" << endl;

    AutoContact* swContact = nullptr;
    if (west() or south()) {
      AutoContact* termContact = doRp_Access( getGCell(), rp, NoFlags );
      if (west() and south()) {
        swContact = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
        AutoSegment::create( termContact, swContact, Flags::Vertical );
      } else {
        if (west()) {
          swContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
          AutoSegment::create( termContact, swContact, Flags::Vertical );
        } else
          swContact = termContact;
      }
      setSouthWestContact( swContact );
    }

    AutoContact* neContact = nullptr;
    if (east() or north()) {
      AutoContact* termContact = doRp_Access( getGCell(), rp, NoFlags );
      if (east() and north()) {
        neContact = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
        AutoSegment::create( termContact, neContact, Flags::Vertical );
      } else {
        if (east()) {
          neContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
          AutoSegment::create( termContact, neContact, Flags::Vertical );
        } else
          neContact = termContact;
      }
      setNorthEastContact( neContact );
    }

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_xG_1M1 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_xG_1M1()" << endl;

    doRp_xG_1M1( getRoutingPads()[0] );

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::doRp_xG_xM1_xM3 ( const vector<RoutingPad*>& rps )
  {
    cdebug_log(145,1) << getTypeName() << "::doRp_xG_xM1()" << endl;

    AutoContact* prevContact = nullptr;
    AutoContact* currContact = nullptr;

    for ( size_t i=0 ; i<rps.size() ; ++i ) {
      prevContact = currContact;
      AutoContact* currTerm = doRp_Access( getGCell(), rps[i], NoFlags );

      if (i == 0) {
        if (west() or south()) {
          AutoContact* swContact = nullptr;
          currContact = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
          if (west() and south()) {
            swContact = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
          } else {
            if (west())
              swContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
            else
              swContact = currContact;
          }
          if (swContact != currContact)
            AutoSegment::create( currContact, swContact, Flags::Vertical );
          setSouthWestContact( swContact );
        } else {
          currContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
        }
        AutoSegment::create( currTerm, currContact, Flags::Vertical );
        continue;
      }

      if (i+1 == rps.size()) {
        if (east() or north()) {
          AutoContact* neContact = nullptr;
          currContact = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
          if (east() and north()) {
            neContact = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
          } else {
            if (east())
              neContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
            else
              neContact = currContact;
          }
          if (neContact != currContact)
            AutoSegment::create( currContact, neContact, Flags::Vertical );
          setNorthEastContact( neContact );
        } else {
          currContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
        }
        AutoSegment::create( currTerm, currContact, Flags::Vertical );
      } else {
        currContact = AutoContactHTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
      }

      AutoSegment::create( prevContact, currContact, Flags::Horizontal );
    }

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_xG_xM1_xM3 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_xG_xM1()" << endl;

    sortRpByX( getRoutingPads(), NoFlags ); // increasing X.
    doRp_xG_xM1_xM3( getRoutingPads() );

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_1G_xM1 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_1G_xM1()" << endl;

    if (getConnexity().fields.M1 == 1)
      _do_xG_1M1();
    else
      _do_xG_xM1_xM3();

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_xG ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_xG()" << endl;

    const Layer* viaLayer = Session::getContactLayer( 0 );
    if (getConnexity().fields.globals == 2) {
      setBothCornerContacts( AutoContactTurn::create( getGCell(), getNet(), viaLayer ) );
    } else if (getConnexity().fields.globals == 3) {
      if (east() and west()) {
        setSouthWestContact( AutoContactTurn::create( getGCell(), getNet(), viaLayer ) );
        setNorthEastContact( AutoContactVTee::create( getGCell(), getNet(), viaLayer ) );
        if (south()) swapCornerContacts();

        AutoSegment::create( getSouthWestContact(), getNorthEastContact(), Flags::Vertical );
      } else {
        setSouthWestContact( AutoContactTurn::create( getGCell(), getNet(), viaLayer ) );
        setNorthEastContact( AutoContactHTee::create( getGCell(), getNet(), viaLayer ) );
        if (west()) swapCornerContacts();

        AutoSegment::create( getSouthWestContact(), getNorthEastContact(), Flags::Horizontal );
      }
    } else { // fields.globals == 4.
      AutoContact* turn = AutoContactTurn::create( getGCell(), getNet(), viaLayer );
      setSouthWestContact( AutoContactHTee::create( getGCell(), getNet(), viaLayer ) );
      setNorthEastContact( AutoContactVTee::create( getGCell(), getNet(), viaLayer ) );
      AutoSegment::create( getSouthWestContact(), turn, Flags::Horizontal );
      AutoSegment::create( turn, getNorthEastContact(), Flags::Vertical );
    } 
    
    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_1G_1PinM1 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_1G_1PinM1() [Managed Configuration - Optimized] " << getTopology() << endl;

    AutoContact* rpContact = doRp_AccessNorthSouthPin( getGCell(), getRoutingPads()[0] );

    AutoContact* turn1 = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
    AutoSegment::create( rpContact, turn1, Flags::Vertical );

    if (north() or south()) {
      AutoContact* turn2 = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
      AutoSegment::create( turn1, turn2, Flags::Horizontal );
      turn1 = turn2;
    }
    setBothCornerContacts( turn1 );

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_2G_1PinM1 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_2G_1PinM1() [Managed Configuration - Optimized] " << getTopology() << endl;

    AutoContact* rpContact = doRp_AccessNorthSouthPin( getGCell(), getRoutingPads()[0] );
    AutoContact* tee       = nullptr;

    if (east() and west()) {
      tee = AutoContactHTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
    } else {
      tee = AutoContactVTee::create( getGCell(), getNet(), Session::getContactLayer(0) );
    }
    AutoSegment::create( rpContact, tee, Flags::Vertical );
    setBothCornerContacts( tee );

    cdebug_tabw(145,-1);
    return true;
  }
  

  bool  NetBuilderHybridVH::doRp_1G_1PinM2 ( RoutingPad* rp )
  {
    cdebug_log(145,1) << getTypeName() << "::doRp_1G_1PinM2() [Managed Configuration - Optimized] " << getTopology() << endl;

    AutoContact* rpContact = doRp_AccessEastWestPin( getGCell(), rp );
    AutoContact* turn1     = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
    AutoSegment::create( rpContact, turn1, Flags::Horizontal );

    if (east() or west()) {
      AutoContact* turn2 = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
      AutoSegment::create( turn1, turn2, Flags::Vertical );
      turn1 = turn2;
    }
    setBothCornerContacts( turn1 );

    cdebug_tabw(145,-1);
    return true;
  }
  

  bool  NetBuilderHybridVH::_do_1G_1PinM2 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_1G_1PinM2() [Managed Configuration - Optimized] " << getTopology() << endl;

    doRp_1G_1PinM2( getRoutingPads()[0] );

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_xG_1PinM2 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_xG_1PinM2()" << endl;

    AutoContact* rpContact = doRp_AccessEastWestPin( getGCell(), getRoutingPads()[0] );
    AutoContact* neContact = nullptr;
    AutoContact* swContact = nullptr;

    const Layer* viaLayer = Session::getContactLayer( 0 );
    if (getConnexity().fields.globals == 2) {
      if (north() and south())
        neContact = AutoContactVTee::create( getGCell(), getNet(), viaLayer );
      else
        neContact = AutoContactHTee::create( getGCell(), getNet(), viaLayer );
      AutoSegment::create( rpContact, neContact, Flags::Horizontal );
      setBothCornerContacts( neContact );
    } else if (getConnexity().fields.globals == 3) {
      swContact = AutoContactVTee::create( getGCell(), getNet(), viaLayer );
      neContact = AutoContactVTee::create( getGCell(), getNet(), viaLayer );
      if (west())
        AutoSegment::create( rpContact, neContact, Flags::Horizontal );
      else
        AutoSegment::create( rpContact, swContact, Flags::Horizontal );
      AutoSegment::create( swContact, neContact, Flags::Vertical );
      setNorthEastContact( neContact );
      setSouthWestContact( swContact );
    } 
    
    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_1G_xM1_1PinM2 ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_1G_xM1_1PinM2() [Managed Configuration - Optimized] " << getTopology() << endl;

    sortRpByX( getRoutingPads(), NoFlags ); // increasing X.

    vector<RoutingPad*> rpsM1;
    RoutingPad*         rpM2  = nullptr;
    for ( RoutingPad* rp : getRoutingPads() ) {
      if (dynamic_cast<Pin*>(rp->getOccurrence().getEntity())) rpM2 = rp;
      else rpsM1.push_back( rp );
    }
    if (rpsM1.size() > 1)
      doRp_xG_xM1_xM3( rpsM1 );
    else
      doRp_xG_1M1( rpsM1[0] );


    Pin*         pin            = dynamic_cast<Pin*>( rpM2->getOccurrence().getEntity() );
    Pin::AccessDirection pinDir = pin->getAccessDirection();
    AutoContact* rpContact      = doRp_AccessEastWestPin( getGCell(), rpM2 );
    AutoContact* rpM1Contact    = nullptr;
    if (pinDir == Pin::AccessDirection::EAST)
      rpM1Contact = doRp_Access( getGCell(), rpsM1.back(), NoFlags );
    else
      rpM1Contact = doRp_Access( getGCell(), rpsM1.front(), NoFlags );
    AutoContact* turn = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
    AutoSegment::create( rpM1Contact, turn, Flags::Vertical );
    AutoSegment::create( rpContact  , turn, Flags::Horizontal );

    cdebug_tabw(145,-1);
    return true;
  }


  bool  NetBuilderHybridVH::_do_1G_1M1     () { return false; }
  bool  NetBuilderHybridVH::_do_2G_1M1     () { return false; }
  // bool  NetBuilderHybridVH::_do_xG_1Pad    () { return false; }
  // bool  NetBuilderHybridVH::_do_xG_xM2     () { return false; }
  // bool  NetBuilderHybridVH::_do_1G_1M3     () { return false; }
  // bool  NetBuilderHybridVH::_do_xG_xM3     () { return false; }
  // bool  NetBuilderHybridVH::_do_xG_1M1_1M2 () { return false; }
  // bool  NetBuilderHybridVH::_do_4G_1M2     () { return false; }
  bool  NetBuilderHybridVH::_do_2G         () { return false; }


  bool  NetBuilderHybridVH::_do_globalSegment ()
  {
    cdebug_log(145,1) << getTypeName() << "::_do_globalSegment()" << endl;
    cdebug_log(145,0) << "getSourceFlags():" << getSourceFlags()
                      << " getFlags():" << getFlags() << endl;

    if (getSourceContact()) {
      uint64_t     segmentBound  = getSegmentHookType( getFromHook() );
      AutoContact* targetContact
        = (segmentBound & (NorthBound|EastBound)) ? getNorthEastContact() : getSouthWestContact() ;
      if (not getFromHook()) cerr << "getFromHook() is NULL !" << endl;

      AutoContact* sourceContact = getSourceContact();
      if (segmentBound & (NorthBound|SouthBound)) {
        AutoContact* turn = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
        AutoSegment::create( sourceContact, turn, Flags::Vertical );
        sourceContact = AutoContactTurn::create( getGCell(), getNet(), Session::getContactLayer(0) );
        AutoSegment::create( sourceContact, turn, Flags::Horizontal );
      }
      Segment*     baseSegment   = static_cast<Segment*>( getFromHook()->getComponent() );
      AutoSegment* globalSegment = AutoSegment::create( sourceContact
                                                      , targetContact
                                                      , baseSegment
                                                      );
      globalSegment->setFlags( (getDegree() == 2) ? AutoSegment::SegBipoint : 0 );
      cdebug_log(145,0) << "Create global segment: " << globalSegment << endl;
  
    // HARDCODED VALUE.
      if ( (getTopology() & Global_Fixed) and (globalSegment->getAnchoredLength() < 2*Session::getSliceHeight()) )
        addToFixSegments( globalSegment );
        
      if (getConnexity().fields.globals < 2) {
        cdebug_tabw(145,-1);
        return false;
      }
    } else
      setFromHook( NULL );
    
    push( east (), getNorthEastContact() );
    push( west (), getSouthWestContact() );
    push( north(), getNorthEastContact() );
    push( south(), getSouthWestContact() );

    cdebug_tabw(145,-1);
    return true;
  }


  void  NetBuilderHybridVH::singleGCell ( AnabaticEngine* anbt, Net* net )
  {
    cdebug_log(145,1) << getTypeName() << "::singleGCell() " << net << endl;

    vector<RoutingPad*> rps;
    for ( RoutingPad* irp : net->getRoutingPads() )
      rps.push_back( irp );

    if (rps.size() < 2) {
      cerr << Error( "%s::singleGCell(): For %s, less than two Plugs/Pins (%d)."
                   , getTypeName().c_str()
                   , getString(net).c_str()
                   , rps.size() ) << endl;
      cdebug_tabw(145,-1);
      return;
    }

    sortRpByX( rps, NetBuilder::NoFlags ); // increasing X.

    GCell* gcell1 = anbt->getGCellUnder( rps.front()->getCenter() );
    GCell* gcell2 = anbt->getGCellUnder( rps.back ()->getCenter() );

    if (not gcell1) {
      cerr << Error( "No GCell under %s.", getString(rps.front()).c_str() ) << endl;
      cdebug_tabw(145,-1);
      return;
    }
    if (gcell1 != gcell2) {
      cerr << Error( "Not under a single GCell %s.", getString(rps.back()).c_str() ) << endl;
      cdebug_tabw(145,-1);
      return;
    }

    AutoContact* prevContact = nullptr;
    AutoContact* currContact = nullptr;

    for ( size_t i=0 ; i<rps.size() ; ++i ) {
      prevContact = currContact;
      AutoContact* currTerm = doRp_Access( gcell1, rps[i], NoFlags );

      if (i == 0) {
        currContact = AutoContactTurn::create( gcell1, net, Session::getContactLayer(0) );
        AutoSegment::create( currTerm, currContact, Flags::Vertical );
        continue;
      }

      if (i+1 == rps.size()) {
        currContact = AutoContactTurn::create( gcell1, net, Session::getContactLayer(0) );
      } else {
        currContact = AutoContactHTee::create( gcell1, net, Session::getContactLayer(0) );
      }
      AutoSegment::create( currTerm   , currContact, Flags::Vertical );
      AutoSegment::create( prevContact, currContact, Flags::Horizontal );
    }
    
    cdebug_tabw(145,-1);
  }


  string  NetBuilderHybridVH::getTypeName () const
  { return "NetBuilderHybridVH"; }


}  // Anabatic namespace.