/*
 * © 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.
 */

#include "mars.h"

#if !defined(NOFDB5)

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

#include "eckit/eckit.h"
#include "eckit/io/Buffer.h"
#include "eckit/io/DataHandle.h"
#include "eckit/log/Log.h"
#include "eckit/memory/ScopedPtr.h"
#include "eckit/runtime/Main.h"

#include "marslib/EmosFile.h"
#include "marslib/MarsTask.h"

#include "fdb5/LibFdb.h"
#include "fdb5/database/Retriever.h"

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

static void     fdb5_init(void);
static err      fdb5_open(void *data,request*,request*,int);
static err      fdb5_close(void *data);
static err      fdb5_read(void *data,request *r,void *buffer,long *length);
static err      fdb5_write(void *data,request *r,void *buffer,long *length);
static boolean  fdb5_check(void *data,request *r);


static void mars_output_callback(void *context, const char* msg) {
    marslog(int(reinterpret_cast<intptr_t>(context)), msg);
}

extern "C" void mars_fdb5_init(int *argc, char **argv) {
	eckit::Main::initialise(*argc, argv);

    // Redirect the output from FDB5 to the relevant channels in marslog.
    // n.b. Don't just use eckit::Log::setCallback, as that sends all the data to the same place.

    if (mars.debug) {
        eckit::Log::setCallback(&mars_output_callback, reinterpret_cast<void*>(LOG_DBUG));
    }

    eckit::Log::info().setCallback(&mars_output_callback, reinterpret_cast<void*>(LOG_INFO));
    eckit::Log::error().setCallback(&mars_output_callback, reinterpret_cast<void*>(LOG_EROR));
    eckit::Log::warning().setCallback(&mars_output_callback, reinterpret_cast<void*>(LOG_WARN));
}


static void fdb5_init(void)
{
}

#if defined(ECKIT_HAVE_FMEMOPEN) || defined (EC_HAVE_FMEMOPEN)
#define _GNU_SOURCE
#endif



typedef struct fdbdata {
	FILE* f;
	wind* u_v;
	long64 total_read;
	timer* fdb_timer;
	double start_time;
	size_t buffer_size;
	char *buffer;
	char *fdb_home;
} fdbdata;

static option opts[] = {
    {(char*)"buffer-size", (char*)"FDB5_DIRECT_BUFFER_SIZE", NULL, (char*)"67108864", t_long, sizeof(long),
    int(OFFSET(fdbdata, buffer_size))},

	{"home", "MARS_FDB5_HOME",NULL,NULL,t_str,sizeof(char*),
    int(OFFSET(fdbdata, fdb_home))},
};

base_class _fdb5base = {

	NULL,                      /* parent class */
	(char*)"fdb5base",                /* name         */

	false,                     /* inited       */

	sizeof(fdbdata),           /* private size */
	NUMBER(opts),              /* option count */
	opts,                      /* options      */

	fdb5_init,                  /* init         */

	fdb5_open,                  /* open         */
	fdb5_close,                 /* close        */

	fdb5_read,                  /* read         */
	fdb5_write,                 /* write        */

	NULL,                      /* control      */

	fdb5_check,                 /* check        */

};

base_class *fdb5base = &_fdb5base;


static const char* last_home = NULL;

static err  fdb5_open(void *data,request *r,request *ev,int mode)
{
	fdbdata *fdb = (fdbdata*)data;
	timer *index = get_timer("FDB5 index time",NULL,true);

	if(fdb->fdb_home) {
		marslog(LOG_INFO, "Using FDB %s", fdb->fdb_home);
		if(last_home && strcmp(last_home, fdb->fdb_home) != 0) {
			marslog(LOG_EROR, "Cannot change FDB from %s to %s", last_home, fdb->fdb_home);
			return -1;
		}
		last_home = fdb->fdb_home;
        fdb5::LibFdb::instance().libraryHome(last_home);
	}

	fdb->u_v   = wind_new(r,&fdb->total_read);

	fdb->fdb_timer = get_timer("FDB5 read time",NULL,true);

	parameter *p = r->params;

	char name[80];

	MarsRequest req("retrieve");
	MarsRequest env("environ");

	while(p)
	{
		if(p->name[0] == '_') {
			p = p->next;
			continue;
		}

		strcpy(name, lowcase(p->name));
		std::string s = lowcase(p->values->name);

		std::vector<std::string> x;
		value* v = p->values;
		while(v) {
			x.push_back(lowcase(v->name));
			v = v->next;
		}

		req.setValues(name, x);

		// eckit::Log::info() << "key " << p->name << " " << p->values->name << std::endl;

		p = p->next;
	}

	// fdb->buffer_size = 64 * 1024 * 1024;
	fdb->buffer = (char*)malloc(fdb->buffer_size);

	// Initialise to null, to ensure cleanup is aborted if error occurs in initialisation
	fdb->f = 0;

	MarsTask task(req, env);

	try {
		timer_start(index);
		fdb5::Retriever retriever;
        fdb->f = retriever.retrieve(task)->openf("r", true);
		setvbuf(fdb->f,fdb->buffer,_IOFBF,fdb->buffer_size);

		timer_stop(index, 0);
	}
	catch(std::exception& e) {
        marslog(LOG_EROR,"FDB5 open error %s", e.what());
		return -2;
	}

	return 0;
}

static err  fdb5_close(void *data)
{
	fdbdata *fdb = (fdbdata*)data;
	if (fdb->f != 0) {
		fclose(fdb->f);
	}
	wind_free(fdb->u_v);

	timer_partial_rate(fdb->fdb_timer,fdb->start_time, fdb->total_read);

	free(fdb->buffer);
	return 0;
}


static err  fdb5_read(void *data, request *r, void *buffer, long *length)
{
	fdbdata *fdb = (fdbdata*)data;
	return timed_wind_next(fdb->u_v, fdb->f, (char *) buffer, length, fdb->fdb_timer);
}

static err  fdb5_write(void *data,request *r,void *buffer,long *length)
{
	marslog(LOG_EXIT,"FDB5 write not implemeted");
	return EOF;
}

static boolean  fdb5_check(void *data,request *r)
{
	if(track(r))
		return true;

	if(is_bufr(r) || image(r))
		return false;

	return true;
}


#else /* NOFDB5 */

extern "C" {

    base_class *fdb5base = &_nullbase;

	void mars_fdb5_init(int *argc, char **argv) {
	}

} // extern "C"

#endif /* NOFDB5 */
