//---------------------------------------------------------------------------
#include <sstream>
#include "blif_import.h"
#include "hub/err.h"
#include "hub/attr.h"
#include "hub/location.h"
#include "edif_err.h"
#include "hub/structural.h"
#include "hub/behavioral.h"
#include "hub/pla.h"
#include "hub/fsm.h"
#include "util/filename.h"

#include "kiss_import.h"
#include "pla_import.h"

using namespace hub;
// --------------------------------------------------------------------------------------
void BlifImpLang::enregister (void){
    registry_.push_back((new BlifImpLang ("BLIF"))->addSuffix (".blif"));
}
// --------------------------------------------------------------------------------------
void BlifImpLang::getDefaults (hub::Hub* h, const std::string& fn, Import::Destinations& dflt) {
    dflt.clear();
    dflt.push_back (Location (h, "Models"));            // Destination #0: Models
    dflt[0][Location::lib].oblig  (true); 
    dflt[0][Location::lib].content  ("work");
    dflt[0][Location::ent].locked (true);
    dflt[0][Location::arch].oblig (true); 
    dflt[0][Location::arch].content ("blif");

    dflt.push_back (Location (h, "PLA library"));      // Destination #1: Synth library location
    dflt[1][Location::lib].oblig  (true); 
    dflt[1][Location::lib].content ("pla_library");
    dflt[1][Location::ent].locked (true);
    dflt[1][Location::arch].oblig (true); 
    dflt[1][Location::arch].content ("blif");
    dflt[1][Location::design].locked (true);

    dflt.push_back (Location (h, "KISS library"));      // Destination #1: Synth library location
    dflt[2][Location::lib].oblig  (true); 
    dflt[2][Location::lib].content ("kiss_library");
    dflt[2][Location::ent].locked (true);
    dflt[2][Location::arch].oblig (true); 
    dflt[2][Location::arch].content ("kiss");
    dflt[2][Location::design].locked (true);
}
//---------------------------------------------------------------------------
void BlifImport::operator() (std::istream* in, const std::string& inFileName, Import::Destinations& dests) {
    dest_  = dests [0];
    synth_ = dests [1];
    kiss_  = dests [2];
    toplevel_ = &dests[0];
    dest_.theArch(NULL);
    parser_.run(*in, inFileName);
}
//---------------------------------------------------------------------------
void BlifImport::haveModel (const std::string& modelName) {
    // this->modelname = modelName;
    dest_.Reset();
    dest_ [Location::ent].content (modelName);
    dest_.FindOrCreate();                // check return code
    theArchImpl_ = dest_.theArch()->Create <StructArch> ();
    toplevel_->theEntity (dest_.theEntity());
}

//---------------------------------------------------------------------------
void BlifImport::endModel() {
}

//---------------------------------------------------------------------------
void BlifImport::haveInPorts(const std::list<std::string>& portNames) {
    PortProto* proto;
    std::string portName;

    std::list<std::string>::const_iterator it;

    for( it= portNames.begin(); it != portNames.end(); it++ ) {
        portName = *it;
        proto = dest_.theEntity()->CreateNextPort(portName, PortProto::inDir);
        if( !proto ) {
            throw RedefException (RedefReason ("port", portName),
			    LexPosition(parser_.l),
			    CircCtx (dest_.theLib(), dest_.theEntity(), dest_.theArch()));
        }

        // ------------- make an instantiation in the StructArch
        Port <StructArch>* archPort = theArchImpl_->CreatePort (proto);

        // ------------- create the net and connect
        Net* net = theArchImpl_->CreateNet(portName);
        if (!net) {
	        throw RedefException (RedefReason ("net", (*it)),
			      LexPosition(parser_.l),
			      CircCtx (dest_));
        }
        net->Join (archPort);
    }
}
//---------------------------------------------------------------------------
void BlifImport::haveOutPorts(const std::list<std::string>& portNames) {
    std::string portName;
    PortProto* proto;
    std::list<std::string>::const_iterator it;

    for( it = portNames.begin(); it != portNames.end(); it++ ) {
        portName = *it;
        proto = dest_.theEntity()->CreateNextPort (portName, PortProto::outDir);
        if (!proto) {
            throw RedefException (RedefReason ("port", portName),
         		    LexPosition(parser_.l),
			        CircCtx (dest_.theLib(), dest_.theEntity(), dest_.theArch()));
        }

        // ------------- make an instantiation in the StructArch
        Port <StructArch>* archPort = theArchImpl_->CreatePort (proto);

        // ------------- create the net and connect
        Net* net = theArchImpl_->CreateNet (portName);
        if (!net) {
	        throw RedefException (RedefReason ("net", portName),
			      LexPosition(parser_.l),
			      CircCtx (dest_));
        }
        net->Join (archPort);
    }
}
//---------------------------------------------------------------------------
void BlifImport::haveNames(std::list<std::string>& ports, std::list<std::string>& matrix) {
    Name portName;

    BehavArch* behavArch;
    BehavAgent* agent;
    list<std::string>::const_iterator i;
    std::string outputName;

    outputName = ports.back();
    ports.pop_back();
    agent = new PlaAgent(ports.size(), 1, std::string("fr"), matrix);
    synth_.theArch (synth_.theLib()->FindArch(*agent, "PLA fr"));
    if (synth_.theArch()) {
        synth_.theEntity (synth_.theArch()->Parent());
    }
    else {                          // have to create a new entity
        Name entName;
        entName.cons("pla", plaId++);
        synth_[Location::ent].content(entName);
        synth_.Reset();
        synth_.CreateUniqueEntity <BehavArch> ();
									// architecture & implementation
        behavArch = synth_.theArch()->Impl <BehavArch> ();
									// behavioral decription
        behavArch->description (agent->CreateDescr());
									// ports
        synth_.theEntity()->CreateNextPort ("O", PortProto::outDir);
        for( size_t n = 0; n < ports.size(); n++) {
            portName.cons ("I", n+1);
            synth_.theEntity()->CreateNextPort (portName, PortProto::inDir);
        }
    }
    // -----  instantiate architecture - AFTER all inputs and outputs are there
    Instance* inst = synth_.theEntity()->Instantiate (outputName, theArchImpl_, synth_.theArch());
    if (!inst) {
		 throw RedefException (RedefReason ("instance", outputName), 
				      LexPosition(parser_.l),
				      CircCtx (synth_));
    }
    // ------ if Y is the output name
    //	-     if net Y does not exist, create net Y
    //	-     connect THE output of the instance to net Y
    Net* netY = theArchImpl_->FindOrCreateNet (outputName);
    Port <Instance>* portY = inst->FindPort (0, PortProto::outDir);	// if !portY pruser
    if (!portY) {
        throw OthException (OthReason ("no output port","",""),
			  LexPosition(parser_.l),
    			  CircCtx (dest_.theLib(), dest_.theEntity()));
    }
    netY->Join (portY);
    // for each n-th input name X
    //	- if net X does not exist, create net X
    //	- connect net X to the n-th input of the instance
    size_t n;
    for (i=ports.begin(), n=0; i != ports.end(); i++,n++) {
        Net* netX= theArchImpl_->FindOrCreateNet (*i);
        Port <Instance>* portX = inst->FindPort (n, PortProto::inDir);
        if (!portX) {
            throw OthException (OthReason ("no input port","",""),
  			    LexPosition(parser_.l),
  			    CircCtx (dest_.theLib(), dest_.theEntity()));
        }
        netX->Join (portX);
    }
}
//---------------------------------------------------------------------------
void BlifImport::haveSubckt(std::list<SubcktRecord*>& subckts) {
    Name instName;
    unsigned int id = 1;
    std::string subcktName;
    // Entity* stdEnt;
    // Architecture*	stdArch = NULL;
    std::string formal;
    std::string actual;
    std::string data;
    unsigned int i;

    hub::Location entDest = dest_;      // subckts are in the same library and of the same 
    hub::Location parentDest = dest_;   // parent identified only by name

    std::list<SubcktRecord*>::iterator it;
    for( it = subckts.begin(); it != subckts.end(); it++ ) {
        i = 0;
        subcktName = (*it)->subcktName;                 // entity name
        entDest[Location::ent].content(subcktName);     // find or create the entity
        entDest.Reset();
        entDest.FindOrCreate();

        parentDest[Location::ent].content ((*it)->model);
        parentDest.Reset();
        parentDest.Find();

        instName.cons(subcktName,id++);                 // instance name
        Instance* inst = entDest.theEntity()->Instantiate (instName, parentDest.theArch()->Impl <StructArch>());

        //  -------- ports
        data = (*it)->assigns;
        while( true ) {
            formal = "";
            actual = "";
            while(i < data.length() && data[i] != '=')
                formal += data[i++];
            i++;
            while(i < data.length() && data[i] != ' ')
                actual += data[i++];
                                        // find instance port
            Port <Instance>* portY = inst->FindPort(formal);

                                        // find or create the net
            Net* netY = parentDest.theArch()->Impl<StructArch>()->FindOrCreateNet(actual);
            netY->Join (portY);

            if (i == data.length()) break;
            while( data[i] == ' ' ) i++;
        }
    }
}
//---------------------------------------------------------------------------
void BlifImport::haveKiss(int inpN, int outN, std::list<hub::StateTableRow>& stateTable, std::string& resetState, int stateCount, int productCount) {
    Name portName;
    BehavAgent *agent;
    BehavArch* behavArch;

    if( productCount == -1 ) productCount = stateTable.size();

    agent = new FsmAgent(inpN,outN,stateTable, resetState, stateCount, productCount);
    kiss_.theArch (kiss_.theLib()->FindArch(*agent, "KISS mealy"));

    if (kiss_.theArch()) {
        kiss_.theEntity (kiss_.theArch()->Parent());
    } else {                    // this sequential circuit is not in library, 
                                // so we have to create a new entity
                                // create new entity and architecture
        kiss_[Location::ent].content (this->modelname);
        kiss_.Reset();
        kiss_.CreateUniqueEntity <BehavArch> ();
        behavArch = kiss_.theArch()->Impl<BehavArch>();
						// behavioral decription
        behavArch->description (agent->CreateDescr());

        //create inputs
        for (int i = 1; i <= inpN; ++i ) {
            portName.cons("i",i);
            kiss_.theEntity()->CreateNextPort(portName, PortProto::inDir);
        }

        //create outputs
        for (int i = 1; i <= outN; ++i ) {
            portName.cons("o",i);
            kiss_.theEntity()->CreateNextPort(portName, PortProto::outDir);
        }
    }
}
// ---------------------------------------------------------------------------------------------
void BlifImport::haveSearch (const std::string& fn) {
    ImportStream*           importStream;
    ImportLang*             importLang;
    Import*                 import;
    Import::Destinations    dests;
    FileName               f = fn;
    std::istream*           istr;

    importStream = ImportStream::bySuffix (f.suffix());
    if (!importStream) {
        importStream = ImportStream::getDefault();
        istr = importStream->getStream(fn);
        if (!istr->good()) {
            delete istr;
            importStream = ImportStream::byExistence(f);    // returns real name
            if (!importStream) { 
                throw CException <std::runtime_error, IOReason, FilePosition, ImportCtx> (
                    IOReason(0), FilePosition (f), ImportCtx ("blif")
                ); 
            }
            istr = importStream->getStream(f);
        }
    } else {
        f = f.minussuffix();
        istr = importStream->getStream(fn);
    }

    importLang = ImportLang::bySuffix (f.suffix()); 
    if (!importLang) {
         throw CException <std::runtime_error, InconsReason, LexPosition, ImportCtx> (
             InconsReason (f.suffix()+": unknown suffix in .search"),
                LexPosition(parser_.l),
                ImportCtx ("blif")
          );
    }

    importLang->getDefaults (dest_.theHub(), f.bare(), dests);
    if (dests.size() > 0) dests[0][Location::design].locked (true);
    if (dynamic_cast <BlifImport*> (importLang)) {
        dests[0].Inherit (dest_);           // another blif: inherit destinaation, pla lib, kiss lib
        dests[1].Inherit (synth_);
        dests[2].Inherit (kiss_);
    } else if (dynamic_cast <PlaImport*> (importLang)) {
        dests[0].Inherit (synth_);          // pla: the destination is our pla lib
    } else if (dynamic_cast <KissImport*> (importLang)) {
        dests[0].Inherit (kiss_);           // kiss: the destination is our kiss lib
    } else if (dests.size() > 0) {          // otherwise, if #0 exists,
        dests[0].Inherit (dest_);           // assume that #0 is destination (OK for EDIF (no #0), Bench, CIR ...
    }

    import = importLang->getImporter (dest_.theHub());

    InconsReason r;
    istr = importStream->getStream(fn);
    for (Import::Destinations::iterator i = dests.begin(); i != dests.end(); i++) {
        r=i->FindOrCreate();
        if (!r.okay()) {
            throw CException <std::runtime_error, InconsReason, ToolPosition, ImportCtx> (
                r,
                ToolPosition ("location "+i->label()),
                ImportCtx (importLang->name())
            );
        }
    }
    (*import) (istr, fn, dests);
    importStream->closeStream (istr);
    delete istr;
}


