// -*- 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                               |
// |          Alliance / Hurricane  Interface                        |
// |                                                                 |
// |  Author      :                    Jean-Paul CHAPUT              |
// |  E-mail      :       Jean-Paul.Chaput@asim.lip6.fr              |
// | =============================================================== |
// |  C++ Module  :  "./Environnment.cpp"                            |
// +-----------------------------------------------------------------+


#include <cctype>
#include <cstdlib>
#include <map>
#include <iomanip>
#include "crlcore/Utilities.h"
#include "crlcore/Environment.h"
#include "crlcore/AllianceFramework.h"


namespace {


  using namespace CRL;


  const char* badRegex =
    "Environment::Environment() :\n"
    "  Invalid regular expression for %s :\n"
    "    %s.\n";
  const char* badSCALE_X =
    "Environment::Environment() :\n"
    "    Only three values are allowed for SCALE : 1, 10 & 100.\n"
    "    (current value := %ld)";
  const char* badEnvironment =
    "Environment::Environment() :\n"
    "    %s logical format \"%s\" incoherent with physical format \"%s\".\n";


} // End of anonymous namespace.


namespace CRL {

  using namespace std;
  using Hurricane::Initializer;
  using Hurricane::JsonTypes;


// -------------------------------------------------------------------
// Class  :  "Environment".

  Environment::Environment ()
    : _CORIOLIS_TOP       (CORIOLIS_TOP)
    , _displayStyle       ()
    , _SCALE_X            (10)
    , _IN_LO              ("vst")
    , _IN_PH              ("ap")
    , _OUT_LO             ("vst")
    , _OUT_PH             ("ap")
    , _CATALOG            ("CATAL")
    , _POWER              ("vdd")
    , _GROUND             ("vss")
    , _CLOCK              ("^ck$")
    , _BLOCKAGE           ("^blockage$")
    , _pad                ("^.*_px$")
    , _LIBRARIES          ()
    , _PowerRegex         (new regex_t)
    , _GroundRegex        (new regex_t)
    , _ClockRegex         (new regex_t)
    , _BlockageRegex      (new regex_t)
    , _padRegex           (new regex_t)
  {
    setPOWER   ( "vdd" );
    setGROUND  ( "vss" );
    setCLOCK   ( "^ck$" );
    setBLOCKAGE( "^blockage$" );
    setPad     ( "^.*_px$" );

    _LIBRARIES.append( ".", "working" );
  }


  Environment::~Environment ()
  {
    regfree( _PowerRegex    );
    regfree( _GroundRegex   );
    regfree( _ClockRegex    );
    regfree( _BlockageRegex );
    regfree( _padRegex      );
    delete _PowerRegex;
    delete _GroundRegex;
    delete _ClockRegex;
    delete _BlockageRegex;
    delete _padRegex;
  }


  bool  Environment::isPOWER ( const char* name ) const
  {
    if (not _PowerRegex) return false;
    return regexec( _PowerRegex, name, 0, NULL, 0 ) == 0;
  }


  bool  Environment::isGROUND ( const char* name ) const
  {
    if (not _GroundRegex) return false;
    return regexec ( _GroundRegex, name, 0, NULL, 0 ) == 0;
  }


  bool  Environment::isCLOCK ( const char* name ) const
  {
    if (not _ClockRegex) return false;
    return regexec( _ClockRegex, name, 0, NULL, 0 ) == 0;
  }


  bool  Environment::isBLOCKAGE ( const char* name ) const
  {
    if (not _BlockageRegex) return false;
    return regexec ( _BlockageRegex, name, 0, NULL, 0 ) == 0;
  }


  bool  Environment::isPad ( const char* name ) const
  {
    if (not _padRegex) return false;
    return regexec ( _padRegex, name, 0, NULL, 0 ) == 0;
  }


  void  Environment::setPOWER ( const char* value )
  {
    _POWER = value;
    _setRegex ( _PowerRegex , _POWER , "Power" );
  }


  void  Environment::setGROUND ( const char* value )
  {
    _GROUND = value;
    _setRegex ( _GroundRegex , _GROUND , "Ground" );
  }


  void  Environment::setCLOCK ( const char* value )
  {
    _CLOCK = value;
    _setRegex ( _ClockRegex , _CLOCK , "Clock" );
  }


  void  Environment::setBLOCKAGE ( const char* value )
  {
    _BLOCKAGE = value;
    _setRegex ( _BlockageRegex , _BLOCKAGE , "Blockage" );
  }


  void  Environment::setPad ( const char* value )
  {
    _pad = value;
    _setRegex ( _padRegex , _pad , "Pad" );
  }


  string  Environment::getPrint () const
  {
    ostringstream s;

    s << "\n"
      << "  o  Hurricane/Alliance environment.\n";

    s << "     o  Libraries.\n"
      << "        - Catalog:  \"" << _CATALOG << "\"\n"
      << "        - Working Library:\n"
      << "          [ 0]:\"" << _LIBRARIES[0].getPath() << "\"\n"
      << "        - System Libraries:\n";

    if ( _LIBRARIES.getSize() <= 1 ) {
      s << "          <not set or empty>.\n";
    } else {
      for ( size_t i = 1; i < _LIBRARIES.getSize() ; i++ ) {
        s << "          [" << setw(2) << i << "]:\"" << _LIBRARIES[i].getPath() << "\"\n";
      }
    }

    s << "     o  I/O Formats.\n" 
      << Dots::asULong ( "        - Scale", _SCALE_X ) << "\n"
      << Dots::asString( "        - Input, logical"  , _IN_LO    )  << "\n"
      << Dots::asString( "        - Input, physical" , _IN_PH    )  << "\n"
      << Dots::asString( "        - Output, logical" , _OUT_LO   )  << "\n"
      << Dots::asString( "        - Output, physical", _OUT_PH   )  << "\n"
      << "     o  Special Signals.\n"
      << Dots::asString( "        - Power Signal"    , _POWER    )  << "\n"
      << Dots::asString( "        - Ground Signal"   , _GROUND   )  << "\n"
      << Dots::asString( "        - Clock Signal"    , _CLOCK    )  << "\n"
      << Dots::asString( "        - Blockages"       , _BLOCKAGE )  << "\n"
      << "     o  Special Cells.\n"
      << Dots::asString( "        - Pads"            , _pad      )  << "\n\n";

    return s.str();
  }


  void  Environment::_setRegex ( regex_t*& regex, const string& pattern, const char* name )
  {
    char regexError[1024];
    int  regexCode;

    if ( (regexCode = regcomp(regex,getString(pattern).c_str(),REG_EXTENDED|REG_NOSUB)) ) {
      regerror( regexCode, regex, regexError, 1024 );
      throw Error ( badRegex, name, regexError );
    }
  }


  void  Environment::validate () const
  {
    switch ( _SCALE_X ) {
      case 1:   break;
      case 10:  break;
      case 100: break;
      default:
        throw Error ( badSCALE_X , _SCALE_X );
    }

    bool  coherency = false;
    if      (  ( _IN_LO == "vst"   ) && ( _IN_PH == "ap" ) ) coherency = true;
    else if (  ( _IN_LO == "spi"   ) && ( _IN_PH == "ap" ) ) coherency = true;
    else if (  ( _IN_LO == "bench" ) && ( _IN_PH == "ap" ) ) coherency = true;
    else if (  ( _IN_LO == "def"   ) && ( _IN_PH == "def") ) coherency = true;
    else if (  ( _IN_LO == "aux"   ) && ( _IN_PH == "aux") ) coherency = true;
    else if (  ( _IN_LO == "oa"    ) && ( _IN_PH == "oa" ) ) coherency = true;
    if ( !coherency )
      throw Error ( badEnvironment, "Input", _IN_LO.c_str(), _IN_PH.c_str() );

    coherency = false;
    if      (  ( _OUT_LO == "vst" ) && ( _OUT_PH == "ap" ) ) coherency = true;
    else if (  ( _OUT_LO == "spi" ) && ( _OUT_PH == "ap" ) ) coherency = true;
    else if (  ( _OUT_LO == "vst" ) && ( _OUT_PH == "gds") ) coherency = true;
    else if (  ( _OUT_LO == "def" ) && ( _OUT_PH == "def") ) coherency = true;
    else if (  ( _OUT_LO == "aux" ) && ( _OUT_PH == "aux") ) coherency = true;
    else if (  ( _OUT_LO == "oa"  ) && ( _OUT_PH == "oa" ) ) coherency = true;
    if ( !coherency )
      throw Error ( badEnvironment, "Output", _OUT_LO.c_str(), _OUT_PH.c_str() );
  }


  const char* Environment::getEnv ( const char* variable, const char* defaultValue )
  {
    char *value;

    value = getenv ( variable );
    if ( value == NULL ) return defaultValue;

    return value;
  }

  void  Environment::setWORKING_LIBRARY ( const char* value )
  {
    _LIBRARIES.replace( value, "working", 0 );
    AllianceFramework::get()->createLibrary( _LIBRARIES[0].getPath(), AllianceFramework::CreateLibrary );
  }


  void  Environment::addSYSTEM_LIBRARY ( const char* value, const char* libName, unsigned int mode )
  {
    AllianceFramework* af     = AllianceFramework::get();
    unsigned int       afMode = AllianceFramework::CreateLibrary;
    size_t             ilib   = 0;
    
    if (mode == Append) afMode |= AllianceFramework::AppendLibrary;
    
    if ((mode == Prepend) or (mode == Append)) {
      size_t duplicate = _LIBRARIES.hasLib(libName);
      if (duplicate != SearchPath::npos) _LIBRARIES.remove( duplicate );

      if (mode == Prepend) ilib = _LIBRARIES.prepend(value,libName);
      if (mode == Append ) ilib = _LIBRARIES.append (value,libName);
    } else {
      string newLibName = libName;
      for ( ; ilib < _LIBRARIES.getSize() ; ++ilib ) {
        if (newLibName == _LIBRARIES[ilib].getName()) {
          _LIBRARIES.replace ( value, newLibName, ilib );
          break;
        }
      }
      if (ilib == _LIBRARIES.getSize())
        _LIBRARIES.append (value,libName);
    }

    af->createLibrary( _LIBRARIES[ilib].getPath(), afMode );
  }


  string  Environment::getLIBRARYPath ( size_t i )
  {
    if (i >= _LIBRARIES.getSize()) return "";
    return _LIBRARIES[i].getPath();
  }


  string  Environment::_getString () const
  { return "<Environment>"; }


  Record *Environment::_getRecord () const
  {
    Record* record = new Record ( "<Environment>" );
    record->add ( getSlot ( "_CORIOLIS_TOP"       , &_CORIOLIS_TOP        ) );
    record->add ( getSlot ( "_displayStyle"       , &_displayStyle        ) );
    record->add ( getSlot ( "_SCALE_X"            , &_SCALE_X             ) );
    record->add ( getSlot ( "_IN_LO"              , &_IN_LO               ) );
    record->add ( getSlot ( "_IN_PH"              , &_IN_PH               ) );
    record->add ( getSlot ( "_OUT_LO"             , &_OUT_LO              ) );
    record->add ( getSlot ( "_OUT_PH"             , &_OUT_PH              ) );
    record->add ( getSlot ( "_POWER"              , &_POWER               ) );
    record->add ( getSlot ( "_GROUND"             , &_GROUND              ) );
    record->add ( getSlot ( "_CLOCK"              , &_CLOCK               ) );
    record->add ( getSlot ( "_BLOCKAGE"           , &_BLOCKAGE            ) );
    record->add ( getSlot ( "_pad"                , &_pad                 ) );
    record->add ( getSlot ( "_LIBRARIES"          , &_LIBRARIES           ) );
    return record;
  }


  void  Environment::toJson ( JsonWriter* w ) const
  {
    w->startObject();
    jsonWrite( w, "@typename"    , _getTypeName() );
    jsonWrite( w, "_CORIOLIS_TOP", _CORIOLIS_TOP  );
    jsonWrite( w, "_displayStyle", _displayStyle  );
    jsonWrite( w, "_SCALE_X"     , _SCALE_X       );
    jsonWrite( w, "_IN_LO"       , _IN_LO         );
    jsonWrite( w, "_IN_PH"       , _IN_PH         );
    jsonWrite( w, "_OUT_LO"      , _OUT_LO        );
    jsonWrite( w, "_OUT_PH"      , _OUT_PH        );
    jsonWrite( w, "_POWER"       , _POWER         );
    jsonWrite( w, "_GROUND"      , _GROUND        );
    jsonWrite( w, "_CLOCK"       , _CLOCK         );
    jsonWrite( w, "_BLOCKAGE"    , _BLOCKAGE      );
    jsonWrite( w, "_pad"         , _pad           );
    jsonWrite( w, "_CATALOG"     , _CATALOG       );
    w->endObject();
  }


// -------------------------------------------------------------------
// Class  :  "JsonEnvironment".

  Initializer<JsonEnvironment>  jsonEnvironmentInit ( 0 );


  void  JsonEnvironment::initialize ()
  { JsonTypes::registerType( new JsonEnvironment (JsonWriter::RegisterMode) ); }


  JsonEnvironment::JsonEnvironment ( unsigned long flags )
    : JsonObject(flags)
  {
    add( "_CORIOLIS_TOP", typeid(string)  );
    add( "_displayStyle", typeid(string)  );
    add( "_SCALE_X"     , typeid(int64_t) );
    add( "_IN_LO"       , typeid(string)  );
    add( "_IN_PH"       , typeid(string)  );
    add( "_OUT_LO"      , typeid(string)  );
    add( "_OUT_PH"      , typeid(string)  );
    add( "_POWER"       , typeid(string)  );
    add( "_GROUND"      , typeid(string)  );
    add( "_BLOCKAGE"    , typeid(string)  );
    add( "_pad"         , typeid(string)  );
    add( "_CATALOG"     , typeid(string)  );
    add( "_CLOCK"       , typeid(string)  );
  }

  string  JsonEnvironment::getTypeName () const
  { return "Environment"; }


  JsonEnvironment* JsonEnvironment::clone ( unsigned long flags ) const
  { return new JsonEnvironment ( flags ); }


  void JsonEnvironment::toData ( JsonStack& stack )
  {
    check( stack, "JsonEnvironment::toData" );

    Environment* environement = AllianceFramework::get()->getEnvironment();
    environement->setDisplayStyle( get<string> (stack,"_displayStyle").c_str() );
    environement->setSCALE_X     ( get<int64_t>(stack,"_SCALE_X"     )         );
    environement->setIN_LO       ( get<string> (stack,"_IN_LO"       ).c_str() );
    environement->setIN_PH       ( get<string> (stack,"_IN_PH"       ).c_str() );
    environement->setOUT_LO      ( get<string> (stack,"_OUT_LO"      ).c_str() );
    environement->setOUT_PH      ( get<string> (stack,"_OUT_PH"      ).c_str() );
    environement->setPOWER       ( get<string> (stack,"_POWER"       ).c_str() );
    environement->setGROUND      ( get<string> (stack,"_GROUND"      ).c_str() );
    environement->setCLOCK       ( get<string> (stack,"_CLOCK"       ).c_str() );
    environement->setBLOCKAGE    ( get<string> (stack,"_BLOCKAGE"    ).c_str() );
    environement->setPad         ( get<string> (stack,"_pad"         ).c_str() );
    environement->setCATALOG     ( get<string> (stack,"_CATALOG"     ).c_str() );

    update( stack, environement );
  }


} // End of CRL namespace.
