/***************************** 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 "script.h"
#include <stdarg.h>

boolean  Context::stop             = false;
int      Context::trace            = 0;
int      Context::waitmode         = 0;
int      Context::sendlines        = 0;
int      Context::pause            = 0;
Context *Context::Current          = 0;
Step    *Context::Instruction      = 0;
Linkage *Linkage::Links            = 0;
const char    *Context::InitGlobals      = strcache(".init_globals");
const int      UN_BUFSIZ = 300;

const char *Context::UniqueName(void)
{
	static char buf[ UN_BUFSIZ ];
	static int n = 0;
	Context *c = Current;
	while(c && c->Owner()) c = c->Owner();
	const char *name = (c && c->Name()) ? c->Name() : "?macro?";
	int cnt = sprintf(buf,"/Process@%d/%s, line %d:%d",getpid(),name,
		Instruction?Instruction->line:0,n++);
	if( cnt >= UN_BUFSIZ )
	    marslog( LOG_EXIT, "Buffer overflow in Context::UniqueName()" );
	return buf;
}

static const char *modes[] =  {

    "edit",      0,

    "hardcopy",  "batch",
    "batch",     "execute",
    "execute",   0,

    "prepare",   "visualise",
    "examine",   "visualise",
    "save",      "visualise",
    "drop",      "visualise",
    "visualise", "execute",

};


const char *Context::FallBackHandler(const char *name)
{
	static boolean first = true;

	if(!name) return 0;

	if(first)
	{
		for(unsigned int i=0;i<NUMBER(modes);i++)
			modes[i] = strcache(modes[i]);
		first = false;	
	}

	for(unsigned int i=0;i<NUMBER(modes); i += 2)
		if(name == modes[i])
			return modes[i+1];

	return 0;
}

Value Context::Run(const char *handler,int ac,Value *av)
{
	argn  = 0;
	argc  = ac;
	argv  = av;
	stop  = false;

	if(!inited)
	{
		inited = 1;
		Function *f = FindFunction(InitGlobals,0,0);
		if(f && f->Owner() == this) {
			// Because InitGlobals does not return, push a value on the stack
			Push(Value(0.0));
			f->Execute(0,0);
		}
	}

	Context *savedContext = Current;  // save the previous context
	Current = this; // ?????

	SetError(0);

	if(handler)
	{
		Function *f = FindHandler(handler,argc,argv);
		if(f) return f->Execute(argc,argv);
	}

	// Save locals
	List there;

	Locals.Save(there);

	Instruction = FirstStep();

	while(Instruction && (GetError() == 0) && !Stopped())
	{
		if (Context::SendLines())  // should we send line numbers to the macero editor?
		{
			static char lineMessage[64];
			static int line = -1;

			if (Instruction->line != line)
			{
            	line = Instruction->line;
				sprintf (lineMessage, "MACROEDITOR: LINE %d", line);
				Script::PutMessage(0, lineMessage);  // first param is unused

				if (Script::PauseBetweenLines())  // optional pause between lines?
					sleep(PauseBetweenLines());
			}
		}
		Instruction = Instruction->Execute();
	}

	Locals.Empty();
	Locals.Restore(there);

	Current = savedContext;  // restore the pointer to the previous context
	return Pop();
}

Context *Context::FindContext(const char *name)
{
	return (Context*)Contexts.Find(name,0);
}

Function *Context::WhichFunction(const char *name,int arity,Value *arg,int method)
{

	// Make sure that all the ASync are up to date
//	for(int i=0;i<arity;i++) arg[i].Sync();


	Function *v = FindMethod(name,arity,arg,GetObject());

	if(!method)
	{
		if(v == 0)
			v = FindInDictionary(name,arity,arg);

		if(v == 0)
			v = FindFunction(name,arity,arg);

		if(!method && v == 0)
			v = FindFallback(name,arity,arg);

		if(v == 0)
			v = FindHandler(name,arity,arg);

		if(v == 0)
			v = FindLibrary(name,arity,arg);
	}

	if(v == 0) {

		char buf[1024];
		if(method)
			strcpy(buf,"Method not found: ");
		else
			strcpy(buf,"Function not found: ");
		strcat(buf,name);
		if(arity)  { 
			strcat(buf,"(");
			for(int i=0;i<arity;i++)
			{
				const char *p;
				arg[i].GetType(&p);
				strcat(buf,p);
				if(i<arity-1) strcat(buf,",");
			}
			strcat(buf,")");
		}
		Error(buf);

	}
	return v;
}

void Context::CallFunction(const char *name,int arity,int method)
{
	
	Function *v = WhichFunction(name,arity,GetParameters(arity),method);
	Value  u;
	int i;
	int line = CurrentLine();

	if(trace && v)
	{

		if(trace == 1)
		{
			cout << "# Line " << line;
			cout << ": Calling " << v->Name() << '(';
		}
		else
		{
			cout << "call('" << v->Name() << "',\n";
		}

		Value *e = GetParameters(arity);

		for(i=0;i<arity;i++)
		{
			e[i].Dump(trace);		
			if (i != arity - 1) cout << ',';
		}
		if(trace == 1)
			cout << ')' << endl;
		else
			cout << ");" << endl;
	}

	if(v) u = v->Execute(arity,GetParameters(arity));

	// Pop parameters

	while(arity--) Pop();

	// Push result

	if((trace || waitmode) && v)
	{
		if(trace == 1)
		{
			cout << "# Line " << line;
			cout << ": " << v->Name() << " returns ";
			u.Dump(trace);
		}

		if(waitmode || trace > 1)
		{
			u.Sync();
		}

		if(trace > 1)
		{
			cout << "result('" << v->Name() << "',\n";
			u.Dump(trace);
			cout << ");";
		}

		if (trace)
			cout << endl;
	}

	Push(u);

}

Variable  *Context::FindGlobal(const char *name)
{
	Variable *v = (Variable*)Globals.Find(name);
	if(v) return v;
	if(Owner()) return Owner()->FindGlobal(name);
	return 0;
}

Variable  *Context::FindVariable(const char *name)
{
	Variable *v = (Variable*)Locals.Find(name);
	if(v) return v;
	return FindGlobal(name);
}

Value Context::ImportVariable(const char *name)
{
	Context *c = this;
	Variable *v = (Variable*)Locals.Find(name);

	if(v)
	{
		Error("Cannot import '%s', the name is"
		" already defined at this level", name);
		return v->GetValue();
	}

	while(c)
	{
		Variable *v = c->FindVariable(name);
		if(v) 
		{
			if(v->IsExported())
				Locals.Append(new Variable(name,v));
				
			return v->GetValue();
		}
		c = c->Owner();
	}

	return Error("Cannot import '%s', variable not found", name);
}

Value Context::ExportVariable(const char *name)
{
	Variable *v = (Variable*)Locals.Find(name);

	if(!v) return Error("Cannot export '%s', variable not found", name);

	v->Export();
	return v->GetValue();
}

Function  *Context::FindFunction(const char *name,int arity,Value *arg)
{
	Function *v = 0;
	
	while(v = ((Function*)Functions.Find(name,v)) )
	{
		if(v->ValidArguments(arity,arg))
			return v;
	}
	if(Owner()) return Owner()->FindFunction(name,arity,arg);
	return 0;
}

Function  *Context::FindMethod(const char *name,int arity,Value *arg)
{
	Function *v = 0;
	
	while(v = ((Function*)Methods.Find(name,v)) )
	{
		if(v->ValidArguments(arity,arg))
			return v;
	}

	if(Owner()) return Owner()->FindMethod(name,arity,arg);
	return 0;
}

Function  *Context::FindMethod(const char *name,int arity,Value *arg,CObject* obj)
{
	if(!obj)
		return 0;

	Context* code[1024];
	int n = 0;
	obj->GetInheritance(code,n);

	if(n > 1024)
		Error("Inheritance is too deep");
	
	for(int i = 0; i < n; i++)
	{
		Function* f = code[i]->FindMethod(name,arity,arg);
		if(f) return f;
	}

	return 0;
}

Function  *Context::FindFallback(const char *name,int arity,Value *arg)
{
	Function *v = 0;
	
	while(v = ((Function*)Fallbacks.Find(name,v)) )
	{
		if(v->ValidArguments(arity,arg))
			return v;
	}
	if(Owner()) return Owner()->FindFallback(name,arity,arg);
	return 0;
}

Function  *Context::FindHandler(const char *name,int arity,Value *arg)
{
	const char *handler = name;

	while(name)
	{
		Function *v = 0;
		while(v = (Function*)(Handlers.Find(name,v)) )
		{
			if(v->ValidArguments(arity,arg))
				return v;
		}
		name = FallBackHandler(name);
	}

	if(Owner()) return Owner()->FindHandler(handler,arity,arg);

	return 0;
}

void Context::Store(const char *name,Value x,int arity)
{
	Variable *v = FindVariable(name);
	if(v) {
		v->SetValue(x,arity,GetParameters(arity));
		while(arity--) Pop();
	}
	else {
		if(arity == 0)
			Locals.Append(new Variable(name,x));
		else
			Error("Variable not found: %s",name);
	}

}

Value& Context::Fetch(const char *name)
{
	Variable *v = FindVariable(name);

	if(v) return v->GetValue();

	char buf[80];
	sprintf(buf,"Variable not found: %s",name);
	return Error(buf);
}

void Context::RuntimeError(const char *msg,int line)
{
	const char* mname = Current->Name();
	if(*msg == '-')
		marslog(LOG_EROR|LOG_PERR,"Line %d in '%s': %s",line,mname,msg+1);
	else
		marslog(LOG_EROR,"Line %d in '%s': %s",line,mname,msg);
}


Value& Context::Error(const char *msg,...)
{
	va_list list;
	char buf[1024];
	va_start(list,msg);
	vsprintf(buf, msg, list);
	va_end(list);

	if(Owner()) 
		Owner()->Error("%s",buf);
	else 
	{
		if(msg)  {
			int line = Instruction?Instruction->line:0;
			RuntimeError(buf,line);
		}
	}
	SetError(1);

	static Value e;

	object = 0;

	e = Value(1,buf);
	return e;
}

Context::Context(const char *name,int o) : Node(name), oo(o)
{
	error   = 0;
	argc    = 0; 
	argv    = 0; 
	argn    = 0; 
	Macro   = 0;
	inited  = 0;
	object  = 0;

	if(oo) AddGlobal(new Variable(".super",Value(new CList(0))));
}

Context::~Context()
{
	Methods.Empty();
	Functions.Empty();
	Handlers.Empty();
	Steps.Empty();
	Locals.Empty();
	Globals.Empty();
	Contexts.Empty();
}

Value Context::NextParameter(void)
{
    if(argn >= argc)
		return Error("Function was called with too few parameters");
	else
		return argv[argn++];

}

void Context::AddParameter(const char* name)
{
	if(oo)
	{
		Variable *v = FindGlobal(name);
		if(!v || v->Owner() != this)
			AddGlobal(new Variable(name,Value(0.0)));
	}
	else
		AddLocal(new Variable(name,Value(0.0)));
	Store(name,NextParameter(),0);
}

CList  *Context::GetGlobals()
{
	Variable *v = (Variable*)Globals.Head();

	int n = 0;
	while(v)
	{
		n++;
		v = (Variable*)v->Next();
	}

	CList* c = new CList(n);

	n = 0;
	v = (Variable*)Globals.Head();
	while(v)
	{
		(*c)[n] = v->GetValue();
		n++;
		v = (Variable*)v->Next();
	}
	return c;
}

void Context::SetGlobals(CList* c)
{
	Variable *v = (Variable*)Globals.Head();

	int n = 0;
	while(v)
	{
		v->SetValue((*c)[n++],0,0);
		v = (Variable*)v->Next();
	}
}

CObject* Context::GetObject()
{
	if(object) return object;
	if(Owner()) return Owner()->GetObject();
	return 0;
}
