#include "SyscCodeGen.h"

namespace cosi {

namespace io {

namespace onchipcommunication {

using namespace cosi::commstruct::onchipcommunication;
using namespace cosi::quantity::onchipcommunication;

void SyscCodeGen::SyscMakeFileGen() {

	string ExecName;

	int CurrentChar;
	CurrentChar = mFileName.size();
	while (mFileName[CurrentChar] != '.' && mFileName[CurrentChar] != '/') {
		CurrentChar --;
	}

	if (mFileName[CurrentChar] == '/') {
		ExecName = mFileName + ".x";
	} else {
		ExecName = string(mFileName, 0, CurrentChar) + ".x";
	}

	ofstream f;
	f.open(mMakefileName.c_str() );
	f << "COSIROOT=" << Configuration->GetCosiRoot() << endl;
	f << "SYSTEMC =" << Configuration->GetSystemCRoot() << endl;
	f << "CC= g++" << endl;
	f << "OPT= -O2" << endl;
	f << "DEBUG  = -g" << endl;
	f << "CFLAGS = $(OPT) $(DEBUG) $(OTHER)" << endl;
	f
			<< "INCLUDE = -I$(SYSTEMC)/include -I$(COSIROOT)/libraries/onchipcommunication/systemc"
			<< endl;
	f << "LIB = -L$(SYSTEMC)/lib-linux" << endl;
	f << "all: " << ExecName << endl;
	f << ExecName << ": " << mFileName << endl;
	f << "\t" << "$(CC) -o " << ExecName << " " << mFileName
			<< " $(COSIROOT)/libraries/onchipcommunication/systemc/Technology.cpp $(INCLUDE) $(LIB) -lsystemc"
			<< endl;
	f.close();
}

SyscCodeGen::SyscCodeGen(string FileName, string MakeFileName,
		Implementation* I, Specification* PtP, int pFlitWidth,
		double pClockPeriod) {

	mFileName = FileName;
	mMakefileName = MakeFileName;
	mFlitWidth = pFlitWidth;
	mClockPeriod = pClockPeriod;
	mI = I;
	mSpec = PtP;

}

void SyscCodeGen::Run() {
	SyscOpen();
	SyscScMain();
	//SyscPrintStat( f , G , PtP ) ;
	SyscClose();
	SyscMakeFileGen() ;

}

void SyscCodeGen::SyscOpen() {
	mF = new ofstream( mFileName.c_str( ) );
	(*mF) << "//Automatically generated by COSI NOC 1.3 " << endl;
	(*mF) << "#include <iostream>" << endl;
	(*mF) << "#include <map>" << endl;
	(*mF) << "#include <sstream>" << endl;
	(*mF) << "#include \"systemc.h\"" << endl;
	(*mF) << "#include \"SourceIp.h\"" << endl;
	(*mF) << "#include \"DestIp.h\"" << endl;
	(*mF) << "#include \"Router.h\"" << endl;
	(*mF) << "#include \"PtP.h\"" << endl;
	(*mF) << "#include \"Monitor.h\"" << endl;
	(*mF) << "#include \"Technology.h\"" << endl;
	(*mF) << endl;
}

void SyscCodeGen::SyscScMain() {

	(*mF) << "using namespace std ;" << endl;
	(*mF) << "#define FLITWIDTH " << mFlitWidth << endl;
	(*mF) << "#define CLOCKPERIOD " << mClockPeriod << endl;
	(*mF) << "#include \"CosiSysCutil.h\"" << endl;
	(*mF) << "int sc_main( int argc , char* argv[] ) {" << endl;
	(*mF) << "\t" << "TechnologyNode TechParam ;" << endl;

	(*mF) << "\t" << "TechParam = GetTechParameters( \""
			<< Configuration->GetCosiRoot()
			<< "/libraries/onchipcommunication/systemc/energy_10x10_100.dat\" ) ;"
			<< endl;

	(*mF) << "\t" << "Flow F ;" << endl;
	IdGraph::v_iterator U, V;

	//---------------------------------------------------------------------------
	//                    Instantiate all sources and destinations
	//---------------------------------------------------------------------------

	//For each Ip core such that the output port is bounded to some router
	//instantiate a source ip

	//TODO: single input-output port per IP core
	//currently we assume that each core has one input and output port
	//this assumption can be easily removed by using one source ip for each port.
	//We should provide a model for an IP that has multiple output ports and multiple 
	//threads.

	for (U = mSpec->v_begin(); U != mSpec->v_end(); U++) {
		Ports::iterator Pit;
		int InId = 0;
		int OutId = 0;
		for (Pit = (*mI)[*U].PRJ(Ports)Begin(); Pit != (*mI)[*U].PRJ(Ports)End(); Pit ++) {
			Interface If = (*mI)[*U].PRJ(Ports)If(Pit) ;
			string Name = (*mI)[*U].PRJ(Ports)Name(Pit) ;
			bool IsBound = (*mI)[*U].PRJ(Ports)IsBound(Name) ;
			if (If.GetDirection() == Interface::OUT && IsBound) {
				OutputIndex(*U, Name, OutId);
				OutId++;
				(*mF) << SyscSource(*U, Name);
				(*mF) << SyscSourceNetworkInterface(*U, Name);
			}
			if (If.GetDirection() == Interface::IN && IsBound) {
				InputIndex(*U, Name, InId);
				InId++;
				(*mF) << SyscDest(*U, Name);
				(*mF) << SyscDestNetworkInterface(*U, Name);
			}
		}
	}

	//---------------------------------------------------------------------------
	//                    Instantiate all routers
	//---------------------------------------------------------------------------

	for (U = mI->v_begin(); U != mI->v_end(); U++) {
		if (!mSpec->InV(*U) ) {
			//map the ports
			Ports::iterator Pit;
			int InId = 0;
			int OutId = 0;
			for (Pit = (*mI)[*U].PRJ(Ports)Begin(); Pit != (*mI)[*U].PRJ(Ports)End(); Pit ++) {
				Interface If = (*mI)[*U].PRJ(Ports)If(Pit) ;
				string Name = (*mI)[*U].PRJ(Ports)Name(Pit) ;
				bool IsBound = (*mI)[*U].PRJ(Ports)IsBound(Name) ;
				if (If.GetDirection() == Interface::OUT) {
					OutputIndex(*U, Name, OutId);
					OutId++;
				}
				if (If.GetDirection() == Interface::IN) {
					InputIndex(*U, Name, InId);
					InId++;
				}
			}
			(*mF) << SyscRouter(*U);
		}
	}

	//---------------------------------------------------------------------------
	//                    Connect components
	//---------------------------------------------------------------------------

	for (U = mSpec->v_begin(); U != mSpec->v_end(); U++) {
		Ports::iterator Pit;
		for (Pit = (*mI)[*U].PRJ(Ports)Begin(); Pit != (*mI)[*U].PRJ(Ports)End(); Pit ++) {
			Interface If = (*mI)[*U].PRJ(Ports)If(Pit) ;
			string Name = (*mI)[*U].PRJ(Ports)Name(Pit) ;
			bool IsBound = (*mI)[*U].PRJ(Ports)IsBound(Name) ;
			if (If.GetDirection() == Interface::OUT && IsBound) {
				//there must be a source
				//connect the source to the interface 
				(*mF) << "\t" << "COSI_SOURCE_TO_IF_CONNECTION( " << *U
						<< " , " << *U << " , 0 , 6 , 0.0 )  ; " << endl;
			}
			if (If.GetDirection() == Interface::IN && IsBound) {
				//conect the interface to the destination
				(*mF) << "\t" << "COSI_IF_TO_DEST_CONNECTION( " << *U
						<< " , 0 , " << *U << " , 6 , 0.0 ) ; " << endl;
			}
		}
	}

	//Connect source interfaces to routers
	for (U = mI->v_begin(); U != mI->v_end(); U++) {
		for (V = mI->v_begin(); V != mI->v_end(); V++) {
			if (mI->InE(*U, *V) ) {
				string P = (*mI)[*U].PRJ(Ports)IsBoundTo(Edge(*U, *V), Interface::OUT) ;
				int Upid = OutputIndex(*U, P) ;
				P = (*mI)[*V].PRJ(Ports)IsBoundTo(Edge(*U, *V), Interface::IN) ;
				int Vpid = InputIndex(*V, P) ;
				double Dist = Norm1((*mI)[*U].PRJ(Position)Get() , (*mI)[*V].PRJ(Position)Get() ) ;

				if ( !mSpec->InV(*U) && !mSpec->InV(*V)) {
					(*mF) << "\t" << "COSI_ROUTER_TO_ROUTER_CONNECTION( " << *U
							<< " , " << Upid << " , " << *V << " , " << Vpid
							<< " , 6 , " << Dist << " )  ; " << endl;
				} else if ( !mSpec->InV(*U) && mSpec->InV(*V)) {
					(*mF) << "\t" << "COSI_ROUTER_TO_IF_CONNECTION( " << *U
							<< " , " << Upid << " , " << *V << " , " << Vpid
							<< " , 6 , " << Dist << " )  ; " << endl;
				} else if (mSpec->InV(*U) && !mSpec->InV(*V)) {
					(*mF) << "\t" << "COSI_IF_TO_ROUTER_CONNECTION( " << *U
							<< " , " << Upid << " , " << *V << " , " << Vpid
							<< " , 6 , " << Dist << " )  ; " << endl;
				} else {
					(*mF) << "\t" << "COSI_IF_TO_IF_CONNECTION( " << *U
							<< " , " << Upid << " , " << *V << " , " << Vpid
							<< " , 6 , " << Dist << " )  ; " << endl;
				}
			}
		}
	}

	(*mF)
			<< "Monitor< FLITWIDTH> M(\"Monitor\" , 0.000001 , _CosiDestIpVector_ ) ;"
			<< endl;

	(*mF) << SyscTrace() ;

	(*mF) << "sc_start( 0.00001 , SC_SEC);" << endl;

	(*mF) << "M.WriteReport( ) ; " << endl;

	(*mF) << "sc_close_vcd_trace_file(trcf);" << endl ;

	(*mF) << "system(\"" << Configuration->GetTracerRoot() << "/" << Configuration->GetTracerExec() << " cosisyscsim.vcd\");" << endl ;
	
	(*mF) << "exit( 0 ) ; " << endl;

}

string SyscCodeGen::SyscTrace() {
	//trace souces and destinations
	stringstream s;
	s << "sc_trace_file *trcf= sc_create_vcd_trace_file(\"cosisyscsim\");"
			<< endl;
	IdGraph::v_iterator U ; 
	for (U = mSpec->v_begin(); U != mSpec->v_end(); U++) {
		Ports::iterator Pit;
		int InId = 0;
		int OutId = 0;
		for (Pit = (*mI)[*U].PRJ(Ports)Begin(); Pit != (*mI)[*U].PRJ(Ports)End(); Pit ++) {
			Interface If = (*mI)[*U].PRJ(Ports)If(Pit) ;
			string Name = (*mI)[*U].PRJ(Ports)Name(Pit) ;
			bool IsBound = (*mI)[*U].PRJ(Ports)IsBound(Name) ;
			if (If.GetDirection() == Interface::OUT && IsBound) {
				s << "sc_trace(trcf, S" << *U << "_Data, \"" << mSpec->GetName(*U) << "_data_out\");" << endl ;
				s << "sc_trace(trcf, S" << *U << "_Valid, \"" << mSpec->GetName(*U) << "_valid_out\");" << endl ;
				s << "sc_trace(trcf, S" << *U << "_Ack, \"" << mSpec->GetName(*U) << "_ack_in\");" << endl ;
				s << "sc_trace(trcf, S" << *U << "_Full, \"" << mSpec->GetName(*U) << "_full_in\");" << endl ;
				s << "sc_trace(trcf, ClkS" << *U << ", \"" << mSpec->GetName(*U) << "_clk\");" << endl ;
			}
			if (If.GetDirection() == Interface::IN && IsBound) {
				s << "sc_trace(trcf, D" << *U << "_Data, \"" << mSpec->GetName(*U) << "_data_in\");" << endl; 
				s << "sc_trace(trcf, D" << *U << "_Valid, \"" << mSpec->GetName(*U) << "_valid_in\");" << endl;
				s << "sc_trace(trcf, D" << *U << "_Ack, \"" << mSpec->GetName(*U) << "_ack_out\");" << endl;
				s << "sc_trace(trcf, D" << *U << "_Full, \"" << mSpec->GetName(*U) << "_full_out\");" << endl ;
			}
		}
	}
	return s.str() ;
	
}

void SyscCodeGen::SyscClose() {

	(*mF) << "}" << endl;
}

string SyscCodeGen::SyscSource(int S, string Port) {

	stringstream s;

	s << "\t" << " //Source " << mSpec->GetName(S) << " port  " << Port
	<< " has id " << S << endl;

	///////////////////////////////////////////////////////////////////////////
	//////                Flows information
	///////////////////////////////////////////////////////////////////////////
	s << "\t" << "vector< Flow > Flows" << S << ";" << endl;

	IdGraph::v_iterator D;

	//TODO: look at the commodities on the edge bound to the port insted of checking the specification
	for (D = mSpec->v_begin(); D != mSpec->v_end(); D++) {
		if (mSpec->InE(S, *D) ) {
			//TODO: make the packet size a parameter
			s << "\t" << "F.PacketSize = 4 ; " << endl;
			s << "\t" << "F.Destination = " << *D << ";" << endl;
			s << "\t" << "F.Bandwidth = " << (*mSpec)[Edge(S,*D)].PRJ(CommoditySet)GetAggregateBandwidthValue() << ";" << endl;
			s << "\t" << "Flows" << S << ".push_back( F ) ;" << endl;
		}
	}

	///////////////////////////////////////////////////////////////////////////
	//////                Instantiate the source and the clock
	///////////////////////////////////////////////////////////////////////////

	s << "\t" << "SourceIp<FLITWIDTH> S" << S << "( \"s_" << mSpec->GetName(S) << "\" , "<< S
	<< " , Flows" << S <<" ) ;" << endl;
	s << "\t" << "sc_clock ClkS" << S << "( \"ClkS" << S
	<< "\" , CLOCKPERIOD , SC_SEC ) ;" << endl;
	s << "\t" << "S" << S << ".Clock( ClkS" << S <<" ) ;" << endl;

	return s.str();
}

string SyscCodeGen::SyscDest(int D, string Port) {
	stringstream s;
	s << "\t"<< "//Destination " << mSpec->GetName(D) << " port " << Port
	<< " has id " << D << endl;
	s << "\t" << "DestIp<FLITWIDTH> D" << D << "( \"d_" << mSpec->GetName(D) << "\" , " << D
	<< " ) ;" << endl;
	s << "\t" << "sc_clock ClkD" << D << "( \"ClkD" << D
	<< "\" , CLOCKPERIOD , SC_SEC ) ;" << endl;
	s << "\t" << "D" <<D << ".Clock( ClkD" << D << " ) ;" << endl;
	s << "\t" << "_CosiAddDestIp_( &D" << D << " ) ;" << endl;
	return s.str();

}

string SyscCodeGen::SyscRouter(int V) {
	stringstream s;
	TransferTable::iterator Entry;
	TransferTable T = (*mI)[V].PRJ(TransferTable)Get();

	//---------------------------------------------------------------------------
	//                     Generate the routing table
	//---------------------------------------------------------------------------

	s << "\t" << "map< pair<int,int> , int > RT" << V << " ;" << endl;

	for (Entry = T.TransferTableBegin(); Entry != T.TransferTableEnd(); Entry++) {
		int Sid = mSpec->GetId((Entry->first).first);
		int Did = mSpec->GetId((Entry->first).second);
		int Pid = OutputIndex(V, Entry->second);
		s << "\t" << "RT" << V << "[ pair<int,int>( " << Sid << " , " << Did
		<< ") ] = " << Pid << " ; " << endl;
	}

	//---------------------------------------------------------------------------
	//                     Generate weight vector
	//
	// Has to take into account the number of inputs that
	// share the same output. In particular given two inputs i1 and i2, let I1
	// and I2 be the set of inputs that share the same output of i1 and i2,
	// respectively.
	// Then, w(i1) / w(i2) = bw(i1)|I1| / ( bw(i2)|I2|).
	// Currently this is not implemented, therefore the systemc simulation
	// shows an unfairness phenomenon.
	//---------------------------------------------------------------------------
	s << "\t" << "vector<double> Weights" << V << "( " << mI->InDegree(V)
	<< " ) ;" << endl;
	//Compute the total input flow
	IdGraph::list_iterator U;
	double InputRate = 0.0;
	for (U = mI->in_begin(V); U != mI->in_end(V); U++) {
		InputRate += (*mI)[Edge(*U,V)].PRJ(CommoditySet)GetAggregateBandwidthValue();
	}

	for (U = mI->in_begin(V); U != mI->in_end(V); U++) {
		string P = (*mI)[V].PRJ(Ports)IsBoundTo(Edge(*U, V), Interface::IN);
		s << "\t" << "Weights" << V << "[ " << InputIndex(V, P) << "] = " << (*mI)[Edge(*U,V)].GetAggregateBandwidthValue() / InputRate << " ; "
		<< endl;

	}

	//---------------------------------------------------------------------------
	//                     Instantiate the router and the clock
	//---------------------------------------------------------------------------

	s << "\t" << "Router< " << mI->InDegree(V) << " , " << mI->OutDegree(V)
	<< ", FLITWIDTH > R" << V << "( \"R" << V << "\" , 1 , 2 , RT" << V
	<< " , Weights" << V <<" , TechParam ) ; " << endl;
	s << "\t" << "sc_clock ClkR" << V << "( \"ClkR" << V
	<< "\" , CLOCKPERIOD , SC_SEC ) ;" << endl;
	s<< "\t" << "R" << V << ".Clock( ClkR" << V << ") ;" << endl;
	return s.str();

}

string SyscCodeGen::SyscSourceNetworkInterface(int S, string P) {

	stringstream s;
	TransferTable T;
	TransferTable::iterator Entry;
	T = (*mI)[S].PRJ(TransferTable)Get();
	//---------------------------------------------------------------------------
	//                     Generate the routing table
	//---------------------------------------------------------------------------

	s << "\t" << "map< pair<int,int> , int > ST" << S << " ;" << endl;
	for (Entry = T.TransferTableBegin(); Entry != T.TransferTableEnd(); Entry++) {
		int Sid = mSpec->GetId((Entry->first).first);
		int Did = mSpec->GetId((Entry->first).second);
		int Pid = OutputIndex(S, Entry->second);
		s << "\t" << "ST" << S << "[ pair<int,int>( " << Sid << " , " << Did
		<< ") ] = " << Pid << " ; " << endl;
	}

	//---------------------------------------------------------------------------
	//                     Generate weight vector
	//---------------------------------------------------------------------------
	s << "\t" << "vector<double> Sweights" << S << "( " << 1 << " ) ;" << endl;
	s << "\t" << "Sweights" << S << "[ 0 ] = 1.0 ; " << endl;

	//---------------------------------------------------------------------------
	//                     Instantiate the router and the clock
	//---------------------------------------------------------------------------

	s << "\t" << "Router< 1 , " << mI->OutDegree(S) << ", FLITWIDTH > Sif" << S
	<< "( \"Sif" << S << "\" , 1 , 2 , ST" << S << " , Sweights" << S
	<<" , TechParam ) ; " << endl;
	s << "\t" << "sc_clock ClkSif" << S << "( \"ClkSif" << S
	<< "\" , CLOCKPERIOD , SC_SEC ) ;" << endl;
	s<< "\t" << "Sif" << S << ".Clock( ClkSif" << S << ") ;" << endl;
	return s.str();
}

string SyscCodeGen::SyscDestNetworkInterface(int D, string P) {
	stringstream s;
	map< pair<int,int> , int > RoutingTable;
	map< pair<int,int> , int >::iterator Entry;

	//---------------------------------------------------------------------------
	//                     Generate the routing table
	//
	// We need to create the routing table because destinations don't
	// have one. We look at the PtP graph and for each requirements
	// (s,d) to this destination we add an entry (<s,d>,0) to the
	// routing table.
	//---------------------------------------------------------------------------
	IdGraph::list_iterator S;
	for (S = mSpec->in_begin(D); S != mSpec->in_end(D); S++) {
		if (mSpec->InE(*S, D) ) {
			RoutingTable[ pair<int,int>(*S,D) ] = 0;
		}
	}

	s << "\t" << "map< pair<int,int> , int > DT" << D << " ;" << endl;
	for (Entry = RoutingTable.begin(); Entry != RoutingTable.end(); Entry++) {
		s << "\t" << "DT" << D << "[ pair<int,int>( " << (Entry->first).first << " , " << (Entry->first).second << ") ] = " << Entry->second << " ; "
		<< endl;
	}

	//---------------------------------------------------------------------------
	//                     Generate weight vector
	//---------------------------------------------------------------------------
	s << "\t" << "vector<double> Dweights" << D << "( " << mI->InDegree(D)
	<< " ) ;" << endl;
	//Compute the total input flow by summing all aggregate flows
	double InputRate = 0.0;
	for (S = mSpec->in_begin(D); S != mSpec->in_end(D); S++) {
		if (mI->InE(*S, D) ) {
			InputRate += (*mI)[Edge(*S,D)].PRJ(CommoditySet)GetAggregateBandwidthValue();
		}
	}

	for (S = mSpec->in_begin(D); S != mSpec->in_end(D); S++) {
		if (mI->InE(*S, D) ) {
			string P = (*mI)[D].PRJ(Ports)IsBoundTo(Edge(*S, D), Interface::IN);
			s << "\t" << "Dweights" << D << "[ " << InputIndex(D, P) << "] = "
			<< (*mI)[Edge(*S,D)].PRJ(CommoditySet)GetAggregateBandwidthValue()
			/ InputRate << " ; " << endl;
		}
	}

	//---------------------------------------------------------------------------
	//                     Instantiate the router and the clock
	//---------------------------------------------------------------------------

	s << "\t" << "Router< " << mI->InDegree(D) << " , 1 , FLITWIDTH > Dif" << D
	<< "( \"Dif" << D << "\" , 1 , 2 , DT" << D << " , Dweights" << D
	<<" , TechParam ) ; " << endl;
	s << "\t" << "sc_clock ClkDif" << D << "( \"ClkDif" << D
	<< "\" , CLOCKPERIOD , SC_SEC ) ;" << endl;
	s<< "\t" << "Dif" << D << ".Clock( ClkDif" << D << ") ;" << endl;
	return s.str();
}

string SyscCodeGen::SyscConnect(
		cosi::commstruct::onchipcommunication::Implementation* G, int U, int V) {
	stringstream s;
	return s.str();
}

void SyscCodeGen::SyscPrintStat(ofstream* f,
		cosi::commstruct::onchipcommunication::Implementation* G,
		cosi::commstruct::onchipcommunication::Specification* PtP) {
	//Gather energy from all routers
	(*mF) << "double TotalRouterEnergy = 0 ; " << endl;

	for (int RouterId = G->Ns() + G->Nd(); RouterId < G->Ns() + G->Nd()
			+ G->Nr(); RouterId++) {
		(*mF) << "TotalRouterEnergy += R" << RouterId << "->GetEnergy( );"
		<< endl;
	}
	//Gather Energy for all wires
	(*mF) << "double TotalWireEnergy = 0 ; " << endl;
	for (int SourceId = 0; SourceId < G->Order(); SourceId++) {
		for (int DestId = 0; DestId < G->Order(); DestId++) {
			if (G->InE(SourceId, DestId) ) {
				(*mF) << "TotalWireEnergy += N" << SourceId << "_N"<< DestId
				<< "->total_energy ; "<< endl;

			}
		}
	}

	(*mF) << "cout << \"Power report :\"<<endl ; " << endl;
	(*mF) << "cout << \"\t Wires : \" << TotalWireEnergy << endl ;" << endl;
	(*mF) << "cout << \"\t Routers : \" << TotalRouterEnergy << endl ;" <<endl;
}

int SyscCodeGen::InputIndex(int V, string P) {
	//TODO : what is there is no such combination in the map?
	return mInputPortIndex[pair<int,string>(V ,P )];
}

int SyscCodeGen::OutputIndex(int V, string P) {
	//TODO : what is there is no such combination in the map?
	return mOutputPortIndex[pair<int,string>(V ,P )];
}

void SyscCodeGen::InputIndex(int V, string P, int Pid) {
	mInputPortIndex[pair<int,string>(V ,P )] = Pid;
}

void SyscCodeGen::OutputIndex(int V, string P, int Pid) {
	mOutputPortIndex[pair<int,string>(V ,P )] = Pid;
}

}
}
}
