#include <iostream>
#include <sstream>
#include "bench_import.h"
#include "attr.h"
#include "err.h"
#include "attr.h"
#include "edif_err.h"
#include "structural.h"
#include "behavioral.h"
using namespace hub;
// ======================================================================================
hub::SymFunc::Func BenchImport::functions [LEX_MAX_FUNC-LEX_MIN_SYMFUNC+1] =
{   hub::SymFunc::fAnd, 
	hub::SymFunc::fOr, 
	hub::SymFunc::fNand, 
	hub::SymFunc::fNor,
	hub::SymFunc::fXor,
	hub::SymFunc::fXnor,
	hub::SymFunc::fInv,
	hub::SymFunc::fBuf
};
//---------------------------------------------------------------------------
void BenchImport :: operator() (const std::string& inFileName) {
    if (!inFileName.empty()) fileName (inFileName);
    ParamsCheck();
    FindOrCreate (loc_);		// find / create target location using set names
    parser_.run (fileName());
}
//---------------------------------------------------------------------------
void BenchImport :: operator() (Library* lib, const std::string& inFileName) {
    if (!inFileName.empty()) fileName (inFileName);
    ParamsCheck();
	(Location&)(*this)=lib ;	// set location to the given library
    FindOrCreate (loc_);		// get the rest from names
    parser_.run (fileName());
}
//---------------------------------------------------------------------------
void BenchImport :: operator() (Entity* ent, const std::string& inFileName) {
    if (!inFileName.empty()) fileName (inFileName);
    ParamsCheck();
	(Location&)(*this)=ent ;	// set location to the given entity
    FindOrCreate (loc_);		// get the rest from names
    parser_.run (fileName());
}
//---------------------------------------------------------------------------
void BenchImport :: operator() (Architecture* arch, const std::string& inFileName) {
    if (!inFileName.empty()) fileName (inFileName);
    ParamsCheck();
	(Location&)(*this)=arch ;	// set location to the given architecture
    parser_.run (fileName());
}
//---------------------------------------------------------------------------
void BenchImport :: ParamsCheck (void) {
    // designName can be empty
    if (loc_.entityName().empty()) loc_.entityName (fileName_.bare());	
    if (loc_.archName().empty())   loc_.archName   (std::string ("bench"));
    if (loc_.libName().empty())    loc_.libName    (std::string ("work"));	// what else
    if (synthArchName_.empty())    synthArchName_ = std::string ("bench");
    if (synthLibName_.empty())     synthLibName_  = std::string ("bench_primitives");
}
//---------------------------------------------------------------------------
void BenchImport :: haveDesign (void) {
    // ---------- make new architecture and structural implementation -------
    //            library, entity, and architecture already found/created by FindOrCreate()
    theArchImpl_ = theArch_->Create <StructArch> ();
	// ----------- find or make library for built-in primitives -------------
    theSynthLib_ = theHub_->FindOrCreateLib (synthLibName_);
    if (theSynthLib_ == NULL) {
	throw UndefException (UndefReason ("library", synthLibName_), 
			      LexPosition(parser_.l),		// tool position?
			      CircCtx (0));
    }
}
//---------------------------------------------------------------------------
void BenchImport :: endDesign (void) {
    if (!designName_.empty()) {	
	theDesign_ = theHub_->CreateDesign (designName_, theEntity_, theLib_);
    }	
}
//---------------------------------------------------------------------------
void BenchImport :: haveInPort (const std::string& portName) {
    PortProto* proto = theEntity_->CreateNextPort (portName, PortProto::inDir);
    if (!proto) {
	throw RedefException (RedefReason ("port", portName), 
			      LexPosition(parser_.l),
			      CircCtx (theLib_, theEntity_, 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 (theLib_, theEntity_, theArch_));
    }
    net->Join (archPort);
}
//---------------------------------------------------------------------------
void BenchImport :: haveOutPort (const std::string& portName) {
	PortProto* proto = theEntity_->CreateNextPort (portName, PortProto::outDir);
    if (!proto) {
	throw RedefException (RedefReason ("port", portName), 
			      LexPosition(parser_.l),
			      CircCtx (theLib_, theEntity_, 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 (theLib_, theEntity_, theArch_));
    }
    net->Join (archPort);
}
//---------------------------------------------------------------------------
void BenchImport :: haveAssign (const std::string& outputName, 
			     int function, 
			     const list<std::string> listInputNames) {
    // -----------------------------------------------------------------------
    //	we have to introduce a BENCH-SPECIFIC convention that 
    //	- entity name is <function><inp no>
    //  - input name for a gate is I<inp>
    //  - output name for a gate is O
	//  - input name frlor a DFF is D
	//	- output name for a DFF is Q
	// -----------------------------------------------------------------------
	Name entName;				// entity
	Name portName;				// port
	Architecture*	stdArch =NULL;
	BehavArch*		behavArch;
	Entity*			stdEnt;
	BehavAgent*		agent;
	list<std::string>::const_iterator i;
	
	switch (function) {
	case LEX_B_DFF:
		agent = new RTLRegAgent;
		break;
	default:
	    agent = new SymFuncAgent (decode (function), listInputNames.size());
		break;
	}
	stdArch = theSynthLib_->FindArch (*agent, "");	// really, "SYM"|"RTLREG"
	if (stdArch) {					// found a behavioral decription
		stdEnt = stdArch->Parent();
	} else {						// have to create a new entity
									// alternative: if an equally named entity
									// already exists, make a new UNIQUE arch
		entName.cons (parser_.l.DecodeSymbol(function), listInputNames.size());
		stdEnt = theSynthLib_->CreateUniqueEntity (entName);
									// architecture & implementation
        stdArch = stdEnt->CreateArch <BehavArch>(synthArchName_);
	    stdEnt->CurrentArch (stdArch);
		behavArch = dynamic_cast <BehavArch*> (stdArch->Impl());
									// behavioral decription
		behavArch->description (agent->CreateDescr());
									// ports
		stdEnt->CreateNextPort ("O", PortProto::outDir);
	    for (size_t n=0; n < listInputNames.size(); n++) {
			switch (function) {
			case LEX_B_DFF: portName="D"; break;
			default:		portName.cons ("I", n+1); break;
			}
			stdEnt->CreateNextPort (portName, PortProto::inDir);
		}
		switch (function) {
		case LEX_B_DFF: portName="Q"; break;
		default:		portName="O"; break;
		}
		stdEnt->CreateNextPort (portName, PortProto::outDir);
    }
   // -----  instantiate architecture - AFTER all inputs and outputs are there
    Instance* inst = stdEnt->Instantiate (outputName, theArchImpl_, stdArch);
 
   // ------ 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 (theLib_, 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=listInputNames.begin(), n=0; i != listInputNames.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 (theLib_, theEntity_));
        }
 	netX->Join (portX);
    }
}
