#include <iostream>
#include <sstream>
#include "bench_import.h"
#include "hub/attr.h"
#include "hub/err.h"
#include "hub/attr.h"
#include "edif_err.h"
#include "hub/structural.h"
#include "hub/behavioral.h"
using namespace hub;

// --------------------------------------------------------------------------------------
void BenchImpLang::enregister (void) {
    registry_.push_back ((new BenchImpLang ("Bench"))->addSuffix (".bench")->addSuffix (".BENCH"));
}

// --------------------------------------------------------------------------------------
void BenchImpLang::getDefaults (hub::Hub* h, const std::string& fn, Import::Destinations& dflt) {
    dflt.clear();
    dflt.push_back (Location (h, "Bench entity"));      // Destination #0: Bench entity destination
    dflt[0][Location::lib].oblig  (true); 
    dflt[0][Location::lib].content  ("work");
    dflt[0][Location::ent].oblig  (true); 
    dflt[0][Location::ent].content  (bareName (fn));
    dflt[0][Location::arch].oblig (true); 
    dflt[0][Location::arch].content ("bench");

    dflt.push_back (Location (h, "Bench primitives"));   // Destination #1: Synth library location
    dflt[1][Location::lib].oblig  (true); 
    dflt[1][Location::lib].content ("bench_primitives");
    dflt[1][Location::ent].locked (true);               // entity specified by generator
    dflt[1][Location::arch].oblig (true); 
    dflt[1][Location::arch].content ("bench");
    dflt[1][Location::design].locked (true);            // design makes no sense
}

// ======================================================================================
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() (std::istream* in, const std::string& fi, std::vector<hub::Location>& dests) {
    in_   = in;
    dest_ = dests[0];
    prim_ = dests[1];
    dest_[Location::design].clear();
    dest_.theDesign (NULL);
    parser_.run (*in_, fi);
}
//---------------------------------------------------------------------------
void BenchImport :: haveDesign (void) {
    // ---- make new architecture and structural implementation -------
    //      library, entity, and architecture 
    //      already found/created by Location::FindOrCreate()
    theArchImpl_ = dest_.theArch()->Create <StructArch> ();
}
//---------------------------------------------------------------------------
void BenchImport :: endDesign (void) {	
}
//---------------------------------------------------------------------------
void BenchImport :: haveInPort (const std::string& portName) {
    PortProto* 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", portName), 
			      LexPosition(parser_.l),
			      CircCtx (dest_.theLib(), dest_.theEntity(), dest_.theArch()));
    }
    net->Join (archPort);
}
//---------------------------------------------------------------------------
void BenchImport :: haveOutPort (const std::string& portName) {
	PortProto* 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_.theLib(), dest_.theEntity(), dest_.theArch()));
    }
    net->Join (archPort);
}
//---------------------------------------------------------------------------
void BenchImport :: haveAssign (const std::string& outputName, 
			     int function, 
                 const std::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;
    std::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 = prim_.theLib()->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

                                    // should be made via prim_.FindOrCreate()
		entName.cons (parser_.l.DecodeSymbol(function), listInputNames.size());
		stdEnt = prim_.theLib()->CreateUniqueEntity (entName);
									// architecture & implementation
        stdArch = stdEnt->CreateArch <BehavArch>(prim_[Location::arch].content());
	    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 (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=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 (dest_.theLib(), dest_.theEntity()));
        }
 	    netX->Join (portX);
    }
}
