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

#include <iostream>
#include <stdexcept>

#include <fcntl.h>
#include <unistd.h>

#include "MvScanFileType.h"

#ifdef METVIEW_ODB_NEW	
#ifdef ODB_ECKIT
#include <eckit/exception/Exceptions.h>
#else
#include "Exceptions.h"
#endif
#endif

using namespace std;


void messageToLog(const string& msg, int severity,const char* useLogWindow,MvRequest& out)
{
	if(useLogWindow)
	{
		MvRequest reqst("USER_MESSAGE");
		reqst("INFO") = msg.c_str(); 
		send_message(MvApplication::instance().getService(),(request*)reqst);
	}
	
	out=MvRequest("REPLY");
	out("_FILTER_MESSAGE")=msg.c_str();
	
	// also send to the Mars/Metview module log

	char msg_c[1024];
	strncpy(msg_c, msg.c_str(), 1023);
	marslog(severity, msg_c);
}


void getStringLines(string ibuff, vector<string> &obuff)
{
	string::size_type pos=0, pos_prev=0;
	while((pos=ibuff.find("\n",pos_prev)) != string::npos)
	{
		obuff.push_back(ibuff.substr(pos_prev,pos-pos_prev));
		pos_prev=pos+1;
	}
	if(pos_prev < ibuff.size())
	{
		obuff.push_back(ibuff.substr(pos_prev));
	}	
}

void getShellArgument(string ibuff, string &obuff)
{
	string::size_type pos=0;
	obuff=ibuff;

	//Replace linebrake with whitespace
	pos=0;
	while((pos=obuff.find("\n",pos)) != string::npos)
	{
		obuff.replace(pos,1," ");
		pos++;
	}

        //Replace linebrake with whitespace
	pos=0;
	while((pos=obuff.find("$",pos)) != string::npos)
	{
		obuff.replace(pos,1,"\\$");
		pos+=2;
	}


	// "(" -> "/(" 
	/*pos=0;
	while((pos=obuff.find("(",pos)) != string::npos)
	{
		obuff.replace(pos,1,"\\(");
		pos+=3;
	}

	// ")" -> "/)" 
	pos=0;
	while((pos=obuff.find(")",pos)) != string::npos)
	{
		obuff.replace(pos,1,"\\)");
		pos+=3;
	}*/
}
  
class Base : public MvService { 
protected:
	Base(char* a) : MvService(a) {};
};

class OdbFilter: public Base {	
public:
	OdbFilter() : Base("ODB_FILTER") {};
	void serve(MvRequest&,MvRequest&);
};


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

	// Get prefix
	string verb = in.getVerb();

	cout << verb << endl;

	const char* mode = (const char*)in("_ACTION");	
	cout << "mode: " << mode << endl;
		
	const char* useLogWindow=(const char*)in("_USELOGWINDOW");	

  	const char* dataPath;
  
  	MvRequest dataR=in("ODB_DATA");
  	if(dataR != 0) 
  	{
		const char* dc=dataR("PATH");
		dataPath=dc;	
  	}
	else
	{
		dataPath=in("ODB_FILENAME");
	}

	if(dataR ==0 && 
	  (dataPath==0 || strcmp(dataPath,"OFF") ==0 || strlen(dataPath) == 0)) 
  	{
		messageToLog("No ODB file is specified!",LOG_EROR,useLogWindow,out); 
		setError(13);
		return;			
  	}		

	if(static_cast<const char*>(in("ODB_QUERY")) == 0)
  	{
		messageToLog("No ODB/SQL query is specified!",LOG_EROR,useLogWindow,out);
		setError(13);
		return;			
  	}
	string query(in("ODB_QUERY"));

	/*if(static_cast<const char*>(in("ODB_OUTPUT")) == 0)
  	{
		messageToLog("No output format is specified!",LOG_EROR,useLogWindow);
		setError(13);
		return;			
  	}
	string outputFormat(in("ODB_OUTPUT"));*/
	string outputFormat("ODB");


	if(static_cast<const char*>(in("ODB_NB_ROWS")) == 0)
  	{
		messageToLog("No number of rows is specified!",LOG_EROR,useLogWindow,out);
		setError(13);
		return;			
  	}
	string nb_rows(in("ODB_NB_ROWS"));

  	//Find out and check odb format  	
  	string odbType = MvOdbType(dataPath,true);	
	if(odbType != "ODB_NEW" && odbType != "ODB_OLD")
	{
		stringstream err_s;
		err_s << "Wrong data type: " << odbType << "! Only ODB_DB is accepted!";
		messageToLog(err_s.str(),LOG_EROR,useLogWindow,out); 
		setError(13);
		return;			
	}
	else if(odbType == "ODB_NEW")
	{
#ifndef METVIEW_ODB_NEW		
		messageToLog("Cannot handle ODB-2 data! ODB-2 support is disabled!",LOG_EROR,useLogWindow,out);
		setError(13);
		return;
#endif
	}
	else if(odbType == "ODB_OLD")
	{
#ifndef METVIEW_ODB_OLD		
		messageToLog("Cannot handle ODB-1 data! ODB-1 support is disabled!",LOG_EROR,useLogWindow,out);
		out.print();
		setError(13);
		return;
#endif
	}

	//Check if the old odb api is able to produce a new odb ouput
	if(odbType == "ODB_OLD" && outputFormat == "ODB")
	{
#ifndef METVIEW_ODB_OLD	
		messageToLog("Cannot handle ODB-1 data! ODB-1 support is disabled!",LOG_EROR,useLogWindow,out);
		setError(13);
		return;
#endif	
	}

	bool failOnEmpty=true;
	if(const char* c=in("FAIL_ON_EMPTY_OUTPUT"))
    {
        if(strcmp(c,"NO") == 0)
            failOnEmpty=false;
    } 
    else
    {
        messageToLog("Parameter FAIL_ON_EMPTY_OUTPUT is not defined",
                     LOG_EROR,useLogWindow,out);
		setError(13);
		return;
    }
    
	//Outfile name
	string outFile = marstmp();	

	//Odbsql script
	string odbsqlScript;
	if(odbType == "ODB_OLD")
	{
		char *mvbin=getenv("METVIEW_BIN");
		if (mvbin == 0)  
		{	
			messageToLog("No METVIEW_BIN env variable is defined. Cannot locate mv_odbsql script to run query for ODB-1 databases!",
				     LOG_EROR,useLogWindow,out);
			setError(13);
			return;
		}
		else
		{
			odbsqlScript=string(mvbin) +"/mv_odbsql";
		}
	}

	//-------------------------
	// Performs filter
	//-------------------------

	//string cmd="odbsql -q '" <<  query << "' -o " << outFile << " ";
	
	if(outputFormat == "ODB")
	{
		if(odbType == "ODB_NEW")
		{
			bool notEmpty=true;
            //MvOdb odb(dataPath);
#ifdef METVIEW_ODB_NEW
			try
			{
				notEmpty=MvOdb::retrieveToFile(query,dataPath,outFile);
			}
#ifdef ODB_ECKIT
			catch(eckit::Exception e)
#else
			catch(eclib::Exception e)
#endif
			{
				stringstream err_s;
				err_s << "Failed to perform retrieval: " << e.what();
				messageToLog(err_s.str(),LOG_EROR,useLogWindow,out);
				setError(13);
				return;
			}
			
			if(!notEmpty && failOnEmpty)
            {
                messageToLog("Query result is empty!",LOG_EROR,useLogWindow,out);
				setError(13);
				return;  
            }    
#endif
		}
		else
		{		  
#ifdef METVIEW_ODB_OLD
			string queryForShell, dataPathForShell;
			getShellArgument(query,queryForShell);
			getShellArgument(dataPath,dataPathForShell);

			string cmd = odbsqlScript + " -q \"" +  queryForShell  + "\" -o " + outFile +
				   " -i " + "\"" + dataPathForShell +  "\"" + " -f newodb";

			if(nb_rows != "-1")
			{
				cmd += " -m " + nb_rows;
			}

			//marslog(LOG_INFO,"Execute command: %s",cmd.c_str());

			int ret=system(cmd.c_str());

			if(ret == -1 || WEXITSTATUS(ret) != 0)
			{
				stringstream err_s;
				err_s << "Failed to perform retrieval: odbsql exited with value of " << WEXITSTATUS(ret);
				messageToLog(err_s.str(),LOG_EROR,useLogWindow,out); 
				setError(13);
				return;
			}

			outFile+=".odb";
						
			struct stat st;
            if(stat(outFile.c_str(),&st) != 0 || st.st_size ==0 )
			{
		   		return;
			}	
#endif			
		}
	
		MvRequest rdata("ODB_DB");
		rdata("PATH") = outFile.c_str();
		rdata("TEMPORARY") = 1;
		rdata.print();
		out = rdata;

	}
	
	else if(outputFormat == "GEO_STANDARD" ||
		outputFormat == "GEO_XYV" ||
		outputFormat == "GEO_XY_VECTOR" || 
		outputFormat == "GEO_POLAR_VECTOR")
 	{		
		if(odbType == "ODB_NEW")
		{
#ifdef METVIEW_ODB_NEW
			string tmpFile = marstmp();
			
			try
			{
				MvOdb::retrieveToFile(query,dataPath,tmpFile);
				MvOdb odb(tmpFile,dataPath,query);
				if(odb.toGeopoints(outFile,outputFormat) == false)
				{
					messageToLog("Failed to convert retrieval result into geopoints",
						     LOG_EROR,useLogWindow,out);
					setError(13);
					return;
				}
			}
#ifdef ODB_ECKIT
			catch(eckit::Exception e)
#else
			catch(eclib::Exception e)
#endif
			{
				stringstream err_s;
				err_s << "Failed to perform retrieval: " << e.what();
				messageToLog(err_s.str(),LOG_EROR,useLogWindow,out); 
				setError(13);
				return;
			}
#endif
		}
		else
		{
#ifdef METVIEW_ODB_OLD
			string queryForShell;
			getShellArgument(query,queryForShell);
			
			string tmpFile = marstmp();
			string cmd=odbsqlScript  + " -q \"" +  queryForShell + "\" -o " + tmpFile + 
				    " -i " + dataPath + " -f newodb ";

			if(nb_rows != "-1")
			{
				cmd += " -m " + nb_rows;
			}

			int ret=system(cmd.c_str());
			
			if(ret == -1 || WEXITSTATUS(ret) != 0)
			{
				stringstream err_s;
				err_s << "Failed to perform retrieval: odbsql exited with value of " << WEXITSTATUS(ret);
				messageToLog(err_s.str(),LOG_EROR,useLogWindow,out); 
				setError(13);
				return;
			}

			tmpFile+=".odb";

			try
			{
				MvOdb odb(tmpFile,dataPath,query);	
				if(odb.toGeopoints(outFile,outputFormat) == false)
				{
					messageToLog("Failed to convert query result into geopoints",LOG_EROR,useLogWindow,out);
					setError(13);
					return;
				}
			}
#ifdef ODB_ECKIT
			catch(eckit::Exception e)
#else
			catch(eclib::Exception e)
#endif
			{
				stringstream err_s;
				err_s << "Failed to convert retrieval result into geopoints: " << e.what();
				messageToLog(err_s.str(),LOG_EROR,useLogWindow,out); 
				setError(13);
				return;
			}	
#endif
	
		}





			
		MvRequest rdata("GEOPOINTS");
		rdata("PATH") = outFile.c_str();
		rdata("TEMPORARY") = 1;
		rdata.print();
		out = rdata;

	}

	cout << "---OdbFilter::serve() OUT---" << endl;
	out.print();	

}
  	
int main( int argc, char** argv )
{
	MvApplication theApp( argc, argv );

    	OdbFilter odb;
 
   	theApp.run();
}
