/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

///
/// \file odb.cc
///
/// @author Piotr Kuchta, ECMWF May 2009
///

#ifdef ODB_SUPPORT

#include "mars.h"


# include "eckit/eckit_version.h"
# include "eckit/io/StdFileHandle.h"
# include "eckit/filesystem/PathName.h"
# include "eckit/io/Length.h"
# include "eckit/exception/Exceptions.h"

#include "odb.h"
#include <set>

#include "odb_api/odbcapi.h"
#include "odb_api/FastODA2Request.h"
#include "odb_api/Select.h"
#include <string.h>

#if !defined(OLD_ODB_API)

	/* valid for versions of ODB_API > 0.9.20 or so */
	#define ECLIB_FULL_VERSION  ((ECLIB_MAJOR_VERSION)*10000) + ((ECLIB_MINOR_VERSION)*100)  + (ECLIB_PATCH_VERSION)

	/* From version 7.3.0, eclib is protected by a namespace */
	#if ECLIB_FULL_VERSION >= 70300
		using namespace eclib;
	#endif

	/* The name of the eclib function to check whether a path exists changed. We try to keep compatibility with older versions. */
	#define ECLIB_PATH_EXIST_FN exists

#else
    #define ECLIB_PATH_EXIST_FN exist
#endif



boolean is_part_of_mars_language(const char *n)
{
	static request *archive = NULL;
	const char *s = NULL;

	if(!archive)
	{
		request *r = mars_language();
		while(r && !EQ(r->name, "ARCHIVE"))
			r = r->next;
		if(r == NULL)
		{
			marslog(LOG_EROR, const_cast<char *>("ARCHIVE request not found in language. OOOPPPSSS!!!"));
			marsexit(1);
		}

		archive = r;
	}

	if((s = get_value(archive,n,0)) != NULL)
		return true;

	return false;
}

// TODO: In //depot/mars/client/dev/oda/etc then to install in: /usr/local/lib/metaps/etc/ 
// TODO: Tell Cristian if this is deployed in a config file eventually....
const char* odb2request_config = "/usr/local/lib/metaps/etc/";
/*
const char * cfg = 
"CLASS: class\n"
"DATE: andate\n"
"TIME: antime\n"
"TYPE: type\n"
"OBSGROUP: groupid\n"
"REPORTYPE: reportype\n"
"STREAM: stream\n"
"EXPVER: expver\n"
;
*/
const char * cfg = "let,CLASS=class,DATE=andate,TIME=antime,TYPE=type,OBSGROUP=groupid,REPORTYPE=reportype,STREAM=stream,EXPVER=expver";

err odb_to_request_from_file(request *r, const char *fileName, unsigned long long *cnt)
{
	err e = NOERR;
	try { 
		odb_start();
		marslog(LOG_DBUG, const_cast<char *>("odb_to_request_from_file: fileName = %s."), fileName); 

        eckit::PathName pn(fileName);
		if (! pn.ECLIB_PATH_EXIST_FN())
		{
			marslog(LOG_EROR, (char *)"oda_to_request_from_file: file '%s' does not exist.", (char *) fileName);
			return e = TOO_SHORT_ERR;
		}
		if (! pn.size())
		{
			marslog(LOG_EROR, (char *)"oda_to_request_from_file: file '%s' empty.", fileName);
			return e = TOO_SHORT_ERR;
		}

		odb::FastODA2Request<odb::ODA2RequestClientTraits> o2r;
		o2r.parseConfig(cfg);

        eckit::OffsetList offsets;
        eckit::LengthList lengths;
        std::vector<odb::ODAHandle*> handles;
		bool rc = o2r.scanFile(fileName, offsets, lengths, handles);
		for (size_t i = 0; i < handles.size(); ++i)
			delete handles[i];
		handles.clear();
		if (! rc) return HYPERCUBE_ERROR;

		ASSERT(lengths.size());
		ASSERT(lengths.size() == offsets.size());
		for(size_t i = 1; i < offsets.size(); i++)
			ASSERT(offsets[i] > offsets[i-1]);
		size_t last = offsets.size()-1;
        ASSERT(eckit::PathName(fileName).size() == offsets[last] + lengths[last]);

		*cnt = o2r.rowsNumber();

        std::string filesRequest = "ARCHIVE,\n";
		filesRequest += o2r.genRequest();

        eckit::Log::debug() << "odb_to_request_from_file: " << filesRequest << endl;
		request *n = string2request(filesRequest.c_str());
		if (! n)
		{
			marslog(LOG_EROR, (char *)"Error creating a MARS request from data");
			return e = TOO_SHORT_ERR;
		}
		//Log::debug() << "odb_to_request_from_file: converted to request:" << endl;
		//print_all_requests(n);
		reqmerge(r, n);
		free_all_requests(n);
		return e;
	}
    catch (eckit::Exception& ex)
	{
		marslog(LOG_EROR, (char *)"Error making a request from file %s\n",ex.what());
		marslog(LOG_EROR, (char *)"Exception ignored");
		e = TOO_SHORT_ERR;
		return e;
	}
	return e;
}

typedef std::map<std::string, std::set<std::string> > Parameters ;

Parameters parameters(request* r)
{
	Parameters ret;

	for (parameter *p = r->params; p; p = p->next)
	{
		string parameterName = p->name;
		size_t n = count_values(r, p->name);
		if (n == 0)
		{
			marslog(LOG_EROR, (char *)"parameters: no values of param '%s'", p->name);
			ASSERT(n != 0);
		}
		set<string> values;
		for (size_t i = 0; i < n; ++i)
			values.insert(get_value(r, p->name, i));
		ret[parameterName] = values;
	}
	return ret;
}

err odb_compare_attributes_of_first_request(request* first, request* second)
{
	typedef Parameters P;

	P firstParams = parameters(first);
	P secondParams = parameters(second);

	for (P::iterator it = firstParams.begin(); it != firstParams.end(); ++it)
	{
		const string& paramName = it->first;
		const set<string>& values = it->second;

		P::iterator jt = secondParams.find(paramName);
		if (jt == secondParams.end())
		{
			marslog(LOG_EROR, (char *)"odb_compare_attributes_of_first_request: second request has no param '%s'", paramName.c_str());
			return -1;
		}
		const set<string>& otherValues = jt->second;
		if (values != otherValues)
		{
			stringstream ss;
			if (values.size() == 1 && otherValues.size() == 1)
			{
				ss << "Values of '" << paramName << "' differ: " << *values.begin() << " <> " << *otherValues.begin();
				marslog(LOG_EROR, (char *)"odb_compare_attributes_of_first_request: %s", ss.str().c_str());
			}
			else
			{
				marslog(LOG_EROR, (char *)"odb_compare_attributes_of_first_request: values for param '%s' differ", paramName.c_str());
				ostream_iterator<string> out(ss, ", ");
				set_symmetric_difference(values.begin(), values.end(), otherValues.begin(), otherValues.end(), out);
				marslog(LOG_EROR,(char *)
					"odb_compare_attributes_of_first_request: values present in one of the sets, but not in the other: %s",
					ss.str().c_str());
			}
			return -1;
		}
	}
	return NOERR;
}

long long odb_filter(const char *sql, FILE *fin, FILE *fout, long long total_to_read)
{
	try { 
		odb_start();
		marslog(LOG_INFO, const_cast<char *>("odb_filter: sql = '%s', total_to_read = %lld"),
			((sql == 0) ? "NULL" : sql),
			total_to_read);

		if (total_to_read == 0)
			return 0;

		// TODO: check sql is a select really and does not have INTO clause (?)
        eckit::StdFileHandle fhin(fin), fhout(fout);
		fhin.openForRead();

		if ( ! sql || strlen(sql) == 0 )
		{
            eckit::Length n = fhin.saveInto(fhout);
			ASSERT(total_to_read == n);
		}
		else
		{
			using namespace odb;
            std::string s(sql);
			StringTool::trimInPlace(s);
			s = StringTool::isInQuotes(s) ? StringTool::unQuote(s) : s;
			StringTool::trimInPlace(s);
            if (s[s.size() - 1] != ';') s.append(";");

			odb::Select odb(s, fhin); 
			odb::Select::iterator it = odb.begin();
			odb::Select::iterator end = odb.end();

			odb::Writer<> writer(fhout);
			odb::Writer<>::iterator outit = writer.begin(); 
			outit->pass1(it, end);
		}
		marslog(LOG_INFO, const_cast<char *>(" => odb_filter"));
	}
    catch (eckit::Exception &ex)
	{
		marslog(LOG_EROR, (char*)"Error in odb_filter %s\n",ex.what());
		return -1;
	}

	// TODO: make sure the below is true
	return total_to_read;
}

static int test()
{
	odb_start();
	const char *sql = "select lat, lon";
	FILE *fin = fopen("input.oda", "r");
	FILE *fout = fopen("output.oda", "w");

	long long total_to_read = 0; // ? Do we really need this? maybe
	long long total_read = odb_filter(sql, fin, fout, total_to_read);

	//assert(total_to_read == total_read);

	fclose(fin);
	fclose(fout);
	
	return total_to_read;
}

//int main(int argc, char *argv[]) { return test(); }

#else
#include "mars.h"
#endif
