#include "edif_import.h"
#include "hub/hub.h"
#include "hub/structural.h"
#include "config.h"
#include "edif_err.h"
#include <iostream>
#include <sstream>
using namespace std;
using namespace hub;
// --------------------------------------------------------------------------------------
void EdifImpLang::enregister (void) {
    registry_.push_back((new EdifImpLang ("EDIF"))->addSuffix (".edif")->addSuffix (".edf"));
}
// --------------------------------------------------------------------------------------
void EdifImpLang::getDefaults (hub::Hub* h, const std::string& fn, Import::Destinations& dflt) {
    dflt.clear();
    // no defaults for EDIF
}
// ---------------- make a port/net/instance name from an Edif array -------------
std::string EdifImport::hubName (std::string& nm, EdifIndices& indices) {
    std::ostringstream t;
    EdifIndices::const_iterator i;
    t << nm;
    for (i=indices.begin(); i!=indices.end(); i++)
        t << "_" << *i;
    return t.str();
}

// ---------------- Edif indices handling ----------------------------------------
bool EdifImport::next (EdifIndices& ix, const EdifIndices& dims) {
    EdifIndices::const_reverse_iterator d;
    EdifIndices::reverse_iterator i;

    for (i=ix.rbegin(), d=dims.rbegin(); i!=ix.rend(); i++ , d++) {
        (*i)++;
        if (*i < *d) return true; else (*i)=0;
    }
    return false;
}
int EdifImport::product (const EdifIndices& dims) {
    EdifIndices::const_iterator i;
    int res=1;
    for (i=dims.begin(); i!= dims.end(); i++) res *= *i;
    return res;
}

// ----------------------------------------------------------------------------------------------
void EdifImport:: operator () (std::istream* in, const std::string& fn, std::vector<hub::Location>&) {
    parser_.run (*in, fn);
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveLibrary (std::string libName) {
    dest_[Location::lib].content (libName);
    dest_.Reset();
    dest_.FindOrCreate();
    libCtx_ = DefCtx;
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveExternal (std::string libName) {
    dest_[Location::lib].content (libName);
    dest_.Reset();
    dest_.FindOrCreate();
    libCtx_ = ExtCtx;
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveDesign (std::string designName, std::string cellName, std::string libName) {
    dest_.Reset();
    dest_[Location::lib].content (libName);
    dest_[Location::ent].content (cellName);
    dest_[Location::arch].clear();
    dest_[Location::design].content (designName);
    InconsReason ret = dest_.FindOrCreate();
    if (!ret.okay()) throw UndefException (UndefReason ("library", libName), 
			      LexPosition(parser_.l),
			      CircCtx (0));             // wrong - improve
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveCell (std::string cellName) {
    dest_[Location::ent].content(cellName);
    dest_.FindOrCreate();
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveView (std::string viewName) {
    // this is a netlist view
    switch (libCtx_) {
	case ExtCtx:
        dest_[Location::arch].content(viewName);
	    dest_.FindOrCreate ();
	    theArchImpl_ = NULL;
	    break;
	case DefCtx:		// this must be NETLIST or the parser has folded down
	    dest_.theArch (dest_.theEntity()->CreateArch <StructArch> (viewName));
        dest_[Location::arch].content(viewName);
	    if (!dest_.theArch()) {
		    throw RedefException (RedefReason ("view", viewName), 
				      LexPosition(parser_.l),
				      CircCtx (dest_));
	    }	
	    theArchImpl_ = dest_.theArch()->Impl<StructArch>();
	    break;
    }
    portNum_=0;
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: havePort (std::string portName, EdifIndices& portDims) {
    thePorts_.reserve (portDims.empty() ? 1 : product (portDims));
    EdifIndices ix (portDims.size(), 0);

    do {
        thePorts_.push_back (havePort (hubName (portName, ix)));
    } while (next (ix, portDims));
}
// ----------------------------------------------------------------------------------------------
PortProto* EdifImport:: havePort (std::string portName) {
    PortProto* thePort;

    // ------------- make port prototype in the Entity 
    if (dest_.theEntity()-> ArchNum() == 1) {
	    thePort = dest_.theEntity()->CreateNextPort (portName);
	    if (thePort == NULL) {
	        throw RedefException (RedefReason ("port", portName), 
				    LexPosition(parser_.l),
				    CircCtx (dest_));
	    }
    // ------------ check if the prototype is equal
    } else {
	    thePort = dest_.theEntity()->FindPort (portName);
	    if (thePort == NULL) {
	        throw OthException (OthReason ("view", dest_.theArch()->name(), "interface mismatch"), 
				    LexPosition(parser_.l),
				    CircCtx (dest_));
	    }
	    portNum_++;
    }
    // ------------- if this definition not declaration make an instantiation in the StructArch
    if (theArchImpl_)						// bad practice
        theArchImpl_->CreatePort (thePort);
    return thePort;
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveDir (std::string portDir) { 
    std::vector<hub::PortProto*>::const_iterator p;
    PortProto :: flagsType dir = DecodeDir (portDir);
    // ------------- set dir
    if (dest_.theEntity()-> ArchNum() == 1) {	
        for (p=thePorts_.begin(); p!= thePorts_.end(); p++)
            dir.Project ((*p)->flags());	// this si seen by possible arch's instance
    // ------------- check dir against entity's prototype	
    } else {	
        for (p=thePorts_.begin(); p!= thePorts_.end(); p++) {
	        if (!dir.IsSubset ((*p)->flags())) {
	            throw OthException (OthReason ("view", dest_.theArch()->name(), "interface mismatch"), 
				      LexPosition(parser_.l),
				      CircCtx (dest_));
	        }	 
        }
    }
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveInstance (
				std::string instName,
                EdifIndices& instDims,
				std::string viewName, 
				std::string cellName, 
				std::string libName) {
 
    // ------ the referenced entity and its architecture --------------
    Library* lib;
    if (libName.empty()) {
	    lib = dest_.theLib();		// library name not mentioned -> this library
    } else {
	    lib = dest_.theHub()->FindLib (libName);		// 
	    if (lib==NULL) {
	        throw UndefException (UndefReason ("library", libName), 
				    LexPosition(parser_.l),
				    CircCtx (0));
	    }
    }
    Entity* entity = lib->FindEntity (cellName);	// 
    if (entity==NULL) {
	    throw UndefException (UndefReason ("cell", cellName), 
			        LexPosition(parser_.l),
			        CircCtx (lib));
    }
    Architecture* arch = entity->FindArch (viewName);	// 
    if (arch==NULL) {
	    throw UndefException (UndefReason ("view", viewName), 
			        LexPosition(parser_.l),
			        CircCtx (lib, entity));
    }
    if (!dest_.theArch()->predicate(StructArch :: is)) {
	    throw OthException (OthReason ("view", dest_.theArch()->name(), "interface mismatch"), 
			        LexPosition(parser_.l),
			        CircCtx (lib, entity));
    }

    theInstances_.reserve (instDims.empty() ? 1 : product (instDims));
    EdifIndices ix (instDims.size(), 0);
    Instance* inst;
    do {
        inst = entity->Instantiate (hubName (instName, ix), theArchImpl_, arch);
        if (inst==NULL) {
	        throw RedefException (RedefReason ("instance", hubName (instName, ix)), 
			            LexPosition(parser_.l),
			            CircCtx (dest_));
        }
        theInstances_.push_back (inst);
    } while (next (ix, instDims));
}
// ----------------------------------------------------------------------------------------------
bool EdifImport:: wantProperty (propCtx ctx, std::string propName) { 
    switch (ctx) {
	case instCtx:   return true; 
	case viewCtx:   return false; 
	case designCtx: return true;
	case netCtx:    return false; 
	case cellCtx:   return false;  
	case portCtx:   return false;  
    }
    return false;
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveStrProperty (propCtx ctx, std::string propName, std::string propValue) {
    vector<Instance*>::iterator i;
    switch (ctx) {
	case instCtx:   
        for (i=theInstances_.begin(); i!=theInstances_.end(); i++)
            (*i)->CreateProperty (propName, propValue); 
        break;
	case viewCtx:   break;
	case designCtx: break;
	case netCtx:    break;
	case cellCtx:   break;
	case portCtx:   break;
    }
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveIntProperty (propCtx ctx, std::string propName, int propValue) {
    switch (ctx) {
	case instCtx:   break;
	case viewCtx:   break;
	case designCtx: break;
	case netCtx:    break;
	case cellCtx:   break;
	case portCtx:   break;
    }
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveBoolProperty (propCtx ctx, std::string propName, bool propValue) {
    switch (ctx) {
	case instCtx:   break;
	case viewCtx:   break;
	case designCtx: break;
	case netCtx:    break;
	case cellCtx:   break;
	case portCtx:   break;
    }
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveNet (std::string netName, EdifIndices& netDims) {
    if (!dest_.theArch()->predicate(StructArch :: is)) {
	    throw OthException (OthReason ("view", dest_.theArch()->name(), "not NETLIST"), 
			        LexPosition(parser_.l),
			        CircCtx (dest_));
    }
    theNetDims_=netDims;        // for haveJoined
    theNets_.reserve (netDims.empty() ? 1 : product (netDims));
    EdifIndices ix (netDims.size(), 0);
    Net* net;
    do {
        net = theArchImpl_ -> CreateNet (hubName (netName,ix));	// 
        if (net == NULL ) {
	        throw RedefException (RedefReason ("net", netName), 
			            LexPosition(parser_.l),
			            CircCtx (dest_));
        }
        theNets_.push_back(net);
    } while (next (ix, netDims));
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: haveJoined (
                std::string portName, 
                EdifIndices& portIndices,
                std::string instName,
                EdifIndices& instIndices) {
    if (!dest_.theArch()->predicate(StructArch :: is)) {
	    throw OthException (OthReason ("view", dest_.theArch()->name(), "not NETLIST"), 
			        LexPosition(parser_.l),
			        CircCtx (dest_));
    }

    if (!theNetDims_.empty() && !portIndices.empty()) {
	    throw OthException (OthReason ("port index", portName, "and array net"), 
			        LexPosition(parser_.l),
			        CircCtx (dest_));
    }
    std::string fullInstName = hubName (instName, instIndices);
    std::string fullPortName;
    EdifIndices nix (theNetDims_.size(), 0);
    vector<Net*>::iterator inet = theNets_.begin();

    Joinable* port; 
    do {
        if (theNetDims_.empty()) {
            fullPortName = hubName (portName, portIndices);
        } else {
            fullPortName = hubName (portName, nix);
        }
        if (instName.empty()) {				// this is outer port
	        port = theArchImpl_->FindPort (fullPortName);
        } else {
	        Instance* inst = theArchImpl_->FindInstance (fullInstName);	// 
	        if (inst==NULL) {
	            throw UndefException (UndefReason ("instance", instName), 
				        LexPosition(parser_.l),
				        CircCtx (dest_));
	        }
            port = inst->FindPort (fullPortName);
        }
        if (port == NULL) {
	        throw UndefException (UndefReason ("port", portName), 
			            LexPosition(parser_.l),
			            CircCtx (dest_));
        }
        (*inet)->Join (port);   // theNets_ has the same ordering as this loop
    } while (next (nix, theNetDims_));
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: endLibrary (void) {
    dest_.theLib (NULL);
    dest_[Location::lib].clear();
}    
// ----------------------------------------------------------------------------------------------
void EdifImport:: endExternal     (void) {
    dest_.theLib (NULL);
    dest_[Location::lib].clear();
}    
// ----------------------------------------------------------------------------------------------
void EdifImport:: endDesign       (void) {
    dest_.theDesign (NULL);
    dest_[Location::design].clear();
}    
// ----------------------------------------------------------------------------------------------
void EdifImport:: endCell         (void) {
    dest_[Location::ent].clear();
    dest_.theEntity(NULL);
}    
// ----------------------------------------------------------------------------------------------
void EdifImport:: endPort         (void) {
    thePorts_.clear();
}
// ----------------------------------------------------------------------------------------------
void EdifImport:: endView         (void) {
    if (dest_.theEntity()-> ArchNum() > 1) {
        if (portNum_ != dest_.theEntity()->PortNum()) {
	    throw OthException (OthReason ("architecture", dest_.theArch()->name(), "interface mismatch"), 
				LexPosition(parser_.l),
				CircCtx (dest_));
	    }
    }
    dest_.theArch (NULL); dest_[Location::arch].clear();
    theArchImpl_ = NULL;
}    
// ----------------------------------------------------------------------------------------------
void EdifImport:: endInstance     (void) {
    theInstances_.clear();
}    
// ----------------------------------------------------------------------------------------------
void EdifImport:: endNet          (void) {
    theNets_.clear();
    theNetDims_.clear();
}    
// -------------------------------------------------------------------
const hub :: PortProto :: flagsType&  EdifImport :: DecodeDir (const std::string& dir) {
    if (CompareText (dir, std::string ("input")) == 0) return PortProto :: inDir;
    if (CompareText (dir, std::string ("output")) == 0) return PortProto :: outDir;
    if (CompareText (dir, std::string ("inout")) == 0) return PortProto :: inOutDir;
    return PortProto :: noDir;
}
