#include "vhdl_export.h"
#include <string>
#include <sstream>
#include "../err.h"
#include "../hub.h"
#include "../entity.h"
#include "../structural.h"
#include "../behavioral.h"

using namespace hub;
using namespace std;
//---------------------------------------------------------------------------
VhdlExport :: VhdlExport (hub::Hub* h, const std::string& fn): 
Export (fn), 
traverser_(h, this)  {
    initPortAttrMap ();
}
//---------------------------------------------------------------------------
VhdlExport :: VhdlExport (hub::Library* lib, const std::string& fn): 
Export (fn), 
traverser_(lib, this)  {
    initPortAttrMap ();
}
//---------------------------------------------------------------------------
VhdlExport :: VhdlExport (hub::Entity* ent, const std::string& fn): 
Export (fn), 
traverser_(ent, this)  {
    initPortAttrMap ();
}
//---------------------------------------------------------------------------
VhdlExport :: VhdlExport (hub::Architecture* arch, const std::string& fn): 
Export (fn), 
traverser_(arch, this)  {
    initPortAttrMap ();
}
//---------------------------------------------------------------------------
std::map < hub::AttrIndex <hub::PortProto>, std::string > VhdlExport :: portAttrMap;

//---------------------------------------------------------------------------
void VhdlExport :: initPortAttrMap (void) {
    if (portAttrMap.size() > 0) return;
    portAttrMap [PortProto::input] = "in";
    portAttrMap [PortProto::output] = "out";
}

//---------------------------------------------------------------------------
void VhdlExport :: havePortAttr (PortProto* port) {
    std::map < AttrIndex <PortProto>, std::string > :: const_iterator attri;
    for (attri = portAttrMap.begin(); attri != portAttrMap.end(); attri++) {
		if (port->predicate (attri->first)) {
			out << attri->second << nl_(" ");
		}
    }
}
// ==========================================================================
void VhdlExport :: operator() (const std::string& fn) {
	if (!fn.empty()) fileName (fn);
    InconsReason locr= traverser_.Find (loc_);	// the specified location must exist
    if (!locr.okay()) 
	throw UnspecException (locr, ToolPosition ("Vhdl export"));
    traverser_ ();
}
//---------------------------------------------------------------------------
void VhdlExport :: operator() (hub::Library* lib, const std::string& fn) {
	if (!fn.empty()) fileName (fn);
	traverser_.Set (lib);
    if (fileName_.empty()) 
		throw UnspecException 
			(InconsReason ("file name not specified"),
			 ToolPosition ("Vhdl export"));
    out.open (fileName_.c_str());
    if (!out.is_open()) 
		throw IOException (IOReason(errno), FilePosition (fileName_));
    nlbase_.sync (out);
    traverser_ ();
    out.close ();
}
//---------------------------------------------------------------------------
void VhdlExport :: operator() (hub::Entity* ent, const std::string& fn) {
    if (!fn.empty()) fileName (fn);
    traverser_.Set (ent);
    // --------- open output -------------------
    if (fileName_.empty()) 
		throw UnspecException 
			(InconsReason ("file name not specified"),
			 ToolPosition ("Vhdl export"));
    out.open (fileName_.c_str());
    if (!out.is_open()) 
		throw IOException (IOReason(errno), FilePosition (fileName_));
    nlbase_.sync (out);
    traverser_ ();
    out.close ();
}
//---------------------------------------------------------------------------
void VhdlExport :: operator() (hub::Architecture* arch, const std::string& fn) {
    if (!fn.empty()) fileName (fn);
    traverser_.Set (arch);
    // --------- open output -------------------
    if (fileName_.empty()) 
		throw UnspecException 
			(InconsReason ("file name not specified"),
			 ToolPosition ("Vhdl export"));
    out.open (fileName_.c_str());
    if (!out.is_open()) 
		throw IOException (IOReason(errno), FilePosition (fileName_));
    nlbase_.sync (out);
    traverser_ ();
    out.close ();
}
//---------------------------------------------------------------------------
void VhdlExport :: haveUnit () {
  
}
//---------------------------------------------------------------------------
void VhdlExport :: endUnit  () {

}
//---------------------------------------------------------------------------
void VhdlExport :: haveLibrary (Library* lib) {
	std::ostringstream fn;
    fn << lib->name() << ".vhdl";
    fileName (fn.str());
    // --------- open output -------------------
    if (fileName_.empty()) 
		throw UnspecException 
			(InconsReason ("file name not specified"),
			 ToolPosition ("Vhdl export"));
    out.open (fileName_.c_str());
    if (!out.is_open()) 
		throw IOException (IOReason(errno), FilePosition (fileName_));
   nlbase_.sync (out);
   out << "library IEEE;" << nl_;
   out << "use ieee.Std_Logic_1164.all;" << nl_;
   out << "use ieee.numeric_std.all;" << nl_; 
}
//---------------------------------------------------------------------------
void VhdlExport :: endLibrary  (Library* lib) {
  out.close();
}
//---------------------------------------------------------------------------
void VhdlExport :: haveDesign (Design* dsgn) {
   
}
//---------------------------------------------------------------------------
void VhdlExport :: endDesign  (Design* dsgn) {
  
}
//---------------------------------------------------------------------------
void VhdlExport :: haveEntity (Entity* ent) {
	out << nl_ << "entity" << " " << ent->name() << " is";
	nl_+=2;
   
}
//---------------------------------------------------------------------------
void VhdlExport :: endEntity  (Entity* ent) {
	nl_-=2;
	out << nl_ <<"end entity " << ent->name() << ";" << nl_;
}
//---------------------------------------------------------------------------
void VhdlExport :: havePortClause(void){
	out << nl_ << "port (" ;
	nl_+=2;
	firstPort_ = true;
}
//---------------------------------------------------------------------------
void VhdlExport :: endPortClause(void){
	nl_-=2;
	out << nl_ << ");";
}
//---------------------------------------------------------------------------
void VhdlExport :: havePortProto (PortProto* pp) {
	if(!firstPort_) out << ";";
	out << nl_ << pp->name() << " : ";
    havePortAttr (pp);
	out << "std_logic";
	firstPort_ = false;
}
//---------------------------------------------------------------------------
void VhdlExport :: haveArch (Architecture* a) {
	out << nl_ << "architecture " << a->name()  << " ";
	out << "of " << a->Parent()->name() << " is";
	nl_+=2;
	StructArch* sa = a->Impl <StructArch> ();
	if (sa) {
		firstComponent_ = true;
		VhdlStructTraverser structTrav (sa, this);
		structTrav.travNets ();
		structTrav.travComponents();
		nl_-= 2;
		out << nl_ << "begin" << nl_;
		nl_+= 2;
		structTrav.travInsts();
		structTrav.travPorts();
		return;
	} 
	BehavArch* ba = a->Impl <BehavArch> ();
	if (ba) {
		BehavDescr* descr = ba->description<BehavDescr>();
		if (!descr) {
			out << nl_ << "<empty description>";
		} else if (!exportMap.Perform (*this, *descr)) {	// either the dispatcher or the method failed
			nl_ -=2;
			out << nl_ << "begin" ;
			nl_ +=2;
			out << nl_ << "-- unknown architecture";
		}
		return;
	}
}
//---------------------------------------------------------------------------
void VhdlExport :: endArch  (Architecture* a) {
	nl_ -= 2;
	out << nl_ << "end architecture " << a->name() << ";" << nl_;
}
//---------------------------------------------------------------------------
void VhdlExport :: haveComponent( Entity* component ){
	if( firstComponent_ ) {
		out << nl_;
		firstComponent_ = false;
	}
	out << nl_ << "component " << component->name();
	nl_+= 2;
}
//---------------------------------------------------------------------------
void VhdlExport :: endComponent(void){
	nl_-= 2;
	out << nl_ << "end component;" << nl_;
}
//---------------------------------------------------------------------------
void VhdlExport :: haveNet(hub::Net* net){}
//---------------------------------------------------------------------------
void VhdlExport :: haveNet (hub::StructArch* arch,hub::Net* net){
	if(!isPort(net)){
		out << nl_ << "signal " << augmentNetName(arch,net) << " : std_logic;";
	}
}
//---------------------------------------------------------------------------
void VhdlExport :: endNet  (hub::Net* net){
	 
}
//---------------------------------------------------------------------------
void VhdlExport :: haveJoined (hub::Joinable* j){

}
//---------------------------------------------------------------------------
void VhdlExport :: havePort (hub::Port <hub::StructArch>* port){

	if(port->Connected()  ){
		if( CompareText(port->Connected()->name(),port->name()) != 0 ){
			if( port->predicate(hub::PortProto::input) )
				out << nl_ << generateNetName(port->Parent(),port->Connected()) << " <= " << port->name();
			else
				out << nl_ << port->name() << " <= "  << generateNetName(port->Parent(),port->Connected());
		out << ";";
		}
	}
}
//---------------------------------------------------------------------------
void VhdlExport :: haveInst (hub::Instance* inst){
	out << nl_ << inst->name() << ": " << inst->entity()->name();
	nl_ += 1;
	out << nl_ << "port map(";
	nl_ += 2;
	Port<Instance> *port;
	int i = 0;
	while( port = inst->FindPort(i) ){
		if( i > 0 ) out << ",";
		out << nl_ << port->name() << " => ";
		if(port->Connected()) {
			out << generateNetName(inst->Parent(),port->Connected());
		}
		else 
			out << "open";
		i++;
	}
	nl_ -= 2;
	out << nl_ << ");";
	nl_ -= 1;
}
//---------------------------------------------------------------------------

void VhdlExport :: havePrimInst(hub::Instance* inst){
	Port<Instance> *output = inst->FindPort(0,PortProto::outDir);

	out << nl_ << generateNetName(inst->Parent(),output->Connected());
	out << " <= ";

	BehavArch* ba = inst->CurrentArch()->Impl<BehavArch>();
	SymFunc* sf = ba->description<SymFunc>();
	if( sf->inpNo() > 1 ){
		Port<Instance> *port = inst->FindPort(1);
		out  << generateNetName(inst->Parent(),port->Connected());;
		for(size_t i = 2; i < (sf->inpNo() + 1); i++ ){
			out << " " << VhdlSymFunc::ftab[sf->func()] << " " ;
			port = inst->FindPort(i);
			out  << generateNetName(inst->Parent(),port->Connected());
		}
		out << ";";
		return;
	}
	if( sf->func() == SymFunc::fInv ){
		Port<Instance> *port = inst->FindPort(1);
		out << VhdlSymFunc::ftab[sf->func()] << " ";
		out <<  generateNetName(inst->Parent(),port->Connected()) << ";";
		return;
	}
	if( sf->func() == SymFunc::fBuf ){
		Port<Instance> *port = inst->FindPort(1);
		out << generateNetName(inst->Parent(),port->Connected()) << ";";
		return;
	} 
	if( sf->func() == SymFunc::fT0 ){
		out << "'0'" << ";";
		return;
	}
	if( sf->func() == SymFunc::fT1 ){
		out << "'1'" << ";";
		return;
	} 

}
//---------------------------------------------------------------------------
bool VhdlExport::isPort(hub::Net* net){
	for(Net::JoinIter i = net->JoinBeg(); i!= net->JoinEnd(); ++i){
		if(dynamic_cast<Port<StructArch>*>(*i)){
			if(CompareText(net->name() ,(*i)->name()) == 0 ) return true;
		}
	}
	return false;
}
//---------------------------------------------------------------------------
Name VhdlExport::augmentNetName(hub::StructArch* arch,hub::Net* net){
	Name new_name = net->name();
	bool first_augment = true;
	bool edited = true;
	while(edited){
		for(StructArch::PortIter i = arch->PortBeg(); i!= arch->PortEnd(); ++i){
			if(CompareText(new_name,(*i)->name()) == 0){
				first_augment ? new_name.augment(0) : new_name.nextAugment();
				first_augment = false;
				edited = true;
				break;
			}
			edited = false;
		}
	}
	return new_name;
}
//---------------------------------------------------------------------------
hub::Name VhdlExport::generateNetName(hub::StructArch* arch,hub::Net* net){
	if(isPort(net))
		return net->name();
	else
		return augmentNetName(arch,net);

}
// ==========================================================================
bool VhdlSymFunc::Represent (VhdlExport& exp, SymFunc& f) {
	exp.nl_ -= 2;
	exp.out << exp.nl_ << "begin";
	exp.nl_ += 2;
	exp.out << exp.nl_ << "O <= ";
	if( f.inpNo() > 1 ){
		int i = 1;
		exp.out << "I" << i ;
			while( i != f.inpNo()){
				i++;
				exp.out << " " << ftab [f.func()] << " I" << i;
			}
		exp.out << ";";
		return true;
	}
	if( f.func() == SymFunc::fInv ){
		exp.out << "not I1;";
	}
	return true;
}
const char* VhdlSymFunc::ftab [SymFunc::fNull+1]={
	"gnd", "vdd",								// no inputs
	"buf", "not",								// one input
	"and", "xor", "or", "nor", "xnor", "nand",	// two and more inputs
	"NULL"										// unknown/invalid
};
int VhdlSymFunc::wantInit = Export :: exportMap.Enlist <VhdlExport&, SymFunc&, &VhdlSymFunc :: Represent> ();
// ==========================================================================
