/***************************** LICENSE START ***********************************

 Copyright 2013 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "Metview.h"
#include "MvPath.hpp"
#include "MvDate.h"
#include "Tokenizer.h"

#include <iostream>
#include <stdexcept>
#include <algorithm>
 
#include <unistd.h>


#include "MvMiscelaneous.h"

using namespace std;
		
#define VAPOR_CHK(str) if(!(str)) return

class Base : public MvService { 
protected:
	Base(char* a) : MvService(a) {};
};

class VaporPrep: public Base {	
public:
	VaporPrep() : Base("VAPOR_PREPARE") {};
	void serve(MvRequest&,MvRequest&);

protected:	
	bool getDate(const string& dd,string& res,const string& parName);
	bool getParamValue(string &resVal,MvRequest& in,const string& parMv,bool canBeEmpty=false);
	bool checkGeometry(const string& areaStr,const string& gridStr);
	bool vdfExists(MvRequest&,bool &);
	bool getInputFiles(std::vector<string>& resVal,MvRequest& in,const string& parMv);
	void vapor_visualise(MvRequest&,MvRequest&);
	void vapor_execute(MvRequest&,MvRequest&);
};


bool VaporPrep::getInputFiles(std::vector<string>& resVal,MvRequest& in,const string& parMv)
{
		MvRequest r=in(parMv.c_str());
		
		do 
		{
				MvRequest rs=r.justOneRequest();
				if(const char *c=rs("PATH"))
				{
							resVal.push_back(string(c));
				}	
					
		} while(r.advance());
		
		bool retVal=(resVal.size() >0)?true:false;
				
		if(!retVal)
		{
			marslog(LOG_EROR,"No input files specified for parameter: %s",parMv.c_str());
			setError(13);
		}	
		
	    return retVal;
}	

bool VaporPrep::getDate(const string& dd,string& res,const string& parName)
{ 
  	res=dd;
	
	if(dd.size() < 8)
	{
		istringstream iss(dd);
 		double d;
		iss >> d; 
		char buf[9];
		MvDate md(mars_julian_to_date(today() + d,1));
		md.Format("yyyymmdd", buf);
		res=string(buf);
	}
	
	bool retVal=(res.size() == 8)?true:false;
	
	if(!retVal)
	{
		marslog(LOG_EROR,"Invalid date format used for parameter: %s",parName.c_str());
		setError(13);
	}	
		
	return retVal;
}  

bool VaporPrep::getParamValue(string& resVal,MvRequest& in,const string& parMv,bool canBeEmpty)
{
	int cnt = in.countValues(parMv.c_str());

	if(cnt == 1)
	{
	  	const char *cval=in(parMv.c_str());
		if(cval) 
			resVal=string(cval);
	}
	else
	{
		vector<string> vals;
		int toIndex=-1;
		int byIndex=-1;
		for( int i=0; i<cnt; i++)
	     	{
	      		const char *cval = in(parMv.c_str(),i );
			if(cval)
			{
			  	vals.push_back(string(cval));
				if(strcmp(cval,"TO") == 0)
				 	toIndex=static_cast<int>(vals.size())-1;
				else if(strcmp(cval,"BY") == 0)
				 	byIndex=static_cast<int>(vals.size())-1;  
			}	
		}
	
	
		if(vals.size() == 0)
		{
			marslog(LOG_EROR,"No value found for parameter: %s",parMv.c_str());
			setError(13);
			return false;
		}
		if(toIndex==1 && byIndex==3 && static_cast<int>(vals.size()) == 5)
		{
			int fromValue;
			istringstream issF(vals[0]);
			issF >> fromValue;  	
		  	
			int toValue;
			istringstream issT(vals[2]);
			issT >> toValue;
					
			int byValue;
			istringstream issB(vals[4]);
			issB >> byValue;
			
		 	if(fromValue >=0 && fromValue < 500 &&
			   toValue >= fromValue && toValue < 500 &&
			   byValue > 0 && byValue<=6)
			  
			{
				int currentValue=fromValue;
				resVal=vals[0];
				currentValue+=byValue;
				while(currentValue <= toValue)
				{				  	
					stringstream sst;
					sst << currentValue;
					resVal.append("/" + sst.str());
					currentValue+=byValue;
				}
			}
		}	
		else if(toIndex != -1 || byIndex != -1)	
		{
			marslog(LOG_EROR,"Incorrect syntax used for parameter: %s",parMv.c_str());
			setError(13);
			return false; 
		}		
		else
		{  
			resVal=vals[0]; 
			for(unsigned int i=1; i< vals.size(); i++)
			{
			  	resVal.append("/" + vals[i]);
			}
		}
	}
	
	/*if(isDate)
	{
		string s;
		if(getDate(resVal,s,parMv))
		{
			  resVal=s;
		}
		else
			resVal.clear();
	}*/	
	
	
	
	if(!resVal.empty())
	{	  	
		return true;
	}
	else
	{
	  	if(canBeEmpty)
		{  
		  	resVal="_UNDEF_";	
		 	return true;
		}
		else
		{  
			marslog(LOG_EROR,"No value found for parameter: %s",parMv.c_str());
			setError(13);
			return false;
		}	
	}	
	
	return false;
}

bool VaporPrep::checkGeometry(const string& areaStr,const string& gridStr)
{
	vector<string> vArea;
	Tokenizer parseA("/");
	parseA(areaStr,vArea);
	
	vector<string> vGrid;
	Tokenizer parseG("/");
	parseG(gridStr,vGrid);
	
	if(vArea.size() != 4 || vGrid.size() != 2)
		return false;
	
	vector<float> area;
	for(unsigned int i=0; i < 4; i++)
	{  
		istringstream iss(vArea[i]);
 		float d;
		iss >> d;
		area.push_back(d);
	}
	
	vector<float> grid;
	for(unsigned int i=0; i < 2; i++)
	{  
		istringstream iss(vGrid[i]);
 		float d;
		iss >> d;
		if(d <= 0.)
		  	return false;
		  
		grid.push_back(d);
	}
	
	double trVal;
	
	float nx=(area[2]-area[0])/grid[1];
	trVal=trunc(nx);
        if(nx != trVal  ||  static_cast<float>(static_cast<int>(trVal)) != trVal)
		return false;
	
	float ny=(area[3]-area[1])/grid[0];
	trVal=trunc(ny);
        if(ny != trVal  ||  static_cast<float>(static_cast<int>(trVal)) != trVal)
		return false;		
	

	return true;
}  


bool VaporPrep::vdfExists(MvRequest& in, bool& err)
{
	string errTxt;
	err=false;

	//Add outpath
	string outPath;
    if(!in.getPath("VAPOR_OUTPUT_PATH",outPath,true))
	{	
		setError(13);
		err=true;
		return false;
	}	
	
	string vdfName;
	getParamValue(vdfName,in,"VAPOR_VDF_NAME");

	string vdfFile=outPath + "/" + vdfName + ".vdf";
	
  	if( ::access(vdfFile.c_str(),F_OK) != 0 )
    	{	
      		return false;                          
    	}
    	
    	return true;
}	


void VaporPrep::vapor_visualise( MvRequest& in, MvRequest& /*out*/)
{
	string vaporScript;
	char *mvbin=getenv("METVIEW_BIN");
	if (mvbin == 0)  
	{	
		marslog(LOG_EROR,"No METVIEW_BIN env variable is defined. Cannot locate mv_vapor_gui.mv macro!");
		setError(13);
		return;
	}
	else
	{
		vaporScript=string(mvbin) + "/mv_vapor_gui";
	}

	string errTxt;

	//Add outpath
	string outPath;
    if(!in.getPath("VAPOR_OUTPUT_PATH",outPath,true))
	{		
		setError(13);
		return;
	}	
	
	//Add vdf filename
	string vdfName;
	VAPOR_CHK(getParamValue(vdfName,in,"VAPOR_VDF_NAME"));
	
	string logFile(marstmp());
	string vdfFile=outPath+"/"+vdfName + ".vdf";
	string cmd=vaporScript + " \"" + vdfFile + "\" \"" + logFile + "\"";
	
	int ret=system(cmd.c_str());	
	
	//If the script failed read log file and
	//write it into LOG_EROR
	if(ret == -1 || WEXITSTATUS(ret) != 0)
	{	
		ifstream in(logFile.c_str());
		string line;
		
		if(WEXITSTATUS(ret) == 1)
		{		
			marslog(LOG_EROR,"Failed to startup VAPOR!");
			while(getline(in,line))
			{
				marslog(LOG_EROR,"%s",line.c_str());
			}
			in.close();
			setError(13);
			return;
		}
		else if(WEXITSTATUS(ret) > 1)
		{		
			marslog(LOG_EROR,"VAPOR startup failed with exit code: %d !",WEXITSTATUS(ret));
			while(getline(in,line))
			{
				marslog(LOG_EROR,"%s",line.c_str());
			}
			in.close();
			setError(13);
			return;
		}
		
	}
}

void VaporPrep::vapor_execute( MvRequest& in, MvRequest& out)
{
	string vaporMacro;
	char *mvbin=getenv("METVIEW_BIN");
	if (mvbin == 0)  
	{	
		marslog(LOG_EROR,"No METVIEW_BIN env variable is defined. Cannot locate mv_vapor_prep.mv macro!");
		setError(13);
		return;
	}
	else
	{
		vaporMacro=string(mvbin) +"/mv_vapor_prep.mv";
	}

	vector<string> param;
	string str,errTxt;
	
	//Add outpath
	string outPath;
    if(!in.getPath("VAPOR_OUTPUT_PATH",outPath,true))
	{		
		setError(13);
		return;
	}	
	param.push_back(outPath);
	
	//Add vdf filename
	string vdfName;
	VAPOR_CHK(getParamValue(vdfName,in,"VAPOR_VDF_NAME"));
	param.push_back(vdfName);
	
	//==============================
	//
	// Execute
	//
	//==============================
	
	//Add reuse(check) input status
	string reuseVdf;
	VAPOR_CHK(getParamValue(str,in,"VAPOR_REUSE_VDF"));
	param.push_back((str == "ON")?"1":"0");
	
	
	//Add refinement level
	string refLevel;
	VAPOR_CHK(getParamValue(refLevel,in,"VAPOR_REFINEMENT_LEVEL"));
	param.push_back(refLevel);
		
	//Add vertical grid type
	string vGrid;
	VAPOR_CHK(getParamValue(vGrid,in,"VAPOR_VERTICAL_GRID_TYPE"));
	param.push_back(vGrid);
	
	if(vGrid == "LAYERED")
	{
	  	string hVar;
		VAPOR_CHK(getParamValue(hVar,in,"VAPOR_ELEVATION_PARAM"));
		param.push_back(hVar);
	
		//Add min-max vertical coordinate
		string vMin;
		VAPOR_CHK(getParamValue(vMin,in,"VAPOR_BOTTOM_COORDINATE"));
		param.push_back(vMin);
	
		string vMax;
		VAPOR_CHK(getParamValue(vMax,in,"VAPOR_TOP_COORDINATE"));
		param.push_back(vMax);
	}

	//Add are selection
	string areaSelect;
	VAPOR_CHK(getParamValue(areaSelect,in,"VAPOR_AREA_SELECTION"));
	param.push_back((areaSelect == "INTERPOLATE")?"1":"0");
		
	if(areaSelect == "INTERPOLATE")
	{		
		//Add area
		string area;
		VAPOR_CHK(getParamValue(area,in,"VAPOR_AREA"));
		param.push_back(area);
	
		//Add grid resolution
		string grid;
		VAPOR_CHK(getParamValue(grid,in,"VAPOR_GRID"));
		param.push_back(grid);
		
		if(!checkGeometry(area,grid))
		{
	  		marslog(LOG_EROR,"Inconsistency between grid area and resolution!");
			setError(13);
			return;
		}  	
	}
	
	//Add steps numbers
	string stepParams;
	VAPOR_CHK(getParamValue(stepParams,in,"VAPOR_STEP_NUMBER"));
	param.push_back(stepParams);
	
	//Add surf params
	string surfParams;
	VAPOR_CHK(getParamValue(surfParams,in,"VAPOR_2D_PARAMS",true));
	param.push_back(surfParams);
	
	//Add upper params
	string upperParams;
	VAPOR_CHK(getParamValue(upperParams,in,"VAPOR_3D_PARAMS"));
	param.push_back(upperParams);
		
	//Create  tmp dir for vapor
	string tmpPath;
	if(!metview::createWorkDir("vapor",tmpPath,errTxt))
	{
		marslog(LOG_EROR,"%s",errTxt.c_str());
		setError(13);
		return;
	}
	param.push_back(tmpPath);
	
	//Add perparation mode 
	string inputMode;
	VAPOR_CHK(getParamValue(inputMode,in,"VAPOR_INPUT_MODE"));	
	param.push_back(inputMode);
	
	//Add input files
	if(inputMode == "ICON")
	{  
		//Add mars expver
		vector<string> gribFiles;
		VAPOR_CHK(getInputFiles(gribFiles,in,"VAPOR_INPUT_DATA"));
		for(int i=0; i < static_cast<int>(gribFiles.size());i++)
				param.push_back(gribFiles[i]);		
	}
	else
	{
	}		
	
	//Build request to be sent to Macro
	MvRequest req("MACRO");

	string processName = MakeProcessName("VaporPrepare");
	MvRequest macroReq("MACRO");
	req("PATH") = vaporMacro.c_str();
	req("_CLASS") = "MACRO";
	req("_ACTION") = "execute";
	req("_REPLY") = processName.c_str();
	
	//Define argument list for the macro!
	for(vector<string>::iterator it=param.begin(); it != param.end(); it++)
	{
		req.addValue("_ARGUMENTS",(*it).c_str());			   			     
	}
	
	req.print();
			
	//Run macro
	int error;	
	//marslog(LOG_INFO,"Execute macro: %s",vaporMacro.c_str());
	MvRequest reply = MvApplication::waitService("macro",req,error);

	string logFile=tmpPath + "/m_log.txt";
	string errFile=tmpPath + "/m_err.txt";
	if(!error && reply)
	{
		marslog(LOG_INFO,"Log file is available at:");
		marslog(LOG_INFO,"---------------------------");
		marslog(LOG_INFO,"  %s",logFile.c_str());	
				
		//const char* myname = reply("_REPLY");
		//MvApplication::callService(myname,reply,0);
	}
	else
	{  	  
		ifstream in(errFile.c_str());
		string line;
		marslog(LOG_EROR,"Failed to perform VAPOR Prepare!");
		marslog(LOG_EROR,"Error message:");
		while(getline(in,line))
		{
			marslog(LOG_EROR,"%s",line.c_str());
		}
		in.close();
		
		marslog(LOG_EROR,"Log file is available at:");
		marslog(LOG_EROR,"---------------------------");
		marslog(LOG_EROR,"  %s",logFile.c_str());
		
		setError(13); 
		return;
	}	
	//reply.print();
	
	out=MvRequest("VAPOR_INPUT");
	out("INPUT_DATA_PATH")=outPath.c_str();
	//out("AVAILABLE_FILE_PATH")=fAvailable.c_str();
	
	out.print();
}	




void VaporPrep::serve( MvRequest& in, MvRequest& out)
{
  	cout << "--------------VaporPrepare::serve()--------------" << endl;
  	in.print();
	
	// Get user action mode
	const char* modeC = (const char*)in("_ACTION");
    string mode="execute";
	if(modeC)
	  	mode=string(modeC);

	if(mode == "visualise")
	{
	  	bool err=false;
		bool hasVdf=vdfExists(in,err);
		
		if(err)
		{
		  	return;
		}
		else if(!hasVdf)
		{
		  	vapor_execute(in,out);	
		}  
		
	  	vapor_visualise(in,out);
	}
	else if(mode == "execute")
	{
	  	vapor_execute(in,out);
	}  
}
	
	  	
int main( int argc, char** argv )
{
	MvApplication theApp( argc, argv,"VaporPrepare");

    	VaporPrep vapor;
	
	vapor.saveToPool(false);
	
    	theApp.run();
}


