//
// Object_Function
//

#include "ascript/Object_Function.h"
#include "ascript/Expr.h"
#include "ascript/Module.h"

namespace AScript {

//-----------------------------------------------------------------------------
// Object_Function
//-----------------------------------------------------------------------------
bool Object_Function::IsFunction() const { return true; }

Object_Function::~Object_Function()
{
	Function::Delete(_pFunc);
}

Value Object_Function::DoCall(Environment &env, Signal sig, Args &args)
{
	if (args.GetSelf().IsInvalid() ||
					(args.GetSelf().IsModule() && _valueSelf.IsValid())) {
		args.SetSelf(_valueSelf);
	}
	return _pFunc->EvalExpr(env, sig, args);
}

Value Object_Function::Eval(Environment &env, Signal sig, ValueList &valListArg) const
{
	_pFunc->GetDeclOwner().Compensate(env, sig, valListArg);
	if (sig.IsSignalled()) return Value::Null;
	Args args(valListArg, _valueSelf);
	return _pFunc->Eval(env, sig, args);
}

Object *Object_Function::Clone() const
{
	return new Object_Function(*this);
}

String Object_Function::GetFullName(Signal sig)
{
	String str = MakePrefix(sig);
	if (sig.IsSignalled()) return String("");
	str += _pFunc->GetName();
	return str;
}

String Object_Function::ToString(Signal sig, bool exprFlag)
{
	String str = MakePrefix(sig);
	if (sig.IsSignalled()) return String("");
	str += _pFunc->ToString();
	return str;
}

String Object_Function::MakePrefix(Signal sig) const
{
	String str;
	if (_valueSelf.IsInvalid()) return str;
	const ObjectBase *pObj = _valueSelf.ExtractObject(sig);
	if (sig.IsSignalled()) return str;
	if (pObj->IsModule()) {
		str += dynamic_cast<const Module *>(pObj)->GetName();
		str += ".";
	} else if (pObj->IsClass()) {
		str += dynamic_cast<const Class *>(pObj)->GetName();
		str += (_pFunc->GetType() == FUNCTYPE_Instance)? "#" : ".";
	} else if (pObj->IsObject()) {
		str += dynamic_cast<const Object *>(pObj)->GetClass()->GetName();
		str += "#";
	}
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Function
//-----------------------------------------------------------------------------
// function#name()
AScript_DeclareMethod(Function, name)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Returns a name of the function object as a string.");
}

AScript_ImplementMethod(Function, name)
{
	Object_Function *pSelf = Object_Function::GetSelfObj(args);
	return Value(env, pSelf->GetName().c_str());
}

// function#fullname()
AScript_DeclareMethod(Function, fullname)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp(
	"Returns a full name of the function object as a string, which contains a module\n"
	"or class name that it belongs to.");
}

AScript_ImplementMethod(Function, fullname)
{
	Object_Function *pSelf = Object_Function::GetSelfObj(args);
	String str = pSelf->GetFullName(sig);
	if (sig.IsSignalled()) return Value::Null;
	return Value(env, str.c_str());
}

// function#setsymbol(symbol:symbol):reduce
AScript_DeclareMethod(Function, setsymbol)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "symbol", VTYPE_Symbol);
	SetHelp("Overwrites a symbol associated with the function.");
}

AScript_ImplementMethod(Function, setsymbol)
{
	Object_Function *pSelf = Object_Function::GetSelfObj(args);
	pSelf->GetFunction()->SetSymbol(args.GetSymbol(0));
	return args.GetSelf();
}

// function#help(help?:string)
AScript_DeclareMethod(Function, help)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "help", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp(
	"If argument help is specified, it sets a help string of the function object.\n"
	"Otherwise, it returns a help string already defined.");
}

AScript_ImplementMethod(Function, help)
{
	Object_Function *pSelf = Object_Function::GetSelfObj(args);
	Function *pFunc = pSelf->GetFunction();
	if (args.IsString(0)) {
		pFunc->SetHelp(args.GetString(0));
		return Value::Null;
	} else {
		return Value(env, pFunc->GetHelp());
	}
}

// function#expr()
AScript_DeclareMethod(Function, expr)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp(
	"Returns an expression object that is associated with the function object.");
}

AScript_ImplementMethod(Function, expr)
{
	Object_Function *pSelf = Object_Function::GetSelfObj(args);
	const Function *pFunc = pSelf->GetFunction();
	if (!pFunc->IsCustom()) {
		sig.SetError(ERR_ValueError, "function '%s' is a built-in function", pFunc->GetName());
		return Value::Null;
	}
	const FunctionCustom *pFuncCustom = dynamic_cast<const FunctionCustom *>(pFunc);
	return Value(env, pFuncCustom->GetExprBody()->IncRef());
}

// function#argsymbols()
AScript_DeclareMethod(Function, argsymbols)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp(
	"Returns a list of symbols declared in the argument list.");
}

AScript_ImplementMethod(Function, argsymbols)
{
	Object_Function *pSelf = Object_Function::GetSelfObj(args);
	const Function *pFunc = pSelf->GetFunction();
	Value result;
	ValueList &valList = result.InitAsList(env);
	foreach_const (DeclarationList, ppDecl, pFunc->GetDeclOwner()) {
		valList.push_back(Value((*ppDecl)->GetSymbol()));
	}
	return result;
}

// Assignment
Class_Function::Class_Function(Environment *pEnvOuter) : Class(pEnvOuter)
{
	// don't assign functions here, because the function objects themselves
	// shall be constructed here!!!!!!! instead, they must be assigned in
	// Prepare() funtion below.
}

void Class_Function::Prepare()
{
	AScript_AssignMethod(Function, name);
	AScript_AssignMethod(Function, fullname);
	AScript_AssignMethod(Function, setsymbol);
	AScript_AssignMethod(Function, help);
	AScript_AssignMethod(Function, expr);
	AScript_AssignMethod(Function, argsymbols);
}

bool Class_Function::CastFrom(Environment &env, Signal sig, Value &value)
{
	if (value.IsExpr()) {
		Function *pFunc = value.GetExpr()->
				ToFunction(env, sig, ValueList::Null, SymbolSet::Null);
		if (sig.IsSignalled()) return false;
		value = Value(env, pFunc, Value::Null);
		return true;
	}
	return false;
}

Object *Class_Function::CreateDescendant(Environment &env, Signal sig, Class *pClass)
{
	ERROREND(env, "this function must not be called");
	return NULL;
}

}
