#include "AScript.h"
#include "Expr.h"

namespace AScript {

//-----------------------------------------------------------------------------
// ObjectBase
//-----------------------------------------------------------------------------
ObjectBase::ObjectBase(Environment *pEnvOuter, EnvType envType) :
							Environment(pEnvOuter, envType), _cntRef(1)
{
}

ObjectBase::~ObjectBase()
{
}

bool ObjectBase::IsFunction() const { return false; }
bool ObjectBase::IsList() const { return false; }
bool ObjectBase::IsDict() const { return false; }
bool ObjectBase::IsFile() const { return false; }
bool ObjectBase::IsExpr() const { return false; }

bool ObjectBase::BuildContent(Environment &env, Signal sig, const Expr_Block *pExprBlock,
											const SymbolSet *pSymbolsAssignable)
{
	Environment envLocal(&env, ENVTYPE_Local);
	foreach_const (ExprList, ppExpr, pExprBlock->GetExprList()) {
		if ((*ppExpr)->IsAssign()) {
			const Expr_Assign *pExprAssign =
								dynamic_cast<const Expr_Assign *>(*ppExpr);
			pExprAssign->Exec(envLocal, sig, *this, pSymbolsAssignable);
		} else {
			(*ppExpr)->Exec(envLocal, sig);
		}
		if (sig.IsSignalled()) return false;
	}
	return true;
}

//-----------------------------------------------------------------------------
// Object
//-----------------------------------------------------------------------------
bool Object::IsObject() const { return true; }

Object::Object(Class *pClass) :
			ObjectBase(pClass, ENVTYPE_Instance), _pClass(pClass->IncRef())
{
	DBG(::_tprintf(_T("%p Object(<%s>)\n"), this,
				(_pClass == NULL)? _T("<class>") : _pClass->GetName()));
}

Object::~Object()
{
	DBG(::_tprintf(_T("%p ~Object(<%s>)\n"), this,
				(_pClass == NULL)? _T("<class>") : _pClass->GetName()));
	if (_pClass->IsCustom()) {
		Class_Custom *pClassCustom = dynamic_cast<Class_Custom *>(_pClass);
		const Function *pFunc =
				pClassCustom->LookupFunction(AScript_Symbol(__del__), false);
		if (pFunc != NULL) {
			Signal sig;
			Value valueSelf(this, VTYPE_Object, false); // reference to self
			pFunc->Eval(*this, sig, Context(SymbolSet::Null, SymbolSet::Null,
									NULL, NULL, valueSelf, ValueList::Null));
			//::printf("%s\n", sig.GetErrString().c_str());
		}
	}
	Object::Delete(_pClass);
}

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

const TCHAR *Object::GetClassName() const
{
	return (_pClass == NULL)? _T("class") : _pClass->GetName();
}

Value Object::GetByIndex(Signal sig, const Value &valueIdx) const
{
	if (!valueIdx.IsSymbol()) {
		sig.SetError(ERR_IndexError, _T("index is must be a symbol"));
		return Value::Null;
	}
	const Symbol *pSymbol = valueIdx.GetSymbol();
	const Value *pValue = LookupValue(pSymbol, false);
	if (pValue == NULL) {
		sig.SetError(ERR_ValueError, _T("object doesn't have a property named '%s'"), pSymbol->GetName());
		return Value::Null;
	}
	return *pValue;
}

void Object::SetByIndex(Signal sig, const Value &valueIdx, const Value &value)
{
	if (!valueIdx.IsSymbol()) {
		sig.SetError(ERR_IndexError, _T("index is must be a symbol"));
		return;
	}
	const Symbol *pSymbol = valueIdx.GetSymbol();
	AssignValue(pSymbol, value, false);
}

Value Object::Call(Environment &env, Signal sig,
								Value &value, ContextExpr &contextExpr)
{
	sig.SetError(ERR_TypeError,
		_T("'%s' is not a callable object"), value.ToString(sig, false).c_str());
	return Value::Null;
}

Value Object::EvalMethod(Signal sig, const Symbol *pSymbol,
							const ValueList &valListArg, bool &evaluatedFlag)
{
	const Function *pFuncSuccRequester = NULL;
	evaluatedFlag = false;
	const Function *pFunc = LookupFunction(pSymbol, true);
	if (pFunc == NULL) return Value::Null;
	Value valueSelf(this, VTYPE_Object, false); // reference to self
	evaluatedFlag = true;
	return pFunc->Eval(*this, sig, Context(SymbolSet::Null, SymbolSet::Null,
							NULL, &pFuncSuccRequester, valueSelf, valListArg));
}

String Object::ToString(Signal sig, bool exprFlag)
{
	bool evaluatedFlag = false;
	Value value = EvalMethod(sig, AScript_Symbol(__str__),
											ValueList::Null, evaluatedFlag);
	if (evaluatedFlag) return value.ToString(sig, false);
	String str;
	str += _T("<object:");
	str += _pClass->GetName();
	str += _T(">");
	return str;
}

// Object#clone()
AScript_DeclareMethod(Object, clone)
{
}

AScript_ImplementMethod(Object, clone)
{
	return Value(context.GetSelfObj()->Clone(), context.GetSelf().GetType());
}

// num = Object#to_number():map:[strict,raise,zero,nil]
AScript_DeclareMethod(Object, to_number)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareAttr(AScript_Symbol(strict));
	DeclareAttr(AScript_Symbol(raise));
	DeclareAttr(AScript_Symbol(zero));
	DeclareAttr(AScript_Symbol(nil));
}

AScript_ImplementMethod(Object, to_number)
{
	bool allowPartFlag = !context.IsSet(AScript_Symbol(strict));
	bool successFlag;
	Number num = context.GetSelf().ToNumber(allowPartFlag, successFlag);
	if (successFlag) {
		return Value(num);
	} else if (context.IsSet(AScript_Symbol(raise))) {
		sig.SetError(ERR_ValueError, _T("failed to convert to number"));
		return Value::Null;
	} else if (context.IsSet(AScript_Symbol(zero))) {
		return Value(0.);
	} else { // context.IsSet(AScript_PrivSymbol(nil)
		return Value::Null;
	}
}

// num = Object#to_string()
AScript_DeclareMethod(Object, to_string)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
}

AScript_ImplementMethod(Object, to_string)
{
	// bug bug bug bug bug
	String str = context.GetSelf().ToString(sig, false);
	if (sig.IsSignalled()) return Value::Null;
	return Value(env, str.c_str());
}

// Object#tap() {block}
AScript_DeclareMethod(Object, tap)
{
	DeclareBlock(OCCUR_Once);
}

AScript_ImplementMethod(Object, tap)
{
	const Function *pFuncBlock = GetBlockFunction(env, sig, context);
	if (pFuncBlock == NULL) return Value::Null;
	pFuncBlock->Eval(env, sig, Context(ValueList(context.GetSelf())));
	if (sig.IsSignalled()) return Value::Null;
	return context.GetSelf();
}

//-----------------------------------------------------------------------------
// Class
//-----------------------------------------------------------------------------
bool Class::IsClass() const { return true; }
bool Class::IsCustom() const { return false; }

Class::Class(Environment *pEnvOuter, const Symbol *pSymbol) :
	ObjectBase(pEnvOuter, ENVTYPE_Class), _pSymbol(pSymbol),
	_pClassSuper(pEnvOuter->IsClass()? dynamic_cast<Class *>(pEnvOuter) : NULL)
{
	// don't assign functions here, because the function objects themselves
	// shall be constructed in Class_Function that is to be created after
	// this class!!!!!! instead, they must be assigned in Prepare() funtion below.
}

void Class::Prepare()
{
	AScript_AssignMethod(Object, clone);
	AScript_AssignMethod(Object, to_number);
	//AScript_AssignMethod(Object, to_string);
	AScript_AssignMethod(Object, tap);
}

Class::~Class()
{
}

Object *Class::CreateDescendant(Environment &env, Signal sig, Class *pClass)
{
	return new Object((pClass == NULL)? this : pClass);
}

String Class::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += _T("<class:");
	str += GetName();
	str += _T(">");
	return str;
}

// Object()
Class::Constructor::Constructor(Environment &env, const TCHAR *name) :
							ConstructorBase(name, env.GetClass_Object())
{
	DeclareArg(env, _T("assigns"), VTYPE_Quote, false, OCCUR_ZeroOrMore);
}

Value Class::Constructor::DoEval(Environment &env, Signal sig, Context &context) const
{
	const ValueList &valListArg = context.GetArgs();
	Value result;
	Object *pObj = result.InitAsObject(env);
	foreach_const (ValueList, pValue, valListArg[0].GetList()) {
		ASSUME(env, pValue->IsExpr());
		const Expr *pExpr = pValue->GetExpr();
		if (!pExpr->IsAssign()) {
			sig.SetError(ERR_SyntaxError, _T("assign expression is expected"));
			return Value::Null;
		}
		const Expr_Assign *pExprAssign = dynamic_cast<const Expr_Assign *>(pExpr);
		pExprAssign->Exec(env, sig, *pObj, NULL);
		if (sig.IsSignalled()) return Value::Null;
	}
	return result;
}

}
