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

#ifdef ODBSERVER
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <ctype.h>


/* db prototypes */

static void    odb_init(void);
static err     odb_open(void *data, request *r, request *e, int mode);
static err     odb_close(void *data);
static err     odb_read(void *data, request *r, void *buffer, long *length);
static err     odb_write(void *data, request *r, void *buffer, long *length);
static err     odb_query(void *data, request *r, long *length);
static boolean odb_check(void *data, request *r);

struct CurrentFile {
	FILE      *f;
	long long size;
	long long offset;

	size_t    currentBlock;
	size_t    numberOfBlocks;
	off64_t*  offsets;
	size_t*   sizes;
};

typedef struct odbdata {
	char      *root;

	struct CurrentFile currentFile;

	long 	  nbfiles;     
	long 	  fileIndex;     
	char      **files;
} odbdata;


static option opts[] = {
	{"odb_server_root","ODB_SERVER_ROOT","-odb_server_root",".",
	t_str,sizeof(char*),OFFSET(odbdata,root) },
};


/* End of ODB interface */


base_class _odbbase = {

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

	false,                  /* inited       */

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

	odb_init,               /* init         */

	odb_open,               /* open         */
	odb_close,              /* close        */

	odb_read,               /* read         */
	odb_write,              /* write        */

	NULL,                   /* control      */

	odb_check,				/* check */

};



/* the only 'public' variable ... */

base_class *odbbase = &_odbbase;


static void odb_init(void)
{
	marslog(LOG_DBUG,"odb_init:Initialize odb");
	odb_start();
}
/* ==========================================*/
typedef char *(*chkproc)(request *r,const char*,const char*,int idx);

static char *extchar(request *r,const char*,const char*,int idx);
static char *extlong(request *r,const char*,const char*,int idx);
static char *extreal(request *r,const char*,const char*,int idx);

typedef struct chktype{
	char    *name;
	char    *format;
	int     size;
	chkproc extract;
	boolean in_request;
} chktype;

static chktype checks_odb[] = {
	{":"            ,"%s",  0,  extchar, false, }, 
	{"CLASS"        ,"%s",  2, 	extchar, true,  },
	{":"            ,"%s",  0,  extchar, false, }, 
	{"STREAM"       ,"%s",  4, 	extchar, true,  },
	{":"            ,"%s",  0,  extchar, false, }, 
	{"EXPVER"       ,"%s",  4,  extchar, true,  },
	{":"            ,"%s",  0,  extchar, false, }, 
	{"DATE"         ,"%s",  8, 	extchar, true,  },
	{"/"            ,"%s",  0,  extchar, false, }, 
	{":"            ,"%s",  0,  extchar, false, }, 
	{"TIME"         ,"%04d",4,  extlong, true,  },
	{":"            ,"%s",  0,  extchar, false, }, 
	{"TYPE"         ,"%s",  3, 	extchar, true,  },
	{":"            ,"%s",  0,  extchar, false, }, 
	{"OBSGROUP"     ,"%s",  0, 	extchar, true,  },
	{"/"            ,"%s",  0,  extchar, false, }, 
	{"REPORTYPE"    ,"%s",  0,  extchar, true,  },
	{"."            ,"%s",  0,  extchar, false, }, 
	{"odb"          ,"%s",  0,  extchar, false, }, 
};


static char *filename(request *r)
{
	static char buf[1024];
	int i;

	chktype *checks = checks_odb;
	int n = NUMBER(checks_odb);

	marslog(LOG_DBUG,"filename(r) checks: %d",n);
	
	buf[0] = 0;
	for(i=0;i<n;i++)
	{
		if(checks[i].in_request)
			strcat(buf,checks[i].extract(r,checks[i].name,checks[i].format,checks[i].size));
		else
			strcat(buf,checks[i].name);
		marslog(LOG_DBUG,"filename(r) intermediate: '%s'",buf);
	}

	marslog(LOG_DBUG,"filename(r) returns: '%s'",buf);

	return buf;
}

static void complete(char *p,int len)
{
	len -= strlen(p);
	while(len--) strcat(p,"x");
}

static char *extchar(request *r,const char* name,const char* format,int size)
{
	static char buf[200];
	const char *x = get_value(r,name,0);
	buf[0] = 0;
	if(x) sprintf(buf,format,lowcase(x));
	if(size>0)
		complete(buf,size);
	return buf;
}

static char *extlong(request *r,const char* name,const char* format,int size)
{
	static char buf[200];
	const char *x = get_value(r,name,0);
	buf[0] = 0;
	if(x) sprintf(buf,format,atol(x));
	if(size>0)
		complete(buf,size);
	return buf;
}

/* ===== odb file operations ===== */

static err odbfile_open(odbdata *odb, int i)
{
	char *s = odb->files[i];
	FILE *f = fopen(s,"r");
	if(f == NULL)
	{
		marslog(LOG_EROR|LOG_PERR, "odbfile_open: '%s'", s);
		return -2;
	}

	marslog(LOG_DBUG, "odbfile_open (%s) index %d", s, i);

	odb->currentFile.numberOfBlocks = 0;
	odb->currentFile.offsets = 0;
	odb->currentFile.sizes = 0;

	int rc = get_blocks_offsets(s, &odb->currentFile.numberOfBlocks, &odb->currentFile.offsets, &odb->currentFile.sizes);

	/* Position at the end to get size */
	if(fseek(f,0,SEEK_END) != 0)
	{
		marslog(LOG_EROR|LOG_PERR, "fseek(%s)", s);
		return -2;
	}
	odb->currentFile.size = ftell(f);

	/* Re-position at the begining */
	if(fseek(f,0,SEEK_SET) != 0)
	{
		marslog(LOG_EROR|LOG_PERR, "fseek(%s)", s);
		return -2;
	}

	odb->fileIndex = i;
	odb->currentFile.f = f;
	odb->currentFile.offset = 0;
	odb->currentFile.currentBlock = 0;

	return NOERR;
}

static err odbfile_close(odbdata *odb, int i)
{
	marslog(LOG_DBUG,"odbfile_close (%s) index %d", odb->files[i], i);

	fclose(odb->currentFile.f);
	odb->currentFile.f = NULL;

	odb_start();
	release_blocks_offsets(&odb->currentFile.offsets);
	release_blocks_sizes(&odb->currentFile.sizes);

	odb->currentFile.offsets = 0;
	odb->currentFile.sizes = 0;

	return NOERR;
}

/*
TODO: set disk buffer sizes
	if(mars.readdisk_buffer > 0) { if(setvbuf(f,dhs->read_disk_buffer,_IOFBF,mars.readdisk_buffer)) marslog(LOG_WARN|LOG_PERR,"setvbuf failed"); }
*/

/* TODO: Check different cases
- buffer smaller than file to read
- buffer bigger than file to read
- read from more than one file
*/
static err odbfile_read(odbdata *odb, char* buffer, long* length)
{
	timer *disk_time = get_timer("Read from disk", NULL, true);
	long read = 0;

	marslog(LOG_DBUG, "odbfile_read %ld bytes", *length);

	if (odb->currentFile.currentBlock >= odb->currentFile.numberOfBlocks)
	{
		if (++odb->fileIndex >= odb->nbfiles)
			return EOF;

		odbfile_close(odb, odb->fileIndex);
		odbfile_open(odb, ++odb->fileIndex);
		return odbfile_read(odb, buffer, length);
	}

	size_t blockSize = odb->currentFile.sizes[odb->currentFile.currentBlock];
	if (blockSize > *length)
	{
		marslog(LOG_EROR, "Block %d is too large.", odb->currentFile.currentBlock);
		return -3;
	}

	long n = timed_fread(buffer, 1, blockSize, odb->currentFile.f, disk_time);
	if (n != blockSize)
	{
		marslog(LOG_EROR, "Error reading block %d.", odb->currentFile.currentBlock);
		return -2;
	}
	*length = blockSize;
	return NOERR;
}

/* ==========================================*/

static err odb_open(void *data, request *r, request *e, int mode)
{
	odbdata *odb = (odbdata*)data;
    parameter *p = 0;
	long      flags  = expand_flags(EXPAND_MARS| EXPAND_SUB_LISTS);
	request   *s = 0;
	const char *type = 0;
	char *fname = NULL;
	request *u = NULL;
	request *a = NULL;
	int i = 0;

	marslog(LOG_DBUG,"odb_open");

	unset_value(r,"TARGET");
	s = expand_all_requests(mars_language(),mars_rules(),r);
	type = get_value(s,"TYPE",0);

	expand_flags(flags);
	reset_language(mars_language());

	marslog(LOG_INFO,"ODB_SERVER_ROOT=%s",odb->root);

	/* Find the file names to open */
	u = unwind_one_request(r);
	odb->nbfiles = count_requests(u);
	marslog(LOG_DBUG,"Files to open: %ld",odb->nbfiles);

	odb->files = (char **)reserve_mem(odb->nbfiles * sizeof(char *));
	a = u;
	i = 0;
	while(a)
	{
		char *f = filename(a);
		char name[1024];
		sprintf(name,"%s/%s",odb->root,f);
		/* marslog(LOG_INFO,"Request: "); */
		print_one_request(a);
		/* marslog(LOG_INFO,"Filename for request: '%s'",name); */
		if(faccess(name,0) == 0)
			odb->files[i++] = strcache(name);
		else
		{
			marslog(LOG_EROR|LOG_PERR,"file %s",name);
		}
		a = a->next;
	}
	odb->nbfiles = i;

	marslog(LOG_DBUG,"Filenames found: ");
	for(i = 0; i<odb->nbfiles; ++i)
		marslog(LOG_INFO,"ODB file: '%s'",odb->files[i]);

	free_all_requests(u);
	free_all_requests(s);

	/* If no files found, there is no data for this request. Return */
	if(odb->nbfiles == 0)
		return -1;

	odbfile_open(odb,0);

	return NOERR;
}


static err odb_close(void *data)
{
	odbdata *odb = (odbdata*)data;
	int i = 0;
	marslog(LOG_DBUG,"odb_close");
	
	for(i = 0; i<odb->nbfiles; ++i)
		strfree(odb->files[i]);

	release_mem(odb->files);
	odb->files = NULL;
	if(odb->currentFile.f != NULL)
		fclose(odb->currentFile.f);
	odb->currentFile.f = NULL;
	odb->currentFile.size = 0;
	odb->currentFile.offset = 0;
	odb->nbfiles = 0;
	odb->fileIndex = 0;
	return NOERR;
}



static err odb_read(void *data,request *r,void *buffer,long *length)
{
	odbdata *odb = (odbdata*)data;
	static int count = 0;
	long size = *length;
	err e = NOERR;
	boolean save = (getenv("SAVE_DATA_READodb") != NULL);

	marslog(LOG_DBUG,"-> odb_read: %ld bytes",*length);

	e = odbfile_read(odb,buffer,length);

	marslog(LOG_DBUG,"<- odb_read: %ld bytes, return code %d",*length,e);
	if(*length > size)
		marslog(LOG_EROR,"Read %ld bytes > buffer size (%ld bytes)",*length,size);

	if(save)
	{
		FILE   *fd;
		char fname[50];
		sprintf(fname,"readodb.buffer.%d",count);
		if( (fd = fopen(fname,"w")) == NULL)
			marslog(LOG_EROR|LOG_PERR,"Error saving buffer read from odb. Can't open file %s",fname);
		else
		{
			if(fwrite(buffer,1,*length,fd) != *length)
				marslog(LOG_EROR|LOG_PERR,"Error writing to file %s",fname);
		}
		fclose(fd);
	}
	
	count++;
	marslog(LOG_DBUG,"odb_read returns %d",e);
	return e;
}


static err odb_write(void *data, request *r, void *buffer, long *length)
{
	marslog(LOG_EROR,"odb_write:Not implemented");

	return NOERR;
}


static boolean odb_check(void  *data, request *r)
{
	const char *type = get_value(r,"TYPE",0);

	marslog(LOG_DBUG,"odb_check: ");
	if(is_odb(r))
		return true;

	return false;
}
#else
extern base_class _nullbase;
base_class *odbbase = &_nullbase;
#endif
