#include "vhdl_import.h"
#include "hub/behavioral.h"
#include "config.h"
#include "hub/err.h"
#include "edif_err.h"
#include <iostream>

using namespace std;
using namespace hub;
// --------------------------------------------------------------------------------------
void VhdlImpLang::enregister (void) {
    registry_.push_back((new VhdlImpLang ("VHDL"))->addSuffix (".vhd")->addSuffix (".vhdl"));
}
// --------------------------------------------------------------------------------------
void VhdlImpLang::getDefaults (hub::Hub* h, const std::string& fn, Import::Destinations& dflt) {
    dflt.clear();                                      
    dflt.push_back (Location (h, "Destination"));       // Destination #0: code
    dflt[0][Location::lib].oblig  (true); 
    dflt[0][Location::lib].content  ("work");
    dflt[0][Location::ent].locked (true);               // entity set in code
    dflt[0][Location::arch].locked (true);              // architecture set in code

    dflt.push_back (Location (h, "Primitives"));       // Destination #1: synth primitives
    dflt[1][Location::lib].oblig  (true); 
    dflt[1][Location::lib].content  ("primitives");
    dflt[1][Location::ent].locked (true);               // entity set in code
    dflt[1][Location::arch].oblig (true);               // architecture snot et in code
    dflt[1][Location::arch].content ("vhdl");
}

// ----------------------------------------------------------------------------------------------
void VhdlImport:: operator () (std::istream* istr, const std::string& f, Import::Destinations& dests) {
    dest_  = dests[0];
    toplevel_ = &(dests[0]);
    synth_ = dests [1];
    parser_.run (*istr, f);
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveUnit(){}
// ----------------------------------------------------------------------------------------------
void VhdlImport::endUnit(){}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveEntity(const StringToken& entName){
	dest_.theEntity (dest_.theLib()->CreateEntity (entName));
	if (dest_.theEntity() == NULL) {
	throw VhdlRedefException (RedefReason ("entity", entName), 
				VhdlFilePosition(entName.Location()),
				CircCtx (dest_.theLib()));
	}
    if (! toplevel_->theEntity()) toplevel_->theEntity (dest_.theEntity());
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::endEntity(){
	dest_.theEntity (NULL);
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::havePort(InterfaceDecl& intDecl ){
	vector<StringToken> names = intDecl.GeneratePortNames();
	for(vector<StringToken>::iterator i=names.begin();i!=names.end();++i){
		PortProto* proto = dest_.theEntity()->CreateNextPort ( (*i), DecodeDir(intDecl.mode) );
		if (proto == NULL) {
	    throw VhdlRedefException (RedefReason ("port", (*i)), 
				  VhdlFilePosition( (*i).Location() ),
				  CircCtx (dest_));
		}	
	}
}
// ----------------------------------------------------------------------------------------------

void VhdlImport::haveArch(const StringToken& archName,const StringToken& entName  ){
	dest_.theEntity (dest_.theLib()->FindEntity(entName));
	if(!dest_.theEntity()){
		throw VhdlUndefException (UndefReason ("entity", entName), 
			VhdlFilePosition(entName.Location()),
				  CircCtx (dest_.theLib()));
	}
	dest_.theArch (dest_.theEntity()->CreateArch <StructArch> (archName));
	    if (!dest_.theArch()) {
		throw VhdlRedefException (RedefReason ("architecture", archName), 
			VhdlFilePosition(archName.Location()),
				      CircCtx (dest_.theLib(), dest_.theEntity()));
	    }	
	theArchImpl_ = dest_.theArch()->Impl<StructArch>();

	//instantiation of entity's ports and creation of nets for these ports
	
	for(Entity::PortProtoIter pi = dest_.theEntity()->PortProtoBeg(); pi!= dest_.theEntity()->PortProtoEnd(); ++pi){
		Port <StructArch>* archPort = theArchImpl_->CreatePort (pi->second);
	}    
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::endArch(const StringToken& archName  ){
    dest_.theArch (NULL);
    theArchImpl_ = NULL;
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveSignalDeclar( InterfaceDecl intDecl ){

	vector<StringToken> names = intDecl.GeneratePortNames();
	for(vector<StringToken>::iterator i=names.begin();i!=names.end();++i){
		Port<StructArch>* port = theArchImpl_->FindPort(*i);
		if(port){
			throw VhdlRedefException (RedefReason ("net", (*i)), 
				  VhdlFilePosition((*i).Location()),
				  CircCtx (dest_));
		}
		Net* net = theArchImpl_->CreateNet(*i);
		if(!net){
			throw VhdlRedefException (RedefReason ("net", (*i)), 
				  VhdlFilePosition((*i).Location()),
				  CircCtx (dest_));
		}
	}
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveComponent(const StringToken& compName){
		//find component's entity with same name as component
		theComponent_ = dest_.theLib()->FindEntity(compName);
		if(!theComponent_){
			throw VhdlUndefException (UndefReason ("entity(component)", compName), 
				VhdlFilePosition(compName.Location()),
				  CircCtx (dest_));
		}
		compPortsCount_ = 0;
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::endComponent(const StringToken& compName){
		if(compPortsCount_ != theComponent_->PortNum() ){
			throw VhdlOthException (OthReason ("component declaration", theComponent_->name(), "interface mismatch"), 
				VhdlFilePosition(compName.Location()),
					CircCtx (dest_));
		}
		compPortsCount_ = 0;
		theComponent_ = NULL;
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveComponentPort(InterfaceDecl& intDecl ){
	//check if the port of component is same as port of entity
		vector<StringToken> names = intDecl.GeneratePortNames();
		for(vector<StringToken>::iterator i=names.begin();i!=names.end();++i){
			PortProto* proto = theComponent_->FindPort(*i);
			if (proto == NULL) {
				throw VhdlUndefException (UndefReason ("component port", (*i)), 
					VhdlFilePosition((*i).Location() ),
					CircCtx (dest_));
			}
			if (!DecodeDir(intDecl.mode).IsSubset (proto->flags()) ) {
				throw VhdlOthException (OthReason ("component declaration", proto->name(), "direction"), 
					VhdlFilePosition((*i).Location()),
					CircCtx (dest_));
			}
			compPortsCount_++;
		}
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveComponentInst(const StringToken& label, const StringToken& compName,const StringToken& archName){
	Entity* compInst = dest_.theLib()->FindEntity(compName);
		if(!compInst){
			throw VhdlUndefException (UndefReason ("entity(component)", compName), 
				VhdlFilePosition(compName.Location()),
				  CircCtx (dest_));
		}
		//find last translated architecture of instantiated entity
		Architecture* arch = NULL;
		if(archName.empty()){
			
			for(Entity::ArchIter i = compInst->ArchBeg(); i != compInst->ArchEnd(); ++i)
				arch = i->second;
			if(!arch){
				throw VhdlOthException (OthReason ("Instantiated entity", compName, "has not any atrchitecture "), 
					VhdlFilePosition(compName.Location()),
					CircCtx (dest_));
			}
		} else {
			arch = compInst->FindArch(archName);
			if(!arch){
				throw VhdlOthException (OthReason ("Architecture", archName, "not exist "), 
					VhdlFilePosition(compName.Location()),
					CircCtx (dest_));
			}

		}
		//Instantiation of entity
		theInstance_ = compInst->Instantiate(label,theArchImpl_,arch);
		if(!theInstance_ ){
			throw VhdlRedefException (RedefReason ("instance", label), 
				VhdlFilePosition(label.Location()),
				  CircCtx (dest_));
		}
		//counter of mapped ports
		compPortsCount_ = 0;
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::havePortAssoc(const StringToken& compPortName,const StringToken& NetName){
	Port<Instance>* port = theInstance_->FindPort(compPortName);
	if(!port){
		throw VhdlUndefException (UndefReason ("port", compPortName), 
			VhdlFilePosition(compPortName.Location()),
				CircCtx ( theInstance_->entity()->Parent() , theInstance_->entity() ));
	}
	if(port->Connected()){
		throw VhdlOthException (OthReason ("Port",compPortName, " is alredy connected"), 
			VhdlFilePosition(compPortName.Location()),
				CircCtx ( theInstance_->entity()->Parent() , theInstance_->entity() ));
	}
	compPortsCount_++;
	if(NetName == "open") return;

	Net* net = theArchImpl_->FindNet( NetName );
	if(!net){
		Port<StructArch>* port = theArchImpl_->FindPort(NetName);
		if(!port){
			throw VhdlUndefException (UndefReason ("port or net", NetName), 
				VhdlFilePosition(NetName.Location()),
				CircCtx (dest_));
		}
		net = theArchImpl_->CreateNet (NetName);
		net->Join(port);
	}	
	net->Join(port);
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::havePosPortAssoc(int portPosition, const StringToken& NetName){
	Port<Instance>* port = theInstance_->FindPort(portPosition);
	if(!port){
		throw VhdlUndefException (UndefReason ("port num.", ""), 
			VhdlFilePosition(NetName.Location()),
				CircCtx ( theInstance_->entity()->Parent() , theInstance_->entity() ));
	}
	if(port->Connected()){
		throw VhdlOthException (OthReason ("Port",port->name(), " is already connected"), 
			VhdlFilePosition(NetName.Location()),
				CircCtx ( theInstance_->entity()->Parent() , theInstance_->entity() ));
	}
	compPortsCount_++;
	if(NetName == "open") return;

	Net* net = theArchImpl_->FindNet( NetName );
	if(!net){
		throw VhdlUndefException (UndefReason ("net", NetName), 
			VhdlFilePosition(NetName.Location()),
				CircCtx (dest_));
	}
	
	net->Join(port);
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::endComponentInst(void){
	int count = theInstance_->entity()->PortNum();
	if( count != compPortsCount_){
	throw UnspecException (InconsReason ("Component Instantiation - port count mismatch"),
	 ToolPosition ("Vhdl import"));
	}
	theInstance_ = NULL;
}
// ----------------------------------------------------------------------------------------------
StringToken* VhdlImport::haveSymFunc(const list<StringToken> listInputNames,
									const std::string& func, bool createNet ){
	BehavAgent*		agent;
	BehavArch*		behavArch;
	Name entName;
	Name portName;
	StringToken lastInput;

	agent = new SymFuncAgent (decodeFunc(func), listInputNames.size() );
	synth_.theArch (synth_.theLib()->FindArch (*agent, ""));
	if (synth_.theArch()) {					// found a behavioral decription
        synth_.theEntity (synth_.theArch()->Parent());
	} else {						// have to create a new entity
									// alternative: if an equally named entity
									// already exists, make a new UNIQUE arch
		entName.cons ( func , listInputNames.size());
        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 < listInputNames.size(); n++) {
			portName.cons ("I", n+1);
			synth_.theEntity()->CreateNextPort (portName, PortProto::inDir);
		}
    }
	// -----  instantiate architecture with unique name - AFTER all inputs and outputs are there
	Name *instName = new Name();
	instName->cons(func,listInputNames.size());
	instName->augment(0);
	Instance* inst = NULL;
	do {
		instName->nextAugment();
		inst = synth_.theEntity()->Instantiate (*instName, theArchImpl_, synth_.theArch());
	}
	while (!inst);
	//Creating output net
	if(createNet){
		*instName += "_out";
		Net *outNet = theArchImpl_->CreateNet(*instName);
		if(!outNet){
			throw VhdlRedefException (RedefReason ("net", *instName), 
				  VhdlFilePosition(lastInput.Location()),
				  CircCtx (dest_));
		}
		// Connection of instance's ports 
		outNet->Join(inst->FindPort("O"));
	}
	lastPort_ = inst->FindPort("O");
	

	list<StringToken>::const_iterator i;
	int n;
	for (i=listInputNames.begin(), n=0; i != listInputNames.end(); i++,n++) {
		Net* net = theArchImpl_->FindNet(*i);
		if(!net){
			Port<StructArch>* port = theArchImpl_->FindPort(*i);
			if(!port){
				throw VhdlUndefException (UndefReason ("port or net", *i), 
					VhdlFilePosition((*i).Location() ),
					CircCtx (dest_));
			}
			net = theArchImpl_->CreateNet (*i);
			net->Join(port);
		}
		Port <Instance>* portX = inst->FindPort (n, PortProto::inDir);
		if (!portX) {
            throw VhdlOthException (OthReason ("no input port","",""),
    			    VhdlFilePosition((*i).Location()),
    			    CircCtx (dest_.theLib(), dest_.theEntity()));
        }
		lastInput = *i;
		net->Join(portX);
	}
	return new StringToken(*instName,lastInput.Location());
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveOperand(const StringToken& portName ){
	lastPort_= theArchImpl_->FindPort(portName);
}
// ----------------------------------------------------------------------------------------------
void VhdlImport::haveSignalAssignment(StringToken targetName, Expr* waveform ){
	Net* net = theArchImpl_->FindNet(targetName);
		if(!net){
			Port<StructArch>* port = theArchImpl_->FindPort( targetName );
			if(!port){
				throw VhdlUndefException (UndefReason ("port or net", targetName), 
					VhdlFilePosition(targetName.Location()),
					CircCtx (dest_));
			}
			net = theArchImpl_->CreateNet ( targetName );
			net->Join(port);
		}
		lastPort_ = NULL;
		waveform->Connect (this , false); //connects intstances and nets and set lastPort_
		if(!lastPort_){
			throw VhdlOthException (OthReason ("no port on right side of assignment","",""),
    			    VhdlFilePosition(targetName.Location()),
    			    CircCtx (dest_));
		}
		net->Join(lastPort_);
}
// ----------------------------------------------------------------------------------------------
const hub::PortProto::flagsType&  VhdlImport::DecodeDir (const Mode m) {
	switch( m ){
		case M_IN:  return PortProto :: inDir;
		case M_OUT: return PortProto :: outDir;
		case M_INOUT: 
		case M_BUFFER:
		case M_LINKAGE:
					return PortProto :: inOutDir;
		default:
			return PortProto :: noDir;
	}
}
// ----------------------------------------------------------------------------------------------
hub::SymFunc::Func VhdlImport::decodeFunc ( const std::string& func){
		if( func == "not" )  return hub::SymFunc::fInv;
		if( func == "and" )  return hub::SymFunc::fAnd;
		if( func == "nand" )  return hub::SymFunc::fNand;
		if( func == "or" )  return hub::SymFunc::fOr;
		if( func == "nor" )  return hub::SymFunc::fNor;
		if( func == "xor" ) return hub::SymFunc::fXor;
		if( func == "xnor" ) return hub::SymFunc::fXnor;
		if( func == "vcc" ) return hub::SymFunc::fT1;
		if( func == "gnd" ) return hub::SymFunc::fT0;
		return hub::SymFunc::fNull;
}
// ----------------------------------------------------------------------------------------------


