//-------------------------------------------------------
main() {
 int Gbs_x=map_x/Rbs; // Number of grids on y=1
 int Gbs_y=map_y/Rbs; // Number of grids on x=1
 int Nbs_x1=floor(Gbs_x/2);	//Number og BSs on y=1
 int Nbs_x2=floor(((map_x-Rbs)/Rbs)/2);	//Number og BSs on y=2
 int Nbs_y1=floor(Gbs_y/2);	//Number og BSs on x=1
 int Nbs_y2=floor(((map_y-Rbs)/Rbs)/2);	//Number og BSs on x=2
 int Tbs=(Nbs_x1*Nbs_y1)+(Nbs_x2*Nbs_y2);	//Total BS on map
 int length=Rbs/space;	//real length
 int Px=map_x/space;	//number of points on x-axis
 int Py=map_y/space;	//number of points on y-axis
 double TN;	//Thermal Noise
 int i,j;

 ptrD=&D[0][0];
 ptrBR=&BR[0];
 ptrModulation=&Modulation[0];
 ptrSensitivity=&Sensitivity[0];
 ptrDP=&DP[0];
 ptrSNR=&SNR[0];
 ptrBPL=&BPL[0];
 ptrLT=&LT[0];
 ptrDS=&DS[0];
 ptrPLT=&PathLossType[0][0];
 ptrPL=&PL[0][0];
 //ptrP=&P[0][0];
//printf("\n%d\n",Nbs_x1);printf("\n%d\n",Nbs_x2);printf("\n%d\n",Nbs_y1);printf("\n%d\n",Nbs_y2);printf("\n%d\n",Tbs);

 //for(counter=0;counter<_t;counter++)
	 //totalr+=R[counter];//totalr = 4+6+8+5+2


 //checkInputs();
 fillCoordinatesBSs(Xbs,Ybs,Gbs_x,Gbs_y,Nbs_x1,Nbs_x2,Nbs_y1,Nbs_y2,length); 
 /*for(i=1;i<=Tbs;i++)
	 printf("Xbs[%d],Ybs[%d]=(%d,%d)\n",i,i,Xbs[i],Ybs[i]);
 printf("\n");*/
 fillCoordinatesTPs(Xtp,Ytp,Px,Py,Tbs,Xbs,Ybs);
 /*for(i=1;i<=Ntp;i++)
	 printf("Xtp[%d],Ytp[%d]=(%d,%d)\n",i,i,Xtp[i],Ytp[i]);*/
 fillDistance(Xbs,Ybs,Xtp,Ytp,ptrD,Tbs);
 printf("\n");
/*for(i=1;i<=Tbs;i++)
	for(j=1;j<=Ntp;j++)
		printf("D[%d][%d]=%.2lf\n",i,j,D[i][j]);
printf("\n");	*/
 fillMs(ptrBR);
/* for(i=1;i<=Ntp;i++)
	 printf("BR[%d]:%.2lf Mbps\n",i,BR[i]);
 printf("\n");*/
 TN=ThermalNoise();
 //printf("%.2lf\n",TN);
 //printf("\n");
 modulation(TN,ptrBR,Modulation,Sensitivity,DP,SNR);
 //for(i=1;i<=Ntp;i++)
//	 printf("TP[%d] with modulation %d, DP: %.2lf, SNR: %.2lf, S: %.2lf\n",i,Modulation[i],DP[i],SNR[i],Sensitivity[i]);
 //printf("\n");
 LocationType(ptrLT,Xtp,Ytp);
 bpl(ptrLT,ptrBPL);
 //for(i=1;i<=Ntp;i++)
//	 printf("TP[%d]'s location type is %d, bpl is %d\n",i,LT[i],BPL[i]);
 DataSubcarriers(ptrDS,ptrBR,ptrDP);
 //printf("\n");
 //for(i=1;i<=Ntp;i++)
//	 printf("data subcarriers for TP[%d]:%d\n",i,DS[i]);
// printf("\n");
 
 PathLoss(ptrD,ptrPLT,ptrLT,Tbs,ptrPL);
power(Tbs,TN);
FixedCellSize(Tbs,DS,ptrD,P[0],ptrBR,Xbs,Ybs,Xtp,Ytp);

FILE *PowerSavingCPLEX;
if((PowerSavingCPLEX=fopen("PowerSavingCPLEX","w"))==NULL)
	printf("\nerror!Fail to open file!");
else
	printf("\nOpen PowerSavingCPLEX successfully!\n");
fprintf(PowerSavingCPLEX,"This is the input to CPLEX for power saving model.\n");
 objective(Tbs,PowerSavingCPLEX);
fprintf(PowerSavingCPLEX,"st\n");
 printf("st\n");
 constraint1(Tbs,ptrDS,PowerSavingCPLEX);
 constraint2(Tbs,PowerSavingCPLEX);
 constraint3(Tbs,PowerSavingCPLEX);
 constraint4(Tbs,PowerSavingCPLEX);
 constraint5(Tbs,PowerSavingCPLEX);
 //constraint5(nr,nt,totalr);
 constraint6(Tbs,PowerSavingCPLEX);
 constraint7(Tbs);
 constraint8(Tbs);
 constraint9(Tbs);
fprintf(PowerSavingCPLEX,"bounds\n");
 printf("bounds\n");
 bounds(Tbs,PowerSavingCPLEX);

 specifyTypes(Tbs,PowerSavingCPLEX);
fprintf(PowerSavingCPLEX,"end\n");
 printf("end\n");
fclose(PowerSavingCPLEX);
 checkMSsites(Tbs);
	heuristic(Tbs,P[0],Ntp,MP,DSt,BP,DS,Xbs,Ybs,Xtp,Ytp,ptrD,BR);
	printf("===================================================================================================================================\n");
	Sheuristic(Tbs,P[0],Ntp,MP,DSt,BP,DS,Xbs,Ybs,Xtp,Ytp,ptrD,BR); 

FILE *outfile, *outfile1;
 if ((outfile=fopen("outfile1.txt", "w")) == NULL)
printf("\n\nerror!Fail to open file!");
else
printf("\n\nOpen file successfully!\n");
fprintf(outfile,"BS%dMS%dBP%g\n",Tbs,Ntp,BP);
for(i=1;i<=Tbs;i++)
for(j=1;j<=Ntp;j++)
	fprintf(outfile,"%d. BS[%d]=%d,%d MS[%d]=%d,%d DS=%d power=%g mW BW=%g Mbps\n",(i-1)*Ntp+j,i,Xbs[i],Ybs[i],j,Xtp[j],Ytp[j],DS[j],P[i][j],BR[j]);
fclose(outfile);

if ((outfile1=fopen("coordinates.txt", "w")) == NULL)
printf("\n\nerror!Fail to open file!");
else
printf("\n\nOpen coordinates.txt successfully!\n");

fprintf(outfile1,"#BS%dMS%dBP%g\n",Tbs,Ntp,BP);
for(i=1;i<=Tbs;i++){
for(j=1;j<=Ntp;j++)
fprintf(outfile1,"%d. BS[%d] %d %d MS[%d] %d %d DS %d power %g mW BW %g Mbps\n",(i-1)*Ntp+j,i,Xbs[i],Ybs[i],j,Xtp[j],Ytp[j],DS[j],P[i][j],BR[j]);
}
fprintf(outfile1,"\n\n");
for(i=1;i<=Tbs;i++)
fprintf(outfile1," BS[%d]=%d,%d \n",i,Xbs[i],Ybs[i]);

fclose(outfile1);
 //system("pause");
 return;
}
int main(int argc, char **argv) {
	clock_t t1, t2;
	t1 = clock();

	IloEnv env;
	IloModel model(env);
	IloCplex cplex(model);

	/************************** Defining the parameters ************************************/
	IloInt N;							//No. of nodes
	IloInt M;							//No. of calls
	Num2DMatrix links(env);				//Defines the topology of the network
	IloNumArray call_demand(env);		//link bandwidth requirement of each call
	IloNumArray call_revenue(env);		//revenue generated from the call
	IloNumArray call_origin(env);		//origin node index of each call
	IloNumArray call_destination(env);	//destination node index of each call
	Num2DMatrix Q(env);					//Bandwidth capacity of each link
	Num2DMatrix sigma(env);				//Standard deviation of service times on link (i,j)
	Num2DMatrix cv(env);				//coefficient of variation of service times on the link (i,j)
	IloNum C;							//Unit queueing delay cost per unit time
	IloNumArray R_approx_init(env);

	ifstream fin;
	const char* filename = "BPP_data_sample - Copy.txt";
	if (argc > 1)
		filename = argv[1];
		
	fin.open(filename);
	//fin.open("BPP_10node_navneet.txt");
	fin >> links >> call_origin >> call_destination >> call_demand >>
		call_revenue >> Q >> cv >> R_approx_init >> C ;
	cout << "Reading Data from the file - "<<filename<<endl;

	N = links.getSize();
	M = call_origin.getSize();

	IloInt H = R_approx_init.getSize();
	Num3DMatrix R_approx(env, N);			//The tangential linear function approximation to R.
	for (IloInt i=0; i<N; i++) {
		R_approx[i] = Num2DMatrix(env, N);
		for (IloInt j=0; j<N; j++) {
			R_approx[i][j] = IloNumArray(env, H);
			for (IloInt h=0; h<H; h++)
				R_approx[i][j][h] = R_approx_init[h];
		}
	}

	/************************** Defining the parameters ENDS ************************************/

	/************* Defining the variables defined in the model formulation **********************/
	IloNumVarArray Y(env, M, 0, 1, ILOINT); //Variable to define whether a call m is routed or not
	IloNumArray Y_sol(env, M); //Solution values

	NumVar3DMatrix X(env, N); //Variable to define whether a call m is routed along path i-j
	Num3DMatrix X_sol(env, N);
	for (IloInt i=0; i<N; i++) {
		X[i] = NumVar2DMatrix(env, N);
		X_sol[i] = Num2DMatrix(env, N);
		for (IloInt j=0; j<N; j++) {
			X[i][j] = IloNumVarArray(env, M, 0, 1, ILOINT);
			X_sol[i][j] = IloNumArray(env, M);
		}
	}

	NumVar3DMatrix W(env, N); //Variable to define whether a call m is routed along path i-j
	for (IloInt i=0; i<N; i++) {
		W[i] = NumVar2DMatrix(env, N);
		for (IloInt j=0; j<N; j++)
			W[i][j] = IloNumVarArray(env, M, 0, 1, ILOINT);
	}

	NumVar2DMatrix R(env, (IloInt)N); //The linearization Variable
	for (IloInt i=0; i<N; i++)
		R[i] = IloNumVarArray(env, (IloInt)N, 0, IloInfinity, ILOFLOAT);
	
	/************* Defining the variables defined in the model formulation ENDS *****************/

	/**************************** Defining the Constraints *******************************/
	// Constraint #1 : Flow Conservation Constraint
	for (IloInt m=0; m<M; m++) {
		for (IloInt i=0; i<N; i++) {
			IloExpr constraint1(env);
			for (IloInt j=0; j<N; j++) {
				if (links[i][j] == 1)
					constraint1 += W[i][j][m];
			}
			for (IloInt j=0; j<N; j++) {
				if (links[j][i] == 1)
					constraint1 += -W[j][i][m];
			}
			
			if (i == call_origin[m])
				model.add(constraint1 == Y[m]);
			else if (i == call_destination[m])
				model.add(constraint1 == -Y[m]);
			else 
				model.add(constraint1 == 0);

			constraint1.end();
		}
	}

	// Constraint #2 :
	for (IloInt m=0; m<M; m++) {
		for (IloInt i=0; i<N; i++) {			
			for (IloInt j=0; j<N; j++) {
				if (links[i][j] == 1)
					model.add(W[i][j][m] + W[j][i][m] <= X[i][j][m]);					
			}			
		}
	}

	// Constraint #3 : Link Capacity Constraint
	for (IloInt i=0; i<N; i++) {
		for (IloInt j=i+1; j<N; j++) {
			if (links[i][j] == 1) {
				IloExpr constraint3(env);
				for (IloInt m=0; m<M; m++)
					constraint3 += call_demand[m]*X[i][j][m];
				model.add(constraint3 <= Q[i][j]);
				constraint3.end();
			}
		}
	}
	
	// Constraint #4 : Defining the constraint for initial values of R_approx, 
	//				   Cuts must be added during the iterations whenever the values are updated
	for (IloInt i=0; i<N; i++) {
		for (IloInt j=i+1; j<N; j++) {	
			if (links[i][j] == 1) {
				for (IloInt h=0; h<H; h++) {
					IloExpr constraint4_lhs(env);
					IloNum constraint4_rhs = 0;
					for (IloInt m=0; m<M; m++)
						constraint4_lhs += call_demand[m]*X[i][j][m];

					constraint4_lhs -= (Q[i][j]/((1+R_approx[i][j][h])*(1+R_approx[i][j][h])))*R[i][j];
					constraint4_rhs = Q[i][j]*((R_approx[i][j][h]/(1+R_approx[i][j][h])) *
											(R_approx[i][j][h]/(1+R_approx[i][j][h])));
					model.add(constraint4_lhs <= constraint4_rhs);
					constraint4_lhs.end();
				}
			}
		}
	}

	/************************** Defining the Constraints ENDS ****************************/

	/************************ Defining the Objective Function ****************************/
	IloExpr Objective(env);
	IloExpr Obj_expr1(env);
	IloExpr Obj_expr2(env);
	
	for (IloInt m=0; m<M; m++)
		Obj_expr1 += call_revenue[m]*Y[m];

	for (IloInt i=0; i<N; i++) {
		for (IloInt j=i+1; j<N; j++) {
			if (links[i][j] == 1) {
				Obj_expr2 += (1+cv[i][j] * cv[i][j])*R[i][j];
				for (IloInt m=0; m<M; m++) 
					Obj_expr2 += ((1-cv[i][j] * cv[i][j])/Q[i][j])*call_demand[m]*X[i][j][m];
			}
		}
	}
	Objective += Obj_expr1 - 0.5*C*Obj_expr2;
	model.add(IloMaximize(env, Objective));
	//model.add(IloMinimize(env, -Objective));

	Objective.end();
	Obj_expr1.end();
	Obj_expr2.end();

	/********************** Defining the Objective Function ENDS **************************/

	IloNum eps = cplex.getParam(IloCplex::EpInt);

	IloNum UB = IloInfinity;
	IloNum LB = -IloInfinity;

	/***************** Solve ***********************/
	do {
		cplex.setParam(IloCplex::MIPInterval, 5);
		cplex.setParam(IloCplex::NodeFileInd ,2);
		cplex.setOut(env.getNullStream());

		cplex.exportModel("BPP_model.lp");

		if(!cplex.solve()) {
			cout << "Infeasible"<<endl;
			system("pause");
		}
		else {
			for (IloInt m=0; m<M; m++) {
				if (cplex.getValue(Y[m]) > eps) {
					cout << "Call(m) = "<<m+1<<" : "<<call_origin[m]+1<<" --> "<<call_destination[m]+1
						<<"; demand = "<<call_demand[m]<<endl;
					cout << "Path : ";
					for (IloInt i=0; i<N; i++) {
						for (IloInt j=i+1; j<N; j++) {
							if (links[i][j] == 1) {
								if (cplex.getValue(X[i][j][m]) > eps) {
									X_sol[i][j][m] = 1;
									cout <<i+1<<"-"<<j+1<<"; ";
								}
							}
						}
					}
					cout << endl << endl;
				}				
			}		

			//system("pause");
		}

		UB = min(UB, cplex.getObjValue());

		IloNum lbound = 0;
		for (IloInt m=0; m<M; m++) 
			if(cplex.getValue(Y[m]) > eps)
				lbound += call_revenue[m];

		for (IloInt i=0; i<N; i++) {
			for (IloInt j=i+1; j<N; j++) {
				if (links[i][j] == 1) {
					IloNum lbound_temp1 = 0;
					IloNum lbound_temp2 = 0;
					for (IloInt m=0; m<M; m++)
						lbound_temp1 += call_demand[m]*X_sol[i][j][m];
					lbound_temp2 = 0.5*(1+cv[i][j]*cv[i][j]) * (lbound_temp1*lbound_temp1) / (Q[i][j]*(Q[i][j]-lbound_temp1));
					lbound_temp2 += lbound_temp1 / Q[i][j];

					lbound -= C*lbound_temp2;
				}
			}
		}

		LB = max(LB, lbound);
		
		Num2DMatrix R_approx_new(env, N);
		for (IloInt i=0; i<N; i++)
			R_approx_new[i] = IloNumArray(env, N);

		for (IloInt i=0; i<N; i++) {			
			for (IloInt j=i+1; j<N; j++) {	
				if (links[i][j] == 1) {
					IloExpr cut_lhs(env);
					IloNum cut_rhs = 0;		

					IloNum cut_temp = 0;
					for (IloInt m=0; m<M; m++) {
						cut_temp += call_demand[m]*X_sol[i][j][m];
					}

					R_approx_new[i][j] = cut_temp / (Q[i][j] - cut_temp);
					//cout << "R_approx_new = "<<R_approx_new<<endl;
										
					for (IloInt m=0; m<M; m++)
						cut_lhs += call_demand[m]*X[i][j][m];					

					cut_lhs -= (Q[i][j]/((1+R_approx_new[i][j])*(1+R_approx_new[i][j])))*R[i][j];
					cut_rhs = Q[i][j]*((R_approx_new[i][j]/(1+R_approx_new[i][j])) *
												(R_approx_new[i][j]/(1+R_approx_new[i][j])));

					model.add(cut_lhs <= cut_rhs);
					cut_lhs.end();
				}				
			}
		}

		cout << "UB = "<<UB<<endl;
		cout << "LB = "<<LB<<endl;
		cout << "Gap (%) = "<<(UB-LB)*100/LB<<endl;
		//system("pause");

	}while ((UB-LB)/UB > eps);
	t2 = clock();
	float secs = (float)t2 - (float)t1;
	secs = secs / CLOCKS_PER_SEC;
	cout << "CPUTIME = "<<secs <<endl<<endl;
}