/***************************** 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 <stdlib.h>
#include <time.h>
#include <MvRequest.h>
#include <MvVersionInfo.h>

#if defined(METVIEW_ODB)
    #include "odb_api/odbcapi.h"
#endif

#if defined(HAVE_PPROC_MIR)
    #include "mir/api/mir_version.h"
#endif

#include "macro.h"
#include "script.h"
#include "cbufr.h"


#define QUOTE_PP(P) #P
#define QUOTE_PPP(P) QUOTE_PP(P)


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

class NilFunction : public Function {
public:
	NilFunction(char *n) : Function(n,0) {}
	virtual Value Execute(int arity,Value *arg);
};

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

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

class NilAppendFunction : public Function {
public:
	NilAppendFunction(char *n) : Function(n) {}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int NilAppendFunction::ValidArguments(int arity,Value *arg)
{
	if(arity != 2) return false;	
	if(arg[0].GetType() == tnil || arg[1].GetType() == tnil)
		return true;
	return false;
}

Value NilAppendFunction::Execute(int,Value *arg)
{
	return arg[0].GetType() == tnil ? arg[1] : arg[0];
}

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

class NilCompareFunction : public Function {
	int equal;
public:
	NilCompareFunction(char *n,int e) : Function(n) { equal = e;}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int NilCompareFunction::ValidArguments(int arity,Value *arg)
{
	if(arity != 2) return false;	
	if(arg[0].GetType() == tnil || arg[1].GetType() == tnil)
		return true;
	return false;
}

Value NilCompareFunction::Execute(int,Value *arg)
{
	int cmp;
	if(arg[0].GetType() == tnil && arg[1].GetType() == tnil)
		cmp = 1;	
	else
		cmp = 0;
	return Value(cmp == equal);
}


class NilCountFunction : public Function {
public:
	NilCountFunction(char *n) : Function(n,1,tnil) { };
	virtual Value Execute(int arity,Value *arg);
};

Value NilCountFunction::Execute(int,Value *arg)
{
    int zero = 0;
	return Value(zero);
}



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

class TypeFunction : public Function {
public:
	TypeFunction(char *n) : Function(n,1,tany) 
	{info = "Returns the type of an expression";};
	virtual Value Execute(int arity,Value *arg);
};

Value TypeFunction::Execute(int,Value *arg)
{
	const char *p;

	arg[0].GetType(&p);

	Value v = Value(p);

	return v;
}

//=============================================================================
class CmpFallback : public Function {
	boolean equal;
public:
	CmpFallback(char *n,boolean eq) : Function(n) { equal = eq;}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

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

Value CmpFallback::Execute(int ,Value *)
{
	int d = equal?0:1;	
	return Value(d);
}
//=============================================================================
class SubFallback : public Function {
public:
	SubFallback(char *n) : Function(n) { }
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *arg);
};

int SubFallback::ValidArguments(int arity,Value *arg)
{
	if(arity != 2) return false;
	if(arg[1].GetType() != tnumber) return false;
	int n;
	arg[1].GetValue(n);
	return n == 1;
}

Value SubFallback::Execute(int ,Value *arg)
{
	return arg[0];
}
//=============================================================================
class CountFallback : public Function {
public:
	CountFallback(char *n) : Function(n,1,tany) { }
	virtual Value Execute(int arity,Value *arg);
};

Value CountFallback::Execute(int ,Value *)
{
	return Value(1);
}
//=============================================================================
class RandomFunction : public Function {
public:
	RandomFunction(char *n) : Function(n,0) { info="Generat random";};
	virtual Value Execute(int arity,Value *arg);
};

Value RandomFunction::Execute(int,Value*)
{
	static int first = 1;

	if(first)
	{
		srand48(getpid()*time(0));
		first = 0;
	}

	return Value(drand48());
}

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

class PrintFunction : public Function {
public:
	PrintFunction(char *n) : Function(n) { info="Prints all its arguments";};
	virtual Value Execute(int arity,Value *arg);
};

class FailFunction : public PrintFunction {
public:
	FailFunction(char *n) : PrintFunction(n) 
	{ info="Prints all its arguments and stop in error";};
	virtual Value Execute(int arity,Value *arg);
};

class StopFunction : public PrintFunction {
public:
	StopFunction(char *n) : PrintFunction(n) 
	{ info="Prints all its arguments and stop the macro";};
	virtual Value Execute(int arity,Value *arg);
};


Value PrintFunction::Execute(int arity,Value *arg)
{
	if(Context::Trace() >= 2) return Value(0.0);

	for(int i = 0;i<arity;i++)
	{
		arg[i].Sync(); // This will force sync
		arg[i].Print();
	}
	cout << '\n' << flush;
	return Value(0.0);
}

class ImportFunction : public Function {
public:
	ImportFunction(char *n) : Function(n,1,tstring) {}
	virtual Value Execute(int arity,Value *arg);
};

Value ImportFunction::Execute(int ,Value *arg)
{
	const char *p;
	Context *c = Context::Instruction->Owner();
	arg[0].GetValue(p);
	return c->ImportVariable(p);
}

class ExportFunction : public Function {
public:
	ExportFunction(char *n) : Function(n,1,tstring) {}
	virtual Value Execute(int arity,Value *arg);
};

Value ExportFunction::Execute(int ,Value *arg)
{
	const char *p;
	Context *c = Context::Instruction->Owner();
	arg[0].GetValue(p);
	return c->ExportVariable(p);
}

class DescribeFunction : public Function {
	bool actionSpecified;
public:
	DescribeFunction(char *n) : Function(n) 
	{info = "Describes available function";};
	virtual int ValidArguments(int arity,Value *arg);
	virtual Value Execute(int arity,Value *arg);
};

int DescribeFunction::ValidArguments(int arity,Value *arg)
{
	actionSpecified = false;

	if (arity == 1 && arg[0].GetType() == tstring)
		return true;

	if (arity == 2 && arg[0].GetType() == tstring && arg[1].GetType() == tstring)
	{
		actionSpecified = true;
		return true;
	}

	return false;
}


Value DescribeFunction::Execute(int ,Value *arg)
{
	const char *p;
	Context *c = Context::Instruction->Owner();
	int num_matches = 0;

	arg[0].GetValue(p);

	if (actionSpecified)  // action can currently only be 'as_value', meaning return the description(s) as a value
	{
		const char *second_arg;
		arg[1].GetValue(second_arg);
		if (strcmp(second_arg, "as_value"))
			return Error("The second argument, if supplied, must be 'as_value'.");
	}

	CList *outList;
	if (actionSpecified)
		outList = new CList(1); // 1 to start with because we don't know how big it wikll grow to


	List *l = (List*)c->Dictionaries()->Head();
	while(l)
	{
		Function *n = (Function*)l->Head();
		while(n)
		{
			if(p == n->Name())
			{
				num_matches++;
				if (actionSpecified)
					if (num_matches > 1)
						outList->Add(n->ToString());
					else
						(*outList)[0] = n->ToString();
				else
					n->Print();
			}
			n = (Function*)n->Next();
		}
		l = (List*)l->Next();
	}

	while(c)
	{
		Node *n = c->FirstFunction();
		while(n)
		{
			if(p == n->Name())
			{
				num_matches++;
				if (actionSpecified)
					if (num_matches > 1)
						outList->Add(n->ToString());
					else
						(*outList)[0] = n->ToString();
				else
					n->Print();
			}
			n = n->Next();
		}
		c = c->Owner();
	}

	if (actionSpecified)
		return outList;
	else
		return Value(0.0);
}

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

class StoreFunction : public Function {
public:
	StoreFunction(char *n) : Function(n,2,tstring,tany)
	{info = "Saves the data in a cache under the given name. args: (string, any)";}
	virtual Value Execute(int arity,Value *arg);
};

Value StoreFunction::Execute(int ,Value* arg)
{
	request *r;const char *name;
	arg[0].GetValue(name);
	arg[1].GetValue(r);

	ASync::Store(name,r);


	// for some file types, we need to manually set some flags to indicate
	// that the underlying files should not be deleted

	if (arg[1].GetType() == tgrib)
	{
		CGrib *c;
		arg[1].GetValue(c);
		c->IsIcon(true); // so that InPool won't touch the data
		c->SetFileTempFlag(false);
	}
	else if (arg[1].GetType() == tbufr)
	{
		CBufr *c;
		arg[1].GetValue(c);
		c->IsIcon(true); // so that InPool won't touch the data
	}

	set_value(r,"TEMPORARY","%d",0);  // store/fetch should have permanent files

	return Value();
}

class FetchFunction : public Function {
public:
	FetchFunction(char *n) : Function(n,1,tstring)
	{info = "Returns the item stored in the named cache (or nil). args: (string)";}
	virtual Value Execute(int arity,Value *arg);
};

Value FetchFunction::Execute(int,Value* arg)
{
	const char *name;
	arg[0].GetValue(name);
	request *r = ASync::Fetch(name);
	set_value(r,"TEMPORARY","%d",0);  // store/fetch should have permanent files
	Value v;
	v.SetContent(r);


	// for some file types, we need to manually set some flags to indicate
	// that the underlying files should not be deleted

	Content *content = v.GetContent();

	if (content->GetType(NULL) == tgrib)
	{
		CGrib *c = (CGrib *)content;
		c->IsIcon(true); // so that InPool won't touch the data
		c->SetFileTempFlag(false);
	}
	else if (content->GetType(NULL) == tbufr)
	{
		CBufr *c = (CBufr *)content;
		c->IsIcon(true); // so that InPool won't touch the data
	}

	return v;
}

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

class NameFunction : public Function {
public:
	NameFunction(char *n) : Function(n,0)
	{info = "Returns the path/name of the macro being executed. args: none";}
	virtual Value Execute(int arity,Value *arg);
};

Value NameFunction::Execute(int,Value*)
{
	return Value(Script::MacroPath());
}

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

class InlineFunction : public Function {
public:
	InlineFunction(char *n) : Function(n,2,tstring,tstring) {}
	virtual Value Execute(int arity,Value *arg);
};

Value InlineFunction::Execute(int,Value *arg)
{
	const char *t,*p;
	FILE *f;
	long len;
	void *buffer = 0;
	char *q = 0;

	arg[0].GetValue(t);
	arg[1].GetValue(p);

	switch(Value::NameType(t))
	{
		case tstring:
			f = fopen(p,"r");
			if(!f)
			{
				marslog(LOG_EROR|LOG_PERR,"Cannot open %s",p);
				return Error("Inline: internal error");
			}

			// Should check more here,,,
			fseek(f,0,SEEK_END);
			len = ftell(f);
			buffer = MALLOC(int(len+1));
			q = (char*)buffer;
			rewind(f);
			fread(buffer,1,(unsigned int)len,f);
			fclose(f);
			q[len] = 0;
			break;

		case tnone:
		case tany:
			return Error("Invalid type name '%s'",t);
			/* break; */

		default:
			return Error("Inline data not supported for type %s",t);
			/* break; */
	}

	Value v = q;
	FREE(buffer);
	return v;
}

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

class ArgsFunction : public Function {
public:
	ArgsFunction(char *n) : Function(n,0) 
	{ info = "Returns the list of the calling arguments of the current function";};
	virtual Value Execute(int arity,Value *arg);
};

Value ArgsFunction::Execute(int,Value*)
{
	Context *c = Context::Instruction->Owner();
	int    n = c->Argc();
	Value *v = c->Argv();

	CList *l = new CList(n);

	for(int i = 0;i<n;i++)
		(*l)[i] = v[i];

	return Value(l);
}



class DictionaryFunction : public Function {
public:
	DictionaryFunction(char *n) : Function(n) 
	{ info = "Returns the list of all the documented functions";};
	virtual Value Execute(int arity,Value *arg);
};

static int cmp(const void *a,const void *b)
{
	char **x = (char**)a;
	char **y = (char**)b;
	return strcmp(*x,*y);
}

Value DictionaryFunction::Execute(int arity,Value *)
{
	typedef const char* charp;
	Context *c = Context::Instruction->Owner();
	int count;
	int all = arity > 0;

	count = 0;

	List *l = (List*)c->Dictionaries()->Head();
	while(l)
	{
		Function *n = (Function*)l->Head();
		while(n)
		{
			if(all || n->Info()) count++;
			n = (Function*)n->Next();
		}
		l = (List*)l->Next();
	}


	while(c)
	{
		Function *n = c->FirstFunction();
		while(n)
		{
			if(all || n->Info()) count++;
			n = (Function*)n->Next();
		}
		c = c->Owner();
	}

	charp *names = new charp[count];

	count = 0;
	l = (List*)c->Dictionaries()->Head();
	while(l)
	{
		Function *n = (Function*)l->Head();
		while(n)
		{
			if(all ||n->Info()) 
				names[count++] = n->Name();
			n = (Function*)n->Next();
		}
		l = (List*)l->Next();
	}

	c = Context::Instruction->Owner();
	while(c)
	{
		Function *n = c->FirstFunction();
		while(n)
		{
			if(all ||n->Info())
				names[count++] = n->Name();
			n = (Function*)n->Next();
		}
		c = c->Owner();
	}

	qsort(names,count,sizeof(charp),cmp);
	int m = 0;
	int i;
	for( i=1;i<count;i++)
		if(names[m] != names[i]) 
			names[++m] = names[i];
	count = m+1;

    CList *v = new CList(count);
	for(i = 0;i<count;i++)
			(*v)[i] = Value(names[i]);
	delete[] names;
	return Value(v);
}

Value FailFunction::Execute(int arity,Value *arg)
{
	Context::Stop();
	PrintFunction::Execute(arity,arg);
	return Error("Macro failed");
}

Value StopFunction::Execute(int arity,Value *arg)
{
	Context::Stop();
	PrintFunction::Execute(arity,arg);
	return Value(0.0);
}


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

class MagMLFunction : public Function {

public:
   MagMLFunction(char *n) : Function(n,1,tstring)
      {/*"Executes MagML code";*/};

   virtual Value Execute(int arity,Value *arg);
};

Value MagMLFunction::Execute(int ,Value *arg)
{
	const char *magml_file;
	arg[0].GetValue(magml_file);
    MvRequest out;
    Value v;

    // create a new request to send to the MagML module

    MvRequest r;
    r.setVerb ("MAGML");
    r("PATH")   = magml_file;
    r("_MODE")  = "MACRO";
    r("PATH")   = magml_file;
	r("_MACRO") = Script::MacroPath();
	r("_PATH")  = Script::MacroMainPath();


    // call the MagML service directly with this request

	v = Value("MagML",r);

	return v;
}


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

class MemoryFunction : public Function {
public:
	MemoryFunction(char *n) : Function(n,0) {}
	virtual Value Execute(int arity,Value *arg);
};

Value MemoryFunction::Execute(int,Value*)
{
	memory_info();
	return Value();
}

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

class CallFunction : public Function {
public:
	CallFunction(char *n) : Function(n)
	    {info = "Calls the named function with the supplied arguments";}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *) { return arity>0; }
};

Value CallFunction::Execute(int arity,Value* argv)
{
	const char* name;
	argv[0].GetValue(name);

	for(int i = 1; i < arity; i++)
		Owner()->Push(argv[i]);

	Owner()->CallFunction(name,arity-1);
	return Owner()->Pop();
}

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

class CallArgsFunction : public Function {
public:
	CallArgsFunction(char *n) : Function(n,2,tstring,tlist)
	    {info = "Calls the named function with the given list of arguments";}
	virtual Value Execute(int arity,Value *arg);
};

Value CallArgsFunction::Execute(int arity,Value* argv)
{
	const char* name;
	argv[0].GetValue(name);

	CList *l;
	argv[1].GetValue(l);

	for(int i = 0; i < l->Count(); i++)
		Owner()->Push((*l)[i]);

	Owner()->CallFunction(name,l->Count());
	return Owner()->Pop();
}


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


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

class HelloWorldFunction : public Function {
public:
	HelloWorldFunction(char *n) : Function(n,0)
	{info = "Says Hello";}
	virtual Value Execute(int arity,Value *arg);
	virtual int ValidArguments(int arity,Value *) { return true; }
};

Value HelloWorldFunction::Execute(int arity,Value* argv)
{
	std::cout << "HelloWorldFunction" << std::endl;
	char *pr = strcache("print");

	for(int i = 0; i < arity; i++)
	{
		Owner()->Push(argv[i]);
		Owner()->CallFunction(pr, 1);
	}

	return Value();
}

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



/*******************************************************************************
 *
 * Class        : MetviewVersionFunction : Function
 *
 * Description  : Macro function that returns version information about Metview
 *
 ******************************************************************************/

extern "C" {

#ifdef FORTRAN_NO_UNDERSCORE
	#define EMOSNUM emosnum
#else
	#define EMOSNUM emosnum_
#endif

fortint EMOSNUM (fortint *KONOFF);

}


class MetviewVersionFunction : public Function {
public:
	MetviewVersionFunction (const char *n) : Function(n, 0)
	{info = "Returns version information about Metview";}
	virtual Value Execute        (int arity, Value *arg);
};


Value MetviewVersionFunction::Execute(int arity,Value *arg)
{
	request  *r;
	long grib_api_version;
	long mars_version;
	fortint emos_version, konoff=1;
    MvVersionInfo mvInfo;  // the constructor populates the class with information
	

	// the return value will be a request whose elements we will populate below
    r = empty_request (NULL);


	if (mvInfo.infoFound())
	{
		set_value(r, "metview_version", "%d", mvInfo.version());
		set_value(r, "metview_major",   "%d", mvInfo.majorVersion());
		set_value(r, "metview_minor",   "%d", mvInfo.minorVersion());
		set_value(r, "metview_revision","%d", mvInfo.revision());
	}
	
	else
	{
        char  msg [1024];
        strncpy (msg, mvInfo.errorMessage().c_str(), 1023);
		marslog(LOG_EROR, msg);
	}


	// extra code to take into account the possibility of
	// using either GRIB_API or ecCodes for GRIB handling
	std::string grib_package=QUOTE_PPP(GRIB_HANDLING_PACKAGE);
	for(unsigned int i = 0; i < grib_package.length(); ++i)
		grib_package[i] = tolower(grib_package[i]);
	grib_package += "_version";

	grib_api_version = grib_get_api_version();
	set_value(r, grib_package.c_str(), "%d", grib_api_version);

	mars_version = marsversion();
	set_value(r, "mars_version", "%d", mars_version);

	emos_version = emosnum_(&konoff);
	set_value(r, "emos_version", "%d", emos_version);

#if defined(HAVE_PPROC_MIR)
	unsigned int this_mir_version = mir_version_int();
	set_value(r, "mir_version", "%d", this_mir_version);
#endif



#if defined(METVIEW_ODB)
	const char *odbapi_version = odb_api_version();
	set_value(r, "odb_api_version", "%s", odbapi_version);
#endif


	return Value(r);
}



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

class WaitModeFunction : public Function {
public:
	WaitModeFunction(char *n) : Function(n,1,tnumber)
    {info = "Causes Macro to always wait(1)/not wait(0) for functions to complete before continuing.";}
	virtual Value Execute(int,Value*);
};

Value WaitModeFunction::Execute(int,Value *arg)
{
	int n = Context::WaitMode();
	int m;

	arg[0].GetValue(m);
	Context::WaitMode(m);

	return Value(n);
}


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

class FeatureAvailabilityFunction : public Function {
public:
    FeatureAvailabilityFunction(char *n) : Function(n,1,tstring)
    {info = "Returns 1 if the given feature is available, otherwise 0. Feature names: 'odb'";}
    virtual Value Execute(int,Value*);
};

Value FeatureAvailabilityFunction::Execute(int,Value *arg)
{
    const char *s;
    arg[0].GetValue(s);
    std::string featureName(s);
    
    if (featureName == "odb")
    {
#ifdef METVIEW_ODB
        return Value(1);
#else
        return Value(0.0);  // 0.0 to avoid ambiguity with NULL pointer
#endif
    }
    else
        Error("is_feature_available: feature name %s not recognised", s);

    return Value(); // should not get to here in fact
}


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

static void install(Context *c)
{
	c->AddFunction(new PrintFunction("print"));
	c->AddFunction(new FailFunction("fail"));
	c->AddFunction(new StopFunction("stop"));
	c->AddFunction(new ImportFunction("import"));
	c->AddFunction(new ExportFunction("export"));
	c->AddFunction(new TypeFunction("type"));
	c->AddFunction(new DescribeFunction("describe"));
	c->AddFunction(new DictionaryFunction("dictionary"));
	c->AddFunction(new ArgsFunction("arguments"));
	c->AddFunction(new NilFunction("nil"));
	c->AddFunction(new NilAppendFunction("&"));
	c->AddFunction(new NilAppendFunction("merge"));
	c->AddFunction(new NilCompareFunction("=",1));
	c->AddFunction(new NilCompareFunction("<>",0));
	c->AddFunction(new NilCountFunction("count"));
	c->AddFunction(new NameFunction("name"));
	c->AddFunction(new StoreFunction("store"));
	c->AddFunction(new FetchFunction("fetch"));
	c->AddFunction(new InlineFunction("inline"));
	c->AddFunction(new RandomFunction("random"));
	c->AddFunction(new WaitModeFunction("waitmode"));

	c->AddFunction(new MagMLFunction("magml"));

	c->AddFunction(new CallFunction("call"));
	c->AddFunction(new CallArgsFunction("callargs"));

	c->AddFunction(new MemoryFunction("memory_info"));
	c->AddFunction(new MetviewVersionFunction("version_info"));
	c->AddFunction(new FeatureAvailabilityFunction("is_feature_available"));


	c->AddFunction(new HelloWorldFunction("hello"));


	c->AddFallback(new CountFallback("count"));
	c->AddFallback(new SubFallback("[]"));
	c->AddFallback(new CmpFallback("=",true));
	c->AddFallback(new CmpFallback("<>",false));
}

static Linkage Link(install);

