/*
 * © 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"
#include <ctype.h>

#define DB_COUNT 2

static void     multi_init(void);
static err      multi_open(void *data,request*,request*,int);
static err      multi_close(void *data);
static err      multi_read(void *data,request *r,void *buffer,long *length);
static err      multi_write(void *data,request *r,void *buffer,long *length);
static boolean  multi_check(void *data,request *r);
static err      multi_validate(void *data,request*,request*,int);


typedef struct multidata {
	char     *base[2];
	database *db[2];
	request  *r;
	hypercube  *cube;
	request  *grib;
	int      current;
	int      count;
	int      got;
	int      expect;
	char     *found;
	char     *ignore;
} multidata;

static option opts[] = {
    {"base1",NULL,NULL,"mars1",
    t_str,sizeof(char*), OFFSET(multidata,base[0])},

    {"base2",NULL,NULL,"mars2",
    t_str,sizeof(char*), OFFSET(multidata,base[1])},

    {"ignore",NULL,NULL,NULL,
    t_str,sizeof(char*), OFFSET(multidata,ignore)},
};


base_class _multibase = {

	NULL,                      /* parent class */
	"multibase",                /* name         */

	false,                     /* inited       */

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

	multi_init,                  /* init         */

	multi_open,                  /* open         */
	multi_close,                 /* close        */

	multi_read,                  /* read         */
	multi_write,                 /* write        */

	NULL,                      /* control      */

	multi_check,                 /* check        */
    NULL,                      /* query        */

    NULL,                      /* archive      */
    NULL,                      /* admin        */

    multi_validate,            /* validate */


};


base_class *multibase = &_multibase;

static void multi_init(void)
{
}


static err open_next(multidata *multi)
{
	const char   *name;
	request      *cachesetup = 0;
	request *setup;

	if(multi->current >= 0 && multi->db[multi->current])
	{
		err e = database_close(multi->db[multi->current]);
		multi->db[multi->current] = NULL;
		if(e) return e;
	}

	multi->current++;
	if(multi->current >= DB_COUNT)
		return EOF;

	setup = findbase(multi->base[multi->current]);
	if(!setup)
		return -2;

	multi->db[multi->current] = openbase(setup,multi->r,&name,&cachesetup,READ_MODE);
	if(!multi->db[multi->current])
		return -2;

	return NOERR;
}

static err  multi_open(void *data,request *r,request *e,int mode)
{
	multidata *multi = (multidata*)data;
	multi->current = -1;

	multi->expect = count_fields(r);
	multi->r = clone_all_requests(r);

	multi->cube = new_hypercube_from_mars_request(multi->r);

	unset_value(multi->r,"EXPECT");
	multi->count = count_fields(multi->r);

	if(multi->count)
		multi->found = NEW_ARRAY_CLEAR(char,multi->count);

	multi->grib = empty_request("GRIB");
	return open_next(multi);
}

static err  multi_close(void *data)
{
	multidata *multi = (multidata*)data;
	int ret = 0;
	int i;
	int e;

	for(i = 0; i < DB_COUNT; i++)
		if(multi->db[i] != 0)
		{
			e = database_close(multi->db[i]);
			if(e) ret = e;
		}

	free_all_requests(multi->grib);
	free_all_requests(multi->r);
	free_hypercube(multi->cube);
	FREE(multi->found);

	return ret;
}

static boolean not_duplicate(multidata *multi,void *buffer,long length)
{
	int i = 0;

	if(multi->expect == 0 || multi->count == 0)
		return true;

	if( grib_to_request(multi->grib,buffer,length) != NOERR)
	{
		marslog(LOG_WARN,"Multi-base: error in grib_to_request");
		return true;
	}

	if( multi->ignore ) {
		set_value(multi->grib, multi->ignore, get_value(multi->r, multi->ignore, 0));
	}

	i = cube_order(multi->cube, multi->grib);

	if( i < 0 || i >= multi->count)
	{
		marslog(LOG_WARN,"Multi-base: unexpected grib");
		print_all_requests(multi->grib);
		return true;
	}

	if(multi->found[i])
		return false;

	multi->got++;
	multi->found[i] = true;

	return true;
}

static err  multi_read(void *data,request *r,void *buffer,long *length)
{
	multidata *multi = (multidata*)data;
	int ret = 0;
	long save = *length;


	/* To see.... */
	if(multi->expect > 0 && multi->got == multi->expect)
		return EOF;

	while(multi->current < DB_COUNT)
	{
		*length = save;
		ret = database_read(multi->db[multi->current],r,buffer,length);

		switch(ret)
		{
			case NOERR:
				if(not_duplicate(multi,buffer,*length))
					return NOERR;
				break;

			default:
				ret = open_next(multi);
				if(ret) return ret;
				break;
		}
	}

	return EOF;
}

static err  multi_write(void *data,request *r,void *buffer,long *length)
{
	marslog(LOG_EROR,"Cannot write on Multi-database");
	return -2;
}

static boolean  multi_check(void *data,request *r)
{
	return is_grib(r) || is_bufr(r);
}

static err      multi_validate(void *data,request *r,request *e,int mode)
{
	multidata *multi = (multidata*)data;
	const char   *name;
	request     *setup;
	int i;

	for(i = 0; i < DB_COUNT; i++)
	{
		setup = findbase(multi->base[i]);
		if(!setup) return -1;
		name  = get_value(setup,"class",0);
		if(database_validate(base_class_by_name(name),multi->base[i],r,e,mode) != 0)
			return -1;
	}

	return 0;
}
