/***************************** 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 "macro.h"
#include "cbufr.h"
#include "cnetcdf.h"
#include "ctable.h"
#include "cpngjpeg.h"

#ifdef METVIEW_ODB
#include "codb.h"
#endif

#include "MvFlextra.cc"

#include "LLMatrixToGRIB.h"
#include "MvScanFileType.h"
//=============================================================================


class CFile : public Content {

	typedef int (*closeproc)(FILE*);

	char *name;
	int  rmode; // read mode
	FILE *f;
	closeproc closef;


	virtual void ToFile(CFile* &x)  { x = this; };
	virtual void Print();
public:
	CFile(const char *n) : Content(tfile)
		{ f = NULL; name = strcache(n); rmode = 1;}
	~CFile();

	//

	FILE *File(char *m);
};

CFile::~CFile()
{
	if(f != NULL && closef(f) != 0 )
		Error("-Error while closing file %s",name);
	strfree(name);
}

void CFile::Print()
{
	cout << "<file " << name;
	if(f) {
		const char *p = rmode?"(read)":"(write)";
		cout << ' ' << p;
	}
	cout << '>';
}

FILE *CFile::File(char *m)
{
	int r = *m == 'r';

	if(f == NULL)
	{
		if(*name == '|')
		{
			f = popen(name+1,m);
			closef = pclose;
		}
		else
		{
			f = fopen(name,m);
			closef = fclose;
		}
		if(f == NULL)
			Error("Cannot open file %s",name);
		rmode = r;
		return f;
	}
	else if( r != rmode )
	{
		Error("File %s in open in '%s' mode",name,rmode?"read":"write");
		return NULL;
	}
	return f;
}

//=============================================================================

class FileFunction : public Function {
public:
	FileFunction(const char *n) : Function(n,1,tstring)
	{info = "Returns a file handler for the specified filename. args: (string)"; }
	virtual Value Execute(int arity,Value *arg);
};

Value FileFunction::Execute(int,Value *arg)
{
	const char *p;
	arg[0].GetValue(p);
	return Value(new CFile(p));
}

//=============================================================================
class Write1Function : public Function {
public:
	Write1Function(char *n) : Function(n)
	{info = "Writes/appends the given data to file. args: (string, any)";}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int Write1Function::ValidArguments(int arity,Value *arg)
{
	if(arity < 2) return false;
	if(arg[0].GetType() != tstring) return false;
	return true;
}

Value Write1Function::Execute(int arity,Value *arg)
{
	const char *p;
	arg[0].GetValue(p);

	// Get mode from function name
	char mode[2];
	mode [0] = Name()[0];
	mode [1] = 0;

	FILE *f = *p == '|' ? popen(p+1,mode) : fopen(p,mode);
	if(!f)  return Error("-Cannot open file %s",p);

	int e = 0;

	for(int i = 1;i<arity;i++)
	{
		arg[i].Sync(); // This will force sync
		
		//Flextra related part
		if(arg[i].GetType() == trequest)
		{
		  	request *r;
			arg[i].GetValue(r);
	
			if(r && strcmp(r->name,"FLEXTRA_FILE")==0)
			{
				const char* path=get_value(r,"PATH",0);
				if(!path)
				{  
					e=1;
					break;
				}
	  	 	  	
				string s(path);
				MvFlextra flx(s);
				flx.write(f);
			}
			else if(e = arg[i].Write(f))
				break;
		}
		
		//Any other type
		else if(e = arg[i].Write(f))
			break;
	}

	int c = *p == '|' ? pclose(f) : fclose(f);
	if(c)
		return Error("-Error while closing %s",p);

	if(e) return Error("Write failed");

	return Value(0.0);
}

//=============================================================================
class Write2Function : public Function {
public:
	Write2Function(const char *n) : Function(n)
	{info = "Writes/appends the given data to file. args: (filehandler, any)";}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int Write2Function::ValidArguments(int arity,Value *arg)
{
	if(arity < 2) return false;
	if(arg[0].GetType() != tfile) return false;
	return true;
}

Value Write2Function::Execute(int arity,Value *arg)
{
	CFile *file;
	arg[0].GetValue(file);

	// Get mode from function name
	char mode[2];
	mode [0] = Name()[0];
	mode [1] = 0;

	FILE *f = file->File(mode);
	if(!f) return Error("Invalid file");

	int e = 0;

	for(int i = 1;i<arity;i++)
	{
		arg[i].Sync(); // This will force sync

		//Flextra related part
		if(arg[i].GetType() == trequest)
		{
		  	request *r;
			arg[i].GetValue(r);
	
			if(r && strcmp(r->name,"FLEXTRA_FILE")==0)
			{
				const char* path=get_value(r,"PATH",0);
				if(!path)
				{  
					e=1;
					break;
				}
	  	 	  	
				string s(path);
				MvFlextra flx(s);
				flx.write(f);
			}
			else if(e = arg[i].Write(f))
				break;
		}
		
		if(e = arg[i].Write(f))
			break;
	}

	if(e) return Error("Write failed");

	return Value(0.0);
}

//=============================================================================

class TmpNameFunction : public Function {
public:
	TmpNameFunction(const char *n) : Function(n,0)
	{info = "Returns a new, unique temporary filename. args: none";}
	virtual Value Execute(int arity,Value *arg);
};

Value TmpNameFunction::Execute(int,Value *)
{
	return marstmp();
}

//=============================================================================

class ExistFunction : public Function {
public:
	ExistFunction(const char *n) : Function(n,1,tstring)
	{info = "Determines whether the given file exists. Returns 1 or 0. args: (string)";}
	virtual Value Execute(int arity,Value *arg);
};

Value ExistFunction::Execute(int,Value *arg)
{
	const char *p;
	arg[0].GetValue(p);
	return access(p,F_OK) == 0;
}

//=============================================================================

class ReadFunction : public Function {
public:
	ReadFunction(const char *n) : Function(n,1,tstring)
	{info = "Reads the given file and returns a variable of the appropriate type. args: (string)";}
	virtual Value Execute(int arity,Value *arg);
};

Value ReadFunction::Execute(int,Value *arg)
{
	const char *p;
	arg[0].GetValue(p);

	if(*p == '|')
		return Error("Pipe not yet supported in read()");

	string kind = ScanFileType(p);

	Content *c = NULL;
	// Try to guess

	if( kind == "GRIB" )
		// Here, check for images
		c = new CGrib(p);

	if( kind == "BUFR" )
		c = new CBufr(p);

	if( kind == "GEOPOINTS" )
	  c = new CGeopts(p);

	if( kind == "NETCDF" || kind == "SCM_INPUT_DATA" || kind == "SCM_OUTPUT_DATA" || kind.substr(0,7) == "NETCDF_" )
		c = new CNetCDF(p);

	if( kind == "TABLE" )
		c = new CTable(p);

	if( kind == "LLMATRIX" )
	{
		char *tmp = marstmp();
		if(LLMatrixToGRIB(p,tmp) == 0)
			c = new CGrib(tmp);
		else
			return Error("LLMatrix-to-GRIB failed");
	}

	if( kind == "PNG" || kind == "JPEG" )
	  c = new CPngJpeg(p,kind);

	if( kind == "NOTE" || kind == "MACRO" )
	{
		string line;
		ifstream f(p);
		if ( !f ) {
			marslog(LOG_EROR|LOG_PERR,"Cannot open %s",p);
			return  Error("Open failed");
		}

		int cnt = 0;
		while(getline(f,line))
			cnt++;

		f.clear();
		f.seekg(0, ios::beg);

		CList *l = new CList(cnt);
		for(int i=0;i<cnt;i++)
		{
			getline(f,line);

			if(isdate(line.c_str()))
				(*l)[i] = Value(Date(line.c_str()));
			else if(is_number(line.c_str()))
				(*l)[i] = Value(atof(line.c_str()));
			else
				(*l)[i] = Value(line.c_str());
		}

		c = l;

		f.close();

	}

#ifdef METVIEW_ODB	
	if( kind == "ODB_DB" )
	 	 c = new COdb(p);
#endif

	if( kind == "FLEXTRA_FILE" )
	{
		request *r = empty_request("FLEXTRA_FILE");
		
		const char* pwd=getenv("PWD"); 
		if(pwd)
		{
		  	string str(pwd);
			str+="/" + string(p);
			set_value(r,"PATH",str.c_str());
		}	
		else
		{
		  	set_value(r,"PATH",p);
		}
		c = new CRequest(r);
	}	
	  	
	return c ? Value(c) : Error("Cannot read %s. Type %s is not supported.",
		p, kind.c_str() );
}

//=============================================================================

static void install(Context *c)
{
	c->AddFunction(new Write1Function("write"));
	c->AddFunction(new Write2Function("write"));
	c->AddFunction(new Write1Function("append"));
	c->AddFunction(new Write2Function("append"));
	c->AddFunction(new FileFunction("file"));
	c->AddFunction(new ExistFunction("exist"));
	c->AddFunction(new ReadFunction("read"));
	c->AddFunction(new TmpNameFunction("tmpname"));
}

static Linkage Link(install);
