//
// Object_Environment
//

#include "ascript/Object_Environment.h"
#include "ascript/Expr.h"

namespace AScript {

//-----------------------------------------------------------------------------
// Object_Environment
//-----------------------------------------------------------------------------
Object_Environment::Object_Environment(const Object_Environment &obj) :
													Object(obj), _env(obj._env)
{
}

Object_Environment::~Object_Environment()
{
}

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

Value Object_Environment::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	const Value *pValue = GetEnv().LookupValue(pSymbol, true);
	if (pValue == NULL) return Value::Null;
	evaluatedFlag = true;
	return *pValue;
}

Value Object_Environment::DoPropSet(Signal sig, const Symbol *pSymbol,
										const Value &value, bool &evaluatedFlag)
{
	GetEnv().AssignValue(pSymbol, value, true);
	evaluatedFlag = true;
	return value;
}

String Object_Environment::ToString(Signal sig, bool exprFlag)
{
	String str;
	EnvType envType = _env.GetTopFrame().GetEnvType();
	str += "<environment:";
	str += GetEnvTypeName(envType);
	str += ">";
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Environment
//-----------------------------------------------------------------------------
// environment#getprop!(symbol:symbol):map
AScript_DeclareMethodAlias(Environment, getprop_X, "getprop!")
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "symbol", VTYPE_Symbol);
}

AScript_ImplementMethod(Environment, getprop_X)
{
	Object_Environment *pSelf = Object_Environment::GetSelfObj(args);
	const Symbol *pSymbol = args.GetSymbol(0);
	const Value *pValue = pSelf->GetEnv().LookupValue(pSymbol, false);
	if (pValue == NULL) {
		sig.SetError(ERR_ValueError,
			"environment doesn't have a property named '%s'", pSymbol->GetName());
		return Value::Null;
	}
	return *pValue;
}

// environment#setprop!(symbol:symbol, value):map
AScript_DeclareMethodAlias(Environment, setprop_X, "setprop!")
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "symbol", VTYPE_Symbol);
	DeclareArg(env, "value", VTYPE_Any);
}

AScript_ImplementMethod(Environment, setprop_X)
{
	Object_Environment *pSelf = Object_Environment::GetSelfObj(args);
	pSelf->GetEnv().AssignValue(args.GetSymbol(0), args.GetValue(1), false);
	return Value::Null;
}

// environment#eval(expr:expr):map
AScript_DeclareMethod(Environment, eval)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "expr", VTYPE_Expr);
	SetHelp("Evaluates the expr instance in the environment and returns its result.");
}

AScript_ImplementMethod(Environment, eval)
{
	Object_Environment *pSelf = Object_Environment::GetSelfObj(args);
	return args.GetExpr(0)->Exec(pSelf->GetEnv(), sig);
}

// environment#lookup(symbol:symbol, escalate:boolean => true):map
AScript_DeclareMethod(Environment, lookup)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "symbol", VTYPE_Symbol);
	DeclareArg(env, "escalate", VTYPE_Boolean,
						OCCUR_Once, false, false, new Expr_Value(true));
	SetHelp(
	"Looks up a specified symbol in the environment and returns the associated value.\n"
	"In default, if the symbol is not defined in the environment, it will be searched\n"
	"in environments outside of the current one. Set escalate flag to false\n"
	"in order to disable such an escalation behaviour.\n"
	"Returns false when the symbol could not be found.");
}

AScript_ImplementMethod(Environment, lookup)
{
	Object_Environment *pSelf = Object_Environment::GetSelfObj(args);
	const Value *pValue = pSelf->GetEnv().LookupValue(
								args.GetSymbol(0), args.GetBoolean(1));
	if (pValue == NULL) return Value::Null;
	return *pValue;
}

// assignment
Class_Environment::Class_Environment(Environment *pEnvOuter) : Class(pEnvOuter)
{
	AScript_AssignMethod(Environment, getprop_X);
	AScript_AssignMethod(Environment, setprop_X);
	AScript_AssignMethod(Environment, eval);
	AScript_AssignMethod(Environment, lookup);
}

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

}
