/***************************** 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 "Tokenizer.h"

#include "MvOdb.h"

#include <iostream>
#include <stdexcept>

#include <fcntl.h>

#include "MvScanFileType.h"

#include "MvOdb.h"

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

class OdbVisualiser: public Base {	
public:
	OdbVisualiser(char* a) : Base(a) {};
	void serve(MvRequest&,MvRequest&);

protected:
	bool getParamValue(vector<string>&,MvRequest&,string);
  	bool isTextContained(const char *txt);
	bool columnsHaveExpression(MvAbstractOdb *,const vector<const char*>&);
	void buildColumnList(const vector<const char*>&,const string&,vector<string>&);
	string buildQuery(const vector<string>&);
	string buildQuery(const vector<string>& columns, 
	                  const char* vars,const char* uniqueby, 
			  const char *from, const char *where,const char *orderby,bool);
	bool callFilter(string path,string query,string &resPath);
	bool updateRequest(MvRequest&,const char*,MvAbstractOdb*,int &);
};


bool OdbVisualiser::getParamValue(vector<string>& value,MvRequest& in,string parName)
{
	value.clear();
	
	int cnt = in.countValues(parName.c_str());
	
	if(cnt == 1)
	{
	  	const char *cval=in(parName.c_str());
		if(cval)
  		{		
			value.push_back(string(cval)); 
		}
	}
	else
	{  
		string val;
		for( int i=0; i<cnt; i++)
	     	{
	      		const char *cval = in(parName.c_str(),i );
			if(cval)
			{					
				value.push_back(string(cval));
			}						
		}
	}
	
	return true;
}

bool OdbVisualiser::isTextContained(const char *txt)
{
	if(!txt) return false;
	string str(txt);
	
	return (str.find_last_not_of(" \n\t") != string::npos);  
}

bool OdbVisualiser::columnsHaveExpression(MvAbstractOdb *db,const vector<const char*>& cols)
{
	if(!db)
		return true;  
	  
	for(unsigned int i=0; i < cols.size() ; i++)	  
	{
		if(cols[i])
		{
			string s(cols[i]);
	  		if(!db->hasColumn(s))
				return true;
		}	
	}
	
	return false;
} 
 
 
 
void OdbVisualiser::buildColumnList(const vector<const char*>& cols,const string& metadata,vector<string>& lst)
{	
  	for(unsigned int i=0; i < cols.size() ; i++)	  
	{
		if(cols[i])
		{
			string s(cols[i]);	  		
			lst.push_back(s);
		}		
	}
	
	if(isTextContained(metadata.c_str()))
	  	lst.push_back(metadata);
	
  	/*Tokenizer parse(",");
	vector<string> vs;
		
	parse(metadata,vs);
	
	for(unsigned int i=0; i < vs.size() ; i++)	  
	{
		if(isTextContained(vs.at(i).c_str()))
			lst.push_back(vs.at(i));
	}*/	
}	
	  
string OdbVisualiser::buildQuery(const vector<string> &columns)
{
	string query("SELECT");

	bool empty=true;
	for(unsigned int i=0; i < columns.size(); i++)
	{
		if(!columns[i].empty())
		{
			if(!empty) 
				query.append(",\n");
			else
				query.append("\n");	
			
			query.append("\t" + columns[i]);

			if(empty) 
				empty=false;
		}
	}	

	query.append("\n");

	return query;
}



string OdbVisualiser::buildQuery(const vector<string> &columns, 
	                    const char* vars,const char* uniqueby, const char* from, const char *where, const char *orderby,
			    bool probablyNeedAQuery)
{
	string query;

	//If the columns do not contain any expressions, we check if there is
	//any need for having a query
	if(!probablyNeedAQuery)
	{
		if(!from && !isTextContained(from) &&
		   !uniqueby && !isTextContained(uniqueby) &&
		   !where && !isTextContained(where) &&
		   !orderby && !isTextContained(orderby))
		{
			return query;
		}	
	}
	
	if(vars)
		query.append(vars);

	query.append("SELECT");
	
	bool empty=true;
	
	for(unsigned int i=0; i < columns.size(); i++)
	{
		if(!columns[i].empty())
		{
			if(!empty) 
				query.append(",\n");
			else
				query.append("\n");	
			
			query.append("\t" + columns[i]);

			if(empty) 
				empty=false;
		}
	}	

	query.append("\n");

	if(from && isTextContained(from)) 
		query.append("FROM \n" + string(from) + "\n");

	if(uniqueby && isTextContained(uniqueby)) 
		query.append("UNIQUEBY \n" + string(uniqueby) + "\n");

	if(where && isTextContained(where)) 
		query.append("WHERE \n" + string(where) + "\n");

	if(orderby && isTextContained(orderby)) 
		query.append("ORDER BY \n" + string(orderby) + "\n");


	return query;
}

bool OdbVisualiser::callFilter(string path,string query,string &resPath)
{		
	MvRequest r("ODB_FILTER");
	
	r("ODB_FILENAME")     = path.c_str();
	r("ODB_QUERY")    = query.c_str();
	//r("ODB_OUTPUT")   = "ODB";	
	r("ODB_NB_ROWS")   = "-1";
	r("FAIL_ON_EMPTY_OUTPUT")="on";
	r("_CLASS")   = "ODB_FILTER";
	r("_ACTION")  = "execute";
	r("_SERVICE") = "OdbFilter";
	r("_USELOGWINDOW") = "1";

//r.print();

	int error;
	MvRequest result = MvApplication::waitService("OdbFilter",r,error);
	result.print();

	const char *rPath=result("PATH");
	if(error !=0 || !rPath || string(rPath).empty())
	{
		const char *errmsg=result("_FILTER_MESSAGE");
		  
	  	marslog(LOG_EROR,"Calling ODB Filter failed!");
		if(errmsg)
		  	marslog(LOG_EROR,"%s",errmsg);
		
		setError(13);
		return false;
	}
	
	resPath=string(rPath);

	struct stat st;
     	if(stat(resPath.c_str(),&st) != 0 || st.st_size ==0 )
	{
		marslog(LOG_INFO,"No data retrieved for the specified ODB/SQL query!"); 
	}	
			
			
			
	return true;
}

bool OdbVisualiser::updateRequest(MvRequest& req,const char* par,MvAbstractOdb* db,int &cnt)
{
	const char *c=req(par);
  	if(c)
	{
	  	const MvOdbColumn *col=db->column(cnt);
	  	if(col)
		{	
		  	req(par)=col->name().c_str();
			cnt++;
			return true;
		}
	}
	return false;
}	

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

#ifndef METVIEW_ODB_PLOT
	marslog(LOG_EROR,"ODB plotting is DISABLED! Magics was built without ODB support!");
	setError(13);
	return;	
#endif	
	
	
	// Get prefix
	string verb = in.getVerb();
	
	const char *type=in("ODB_PLOT_TYPE");
	
	if(!type)
	{
	  	marslog(LOG_EROR,"ODB_PLOT_TYPE is not specified!");
		setError(13);
		return;
	}
	
	vector<const char*> cols;
	string outReqName;
	
	if(strcmp(type,"GEO_POINTS") == 0)
	{
		outReqName="ODB_GEO_POINTS";
		
		const char *lat=in("ODB_LATITUDE_VARIABLE");
		cols.push_back(lat);

		const char *lon=in("ODB_LONGITUDE_VARIABLE");
		cols.push_back(lon);
	
		const char *value=in("ODB_VALUE_VARIABLE");
		cols.push_back(value);
	}

	else if(strcmp(type,"GEO_VECTORS") ==0)
	{
		outReqName="ODB_GEO_VECTORS";
		
		const char *lat=in("ODB_LATITUDE_VARIABLE");
		cols.push_back(lat);

		const char *lon=in("ODB_LONGITUDE_VARIABLE");
		cols.push_back(lon);
	
		const char *x=in("ODB_X_COMPONENT_VARIABLE");
		cols.push_back(x);

		const char *y=in("ODB_Y_COMPONENT_VARIABLE");
		cols.push_back(y);
		
		const char *value=in("ODB_VALUE_VARIABLE");
		cols.push_back(value);
	}

	else if(strcmp(type,"XY_POINTS") ==0)
	{
		outReqName="ODB_XY_POINTS";
		
		const char *x=in("ODB_X_VARIABLE");
		cols.push_back(x);

		const char *y=in("ODB_Y_VARIABLE");
		cols.push_back(y);
	
		const char *v=in("ODB_VALUE_VARIABLE");
		cols.push_back(v);
	}

	else if(strcmp(type,"XY_VECTORS") == 0)
	{
		outReqName="ODB_XY_VECTORS";
		
		const char *x=in("ODB_X_VARIABLE");
		cols.push_back(x);

		const char *y=in("ODB_Y_VARIABLE");
		cols.push_back(y);
	
		const char *xc=in("ODB_X_COMPONENT_VARIABLE");
		cols.push_back(xc);

		const char *yc=in("ODB_Y_COMPONENT_VARIABLE");
		cols.push_back(yc);

		const char *v=in("ODB_VALUE_VARIABLE");
		cols.push_back(v);
	}

	else if(strcmp(type,"XY_BINNING") == 0)
	{
		outReqName="ODB_XY_BINNING";
		
		const char *x=in("ODB_X_VARIABLE");
		cols.push_back(x);

		const char *y=in("ODB_Y_VARIABLE");
		cols.push_back(y);
	
		const char *v=in("ODB_VALUE_VARIABLE");
		cols.push_back(v);
	}
	else
	{
	  	return;
	}

	const char *vars=in("ODB_PARAMETERS");
	const char *uniqueby=in("ODB_UNIQUEBY");
	const char *from=in("ODB_FROM");
	const char *where=in("ODB_WHERE");
	const char *orderby=in("ODB_ORDERBY");

	//Get metadata columns
	string metadata;
	const char* metadataC=in("ODB_METADATA_VARIABLES");
	if(metadataC)
	  	metadata=string(metadataC);
	
	//Build the final column list
	vector<string> columns;
	buildColumnList(cols,metadata,columns);
	  
	//-------------------------
	// Check input ODB format
	//------------------------

	string odbPath;
	string odbType;

	MvRequest dataR=in("ODB_DATA");
	const char* iconType=dataR.getVerb();

	//If no icon is specified
	if(!iconType)
	{
		//If no path is there either
		const char *path=in("ODB_FILENAME");
		if(!path || strcmp(path,"OFF") == 0)
		{
			marslog(LOG_EROR,"No data icon or path is specified!");
			setError(13);
			return;
		}
		//If the path is defined it checks the filetype. It has to be "ODB_NEW"
		else
		{
			odbType= MvOdbType(path,true);
			if(odbType != "ODB_OLD" && odbType != "ODB_NEW")
			{
				marslog(LOG_EROR,"Wrong data type: %s! Only ODB_DB is accepted!",odbType.c_str());
				setError(13);
				return;
			}
		}

		odbPath=string(path);

	}
	//The icon type can only be "ODB_DB"
	else if(strcmp(iconType,"ODB_DB") != 0)
	{			
		marslog(LOG_EROR,"Wrong data type: %s! Only ODB_DB is accepted",iconType);
		setError(13);
		return;
	}
	//The "ODB_DB" icon can only be a new ODB!!
	else
	{
		const char *path=dataR("PATH");
		if(!path)
		{
			marslog(LOG_EROR,"No path is specified for ODB_DB icon!");
			setError(13);
			return;
		}
		odbType=MvOdbType(path,false);
		odbPath=string(path);
	}
	
	//----------------------------------------
	// Call ODBFilter service if needed and
	// modify the request!
	//----------------------------------------

	MvRequest tmpR = in;
	tmpR.setVerb(outReqName.c_str());
	tmpR("_VERB") = outReqName.c_str();
   tmpR("_CLASS") = outReqName.c_str();

	bool queryWasRun=false;
	
	//For ODB-1 we always need the filter!!
	string query;
	if(odbType == "ODB_OLD")
	{		
		//Build the query
		query=buildQuery(columns,vars,uniqueby,from,where,orderby,true);
		
		MvRequest r;
 		tmpR("ODB_DATA")=r;

		string res;
		if(!callFilter(odbPath,query,res))
		{
			out=r;
			return;
		}
		tmpR("ODB_FILENAME")=res.c_str();		

		queryWasRun=true;
	}
	
	//For ODB-2 
	if(odbType == "ODB_NEW")
	{
		//If we have metadata columns we surely need to run a query
		bool needAQuery=isTextContained(metadata.c_str());
		
		//Get odb object to access metadata
		MvAbstractOdb* db=MvOdbFactory::make(odbPath);
		
		//Check if any of the specified columns (in the request) contains an expression. If does we 
		//we need to run a query.
		if(!needAQuery && columnsHaveExpression(db,cols))
		{
		  	needAQuery=true;
		}
		
		if(db) delete db;
	
		//Get the query		
		query=buildQuery(columns,vars,uniqueby,0,where,orderby,needAQuery);
		
	  	//The query is empty if the columns do not contain any expressions 
		//and there are no SET, WHERE, ORDERBY, UNIQUEBY statments specified
	  	if(!query.empty())
		{
			MvRequest r;
 			tmpR("ODB_DATA")=r;

			string res;
			if(!callFilter(odbPath,query,res))
			{
				out=r;
				return;
			}
			tmpR("ODB_FILENAME")=res.c_str();
			queryWasRun=true;
		}
		else
		{
			tmpR("ODB_FILENAME")=odbPath.c_str();  
		} 
	}
	  

	//Column names can change during the query. E.g. ODB_VALUE_VARIABLE=an_depar-fg_depar 
	//changes to -(an_depar,fg_depar) in the resulting ODB file. This causes magics++ crash
	//because it uses the column name  specified in ODB_VALUE_VARIABLE etc to extract the columns
	//data from the ODB.
	//As a workaround wee need to replace all the request column names to the actual ones in
	//the resulting ODB! We suppose that the column order in the resulting ODB is exactly
	//the same as in the qurey!!

	if(queryWasRun==true)  
	{  	  
		const char *pR=tmpR("ODB_FILENAME");
		string outPath;
		if(pR)
		  	outPath=string(pR);
	
	  	//Get odb object to access metadata
		MvAbstractOdb* db=MvOdbFactory::make(outPath);
		if(!db)
		{
		  	setError(13);
			return;
		}
		
		if(outReqName == "ODB_GEO_POINTS")
		{
			int cnt=0;
		  	updateRequest(tmpR,"ODB_LATITUDE_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_LONGITUDE_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_VALUE_VARIABLE",db,cnt);
		}	
		else if(outReqName == "ODB_GEO_VECTORS")
		{
			int cnt=0;
		  	updateRequest(tmpR,"ODB_LATITUDE_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_LONGITUDE_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_X_COMPONENT_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_Y_COMPONENT_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_VALUE_VARIABLE",db,cnt);
		}		
		else if(outReqName == "ODB_XY_POINTS")
		{
			int cnt=0;
		  	updateRequest(tmpR,"ODB_X_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_Y_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_VALUE_VARIABLE",db,cnt);
						
		}			
		else if(outReqName == "ODB_XY_VECTORS")
		{
			int cnt=0;
		  	updateRequest(tmpR,"ODB_X_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_Y_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_X_COMPONENT_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_Y_COMPONENT_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_VALUE_VARIABLE",db,cnt);						
		}
		else if(outReqName == "ODB_XY_BINNING")
		{
			int cnt=0;
		  	updateRequest(tmpR,"ODB_X_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_Y_VARIABLE",db,cnt);
			updateRequest(tmpR,"ODB_VALUE_VARIABLE",db,cnt);
						
		}
		
		if(db) delete db;		
	}

	out = tmpR;	

cout << "OdbVisualiser::serve out" << endl;
out.print();

	return;
}

//--------------------------------------------------------

int main(int argc,char **argv)
{
	MvApplication theApp(argc,argv);
	OdbVisualiser vis("ODB_VISUALISER");

	theApp.run();
}
