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

 Copyright 2012 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>

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

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

class FlextraPrep: public Base {	
public:
	FlextraPrep() : Base("FLEXTRA_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);
	bool checkGeometry(const string& areaStr,const string& gridStr);
};


bool FlextraPrep::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(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 FlextraPrep::getParamValue(string& resVal,MvRequest& in,const string& parMv,bool isDate=false)
{
	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
	{
	  	marslog(LOG_EROR,"No value found for parameter: %s",parMv.c_str());
		setError(13);
		return false;
	}	
	
}

bool FlextraPrep::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;
}  


void FlextraPrep::serve( MvRequest& in, MvRequest& out)
{
  	cout << "--------------FlextraPrepare::serve()--------------" << endl;
  	in.print();

	//Etadot is not stored in MARS for dates earlier than this
	MvDate etaDotDate("20080604");
		
	string flextraMacro;
	char *mvbin=getenv("METVIEW_BIN");
	if (mvbin == 0)  
	{	
		marslog(LOG_EROR,"No METVIEW_BIN env variable is defined. Cannot locate mv_flextra_prep.mv macro!");
		setError(13);
		return;
	}
	else
	{
		flextraMacro=string(mvbin) +"/mv_flextra_prep.mv";
	}

	vector<string> param;
	string str;
		
	//Add perparation mode 
	string prepMode;
	FLEXTRA_CHK(getParamValue(prepMode,in,"FLEXTRA_PREPARE_MODE"));	
	if(prepMode == "FORECAST")
	{	  	
		param.push_back("fc");
	}
	else
	{
		param.push_back("period");
	}
	
	//Add reuse(check) input status
	FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_REUSE_INPUT"));
	if(str == "ON")
	{	  	
		param.push_back("1");
	}
	else
	{
	  	param.push_back("0");
	}
		
	//Add outpath
	string outPath;
	FLEXTRA_CHK(getParamValue(outPath,in,"FLEXTRA_OUTPUT_PATH"));
	param.push_back(outPath);
	
	//Add Available file
	string fAvailable=outPath + "/AVAILABLE";
	param.push_back(fAvailable);

	
	//Add area
	string area;
	FLEXTRA_CHK(getParamValue(area,in,"FLEXTRA_AREA"));
	param.push_back(area);
	
	//Add grid resolution
	string grid;
	FLEXTRA_CHK(getParamValue(grid,in,"FLEXTRA_GRID"));
	param.push_back(grid);
	
	//Add top level
	string topL;
	FLEXTRA_CHK(getParamValue(topL,in,"FLEXTRA_TOP_LEVEL"));
	param.push_back(topL);
	
	string topUnits;
	FLEXTRA_CHK(getParamValue(topUnits,in,"FLEXTRA_TOP_LEVEL_UNITS"));
	param.push_back(topUnits);	
		
	if(!checkGeometry(area,grid))
	{
	  	marslog(LOG_EROR,"Inconsistency between grid area and resolution!");
		setError(13);
		return;
	}  	
			
	if(prepMode == "FORECAST")
	{  
		//Add mars expver
		string marsExpver;
		FLEXTRA_CHK(getParamValue(marsExpver,in,"FLEXTRA_FC_MARS_EXPVER"));
		param.push_back(marsExpver);	
	
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_DATE",true));
		param.push_back(str);
		MvDate md(str.c_str());
		if(md < etaDotDate)
		{
			marslog(LOG_EROR,"Cannot prepare data for the date specified in FLEXTRA_DATE: %s",str.c_str());			
			marslog(LOG_EROR,"Etadot is not avaliable in MARS for this date! It has only been archived since 4 June 2008.");	
			setError(13);
			return;
		}
		
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_TIME"));
		param.push_back(str);
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_STEP"));
		param.push_back(str);
		
		
		
	}
	else
	{
		//Add mars expver
		string marsExpver;
		FLEXTRA_CHK(getParamValue(marsExpver,in,"FLEXTRA_FC_MARS_EXPVER"));
		param.push_back(marsExpver);	
	  
		//Add mars expver
		string marsAnExpver;
		FLEXTRA_CHK(getParamValue(marsAnExpver,in,"FLEXTRA_AN_MARS_EXPVER"));
		param.push_back(marsAnExpver);	
			
	  	FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_PERIOD_START_DATE",true));
		param.push_back(str);
		MvDate mdStart(str.c_str());
		if(mdStart < etaDotDate)
		{
			marslog(LOG_EROR,"Cannot prepare data for the date specified in FLEXTRA_PERIOD_START_DATE: %s",str.c_str());			
			marslog(LOG_EROR,"Etadot is not avaliable in MARS for this date! It has only been archived since 4 June 2008.");	
			setError(13);
			return;
		}
		
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_PERIOD_START_TIME"));
		param.push_back(str);
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_PERIOD_END_DATE",true));
		param.push_back(str);
		MvDate mdEnd(str.c_str());
		if(mdEnd < etaDotDate)
		{
			marslog(LOG_EROR,"Cannot prepare data for the date specified in FLEXTRA_PERIOD_END_DATE: %s",str.c_str());			
			marslog(LOG_EROR,"Etadot is not avaliable in MARS for this date! It has only been archived since 4 June 2008.");	
			setError(13);
			return;
		}
						
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_PERIOD_END_TIME"));
		param.push_back(str);
		FLEXTRA_CHK(getParamValue(str,in,"FLEXTRA_PERIOD_STEP"));
		param.push_back(str);
		
		if(mdEnd < mdStart)
		{
			marslog(LOG_EROR,"Inconsistency in period definition! FLEXTRA_PERIOD_END_DATE precedes FLEXTRA_PERIOD_START_DATE!"); 	
			setError(13);
			return;
		}	
	}		
	
	//Build request to be sent to Macro
	MvRequest req("MACRO");

	string processName = MakeProcessName("FlextraPrepare");
	MvRequest macroReq("MACRO");
	req("PATH") = flextraMacro.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());			   			     
	}
		
	//Run macro
	int error;	
	marslog(LOG_INFO,"Execute macro: %s",flextraMacro.c_str());
	MvRequest reply = MvApplication::waitService("macro",req,error);

	//Call myself to process the macro reply request
	if(!error && reply)
	{
		const char* myname = reply("_REPLY");
		MvApplication::callService(myname,reply,0);
	}
	else
	{
	  	marslog(LOG_EROR,"Failed to run FLEXTRA input preparation! Macro failed!");
		setError(13);
		return;
	}  
	  
	reply.print();
	
	out=MvRequest("FLEXTRA_INPUT");
	out("INPUT_DATA_PATH")=outPath.c_str();
	out("AVAILABLE_FILE_PATH")=fAvailable.c_str();
	
	out.print();
}
	
	  	
int main( int argc, char** argv )
{
	MvApplication theApp( argc, argv,"FlextraPrepare");

    	FlextraPrep flextra;	
	
    	theApp.run();
}


