#include "ascript/Object.h"
#include "ascript/Object_Custom.h"
#include "ascript/Object_Function.h"
#include "ascript/Expr.h"
#include "ascript/Module.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::BuildContent(Environment &env, Signal sig, const Value &valueSelf,
			const Expr_Block *pExprBlock, const SymbolSet *pSymbolsAssignable)
{
	Environment envLocal(&env, ENVTYPE_Local);
	envLocal.AssignValue(AScript_Symbol(self), valueSelf, false);
	foreach_const (ExprList, ppExpr, pExprBlock->GetExprList()) {
		const Expr *pExpr = *ppExpr;
		if (pExpr->IsAssign()) {
			const Expr_Assign *pExprAssign =
								dynamic_cast<const Expr_Assign *>(pExpr);
			pExprAssign->Exec(envLocal, sig, *this, pSymbolsAssignable);
		} else {
			pExpr->Exec(envLocal, sig);
		}
		if (sig.IsSignalled()) return false;
	}
	return true;
}

Iterator *ObjectBase::CreateIterator(Signal sig)
{
	sig.SetError(ERR_ValueError, "object cannot generate iterator");
	return NULL;
}

Value ObjectBase::DoCall(Environment &env, Signal sig, Args &args)
{
	sig.SetError(ERR_TypeError, "object is not callable");
	return Value::Null;
}

bool ObjectBase::DoPropDir(Signal sig, SymbolSet &symbols)
{
	foreach_const (ValueMap, iter, GetTopFrame().GetValueMap()) {
		symbols.insert(iter->first);
	}
	return true;
}

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

Object::Object(Class *pClass) :
			ObjectBase(pClass, ENVTYPE_Instance), _pClass(pClass->IncRef())
{
}

Object::~Object()
{
	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, false); // reference to self
			Args args(ValueList::Null, valueSelf);
			pFunc->Eval(*this, sig, args);
		}
	}
	Object::Delete(_pClass);
}

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

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

bool Object::IsInstanceOf(ValueType valType) const
{
	for (const Class *pClass = _pClass; pClass != NULL;
									pClass = pClass->GetClassSuper()) {
		if (pClass->GetValueType() == valType) return true;
	}
	return false;
}

int Object::Compare(const Object *pObj) const
{
	const char *p1 = reinterpret_cast<const char *>(this);
	const char *p2 = reinterpret_cast<const char *>(pObj);
	return (p1 < p2)? -1 : (p1 > p2)? +1 : 0;
}

Value Object::EmptyIndexGet(Signal sig)
{
	sig.SetError(ERR_IndexError, "index is not specified");
	return Value::Null;
}

void Object::EmptyIndexSet(Signal sig, const Value &value)
{
	sig.SetError(ERR_IndexError, "index is not specified");
}

Value Object::IndexGet(Signal sig, const Value &valueIdx)
{
	const Function *pFunc = LookupFunction(AScript_Symbol(__getitem__), true);
	if (pFunc == NULL) {
		sig.SetError(ERR_ValueError, "index access is not supported");
		return Value::Null;
	}
	Value valueSelf(this, false); // reference to self
	ValueList valListArg;
	valListArg.reserve(1);
	valListArg.push_back(valueIdx);
	Args args(valListArg, valueSelf);
	return pFunc->Eval(*this, sig, args);
}

void Object::IndexSet(Signal sig, const Value &valueIdx, const Value &value)
{
	const Function *pFunc = LookupFunction(AScript_Symbol(__setitem__), true);
	if (pFunc == NULL) {
		sig.SetError(ERR_ValueError, "index access is not supported");
		return;
	}
	Value valueSelf(this, false); // reference to self
	ValueList valListArg(valueIdx, value);
	Args args(valListArg, valueSelf);
	pFunc->Eval(*this, sig, args);
}

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, false); // reference to self
	evaluatedFlag = true;
	Args args(valListArg, valueSelf, NULL, false, &pFuncSuccRequester);
	return pFunc->Eval(*this, sig, args);
}

bool Object::DoPropDir(Signal sig, SymbolSet &symbols)
{
	foreach_const (FrameList, ppFrame, GetFrameList()) {
		const Frame *pFrame = *ppFrame;
		if (pFrame->IsType(ENVTYPE_Class) || pFrame->IsType(ENVTYPE_Instance)) {
			foreach_const (ValueMap, iter, pFrame->GetValueMap()) {
				symbols.insert(iter->first);
			}
		}
	}
	return true;
}

Value Object::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	const Function *pFunc = LookupFunction(AScript_Symbol(__getprop__), true);
	if (pFunc == NULL) return Value::Null;
	evaluatedFlag = true;
	Value valueSelf(this, false); // reference to self
	ValueList valListArg;
	valListArg.reserve(1);
	valListArg.push_back(Value(pSymbol));
	Args args(valListArg, valueSelf);
	return pFunc->Eval(*this, sig, args);
}

Value Object::DoPropSet(Signal sig, const Symbol *pSymbol,
										const Value &value, bool &evaluatedFlag)
{
	const Function *pFunc = LookupFunction(AScript_Symbol(__setprop__), true);
	if (pFunc == NULL) return Value::Null;
	Value valueSelf(this, false); // reference to self
	ValueList valListArg(Value(pSymbol), value);
	Args args(valListArg, valueSelf);
	Value result = pFunc->Eval(*this, sig, args);
	evaluatedFlag = result.GetBoolean();
	return value;
}

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

//-----------------------------------------------------------------------------
// AScript interfaces for Object
//-----------------------------------------------------------------------------
// object#istype(type:expr):map
AScript_DeclareMethodPrimitive(Object, istype)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "type", VTYPE_Expr);
}

AScript_ImplementMethod(Object, istype)
{
	SymbolList symbolList;
	if (!args.GetExpr(0)->GetChainedSymbolList(symbolList)) {
		sig.SetError(ERR_TypeError, "invalid type name");
		return Value::Null;
	}
	const ValueTypeInfo *pValueTypeInfo = env.LookupValueType(symbolList);
	if (pValueTypeInfo == NULL) {
		sig.SetError(ERR_ValueError, "invalid type name");
		return Value::Null;
	}
	ValueType valType = args.GetSelf().GetType();
	ValueType valTypeCmp = pValueTypeInfo->GetValueType();
	if (valType == VTYPE_Number && valTypeCmp == VTYPE_Complex) return Value(true);
	return Value(valType == valTypeCmp);
}

// num = object#tonumber():[strict,raise,zero,nil]
AScript_DeclareMethodPrimitive(Object, tonumber)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareAttr(AScript_Symbol(strict));
	DeclareAttr(AScript_Symbol(raise));
	DeclareAttr(AScript_Symbol(zero));
	DeclareAttr(AScript_Symbol(nil));
}

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

// num = object#tostring()
AScript_DeclareMethodPrimitive(Object, tostring)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

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

// object#getprop!(symbol:symbol, default?:nomap):map
AScript_DeclareMethod(Object, getprop_X)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "symbol", VTYPE_Symbol);
	DeclareArg(env, "default", VTYPE_Any, OCCUR_ZeroOrOnce, false, true);
}

AScript_ImplementMethod(Object, getprop_X)
{
	ObjectBase *pSelf = args.GetSelfObjBase();
	if (args.IsDefined(1)) {
		Value value = args.GetValue(1);
		return pSelf->PropGet(sig, args.GetSymbol(0), &value);
	} else {
		return pSelf->PropGet(sig, args.GetSymbol(0));
	}
}

// object#setprop!(symbol:symbol, value):map
AScript_DeclareMethod(Object, setprop_X)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "symbol", VTYPE_Symbol);
	DeclareArg(env, "value", VTYPE_Any);
}

AScript_ImplementMethod(Object, setprop_X)
{
	ObjectBase *pSelf = args.GetSelfObjBase();
	pSelf->AssignValue(args.GetSymbol(0), args.GetValue(1), false);
	return Value::Null;
}

// object#call!(symbol:symbol, args*, dict%):map {block?}
class AScript_Method(Object, call_X) : public Function {
public:
	AScript_Method(Object, call_X)(Environment &env, const char *name);
	virtual Value EvalExpr(Environment &env, Signal sig, Args &args) const;
	virtual Value DoEval(Environment &env, Signal sig, Args &args) const;
};

AScript_Method(Object, call_X)::AScript_Method(Object, call_X)(Environment &env, const char *name) :
							Function(env, Symbol::Add(name), FUNCTYPE_Instance)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "symbol", VTYPE_Symbol);
	DeclareArg(env, "args", VTYPE_Any, OCCUR_ZeroOrOnce);
	DeclareDictArg("dict");
	DeclareBlock(OCCUR_ZeroOrOnce);
}

Value AScript_Method(Object, call_X)::EvalExpr(Environment &env, Signal sig, Args &args) const
{
	ObjectBase *pSelf = args.GetSelfObjBase();
	ExprOwner exprOwnerArgs(args.GetExprListArg());
	const Expr *pExprArg = exprOwnerArgs.front();
	size_t nElems = 0;
	ValueList valListArg;
	if (!pExprArg->ExecInArg(env, sig, valListArg, nElems, false)) return Value::Null;
	if (valListArg.size() != 1) {
		sig.SetError(ERR_ValueError, "invalid argument for call!()");
		return Value::Null;
	}
	const Value &value = valListArg.front();
	if (!value.IsSymbol()) {
		sig.SetError(ERR_ValueError, "invalid argument for call!()");
		return Value::Null;
	}
	const Symbol *pSymbol = value.GetSymbol();
	Value valueFunc;
	const Value *pValue = pSelf->LookupValue(pSymbol, true);
	if (pValue == NULL) {
		valueFunc = pSelf->PropGet(sig, pSymbol);
		if (sig.IsSignalled()) return Value::Null;
	} else {
		valueFunc = *pValue;
	}
	if (!valueFunc.IsFunction()) {
		sig.SetError(ERR_ValueError, "invalid argument for call!()");
		return Value::Null;
	}
	const Function *pFunc = valueFunc.GetFunction();
	exprOwnerArgs.erase(exprOwnerArgs.begin());
	Args argsSub(args, exprOwnerArgs);
	return pFunc->EvalExpr(env, sig, argsSub);
}

Value AScript_Method(Object, call_X)::DoEval(Environment &env, Signal sig, Args &args) const
{
	return Value::Null;
}

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

AScript_ImplementMethod(Object, clone)
{
	Object *pObj = args.GetSelfObj()->Clone();
	if (pObj == NULL) {
		sig.SetError(ERR_ValueError, "failed to create a clone object");
		return Value::Null;
	}
	return Value(pObj);
}

// num = object#__iter__()
AScript_DeclareMethod(Object, __iter__)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Object, __iter__)
{
	Iterator *pIterator = args.GetSelf().CreateIterator(sig);
	if (sig.IsSignalled()) return Value::Null;
	return Value(env, pIterator);
}

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

Class::Class(Environment *pEnvOuter) :
	ObjectBase(pEnvOuter, ENVTYPE_Class), _pSymbol(AScript_Symbol(_anonymous_)),
	_pClassSuper(pEnvOuter->IsClass()? dynamic_cast<Class *>(pEnvOuter) : NULL),
	_pConstructor(NULL), _valType(VTYPE_Nil)
{
	// 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.
}

Class::~Class()
{
	Function::Delete(_pConstructor);
}

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

bool Class::CastFrom(Environment &env, Signal sig, Value &value)
{
	return false;
}

bool Class::CastTo(Environment &env, Signal sig, Value &value, ValueType valType)
{
	return false;
}

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

bool Class::DoPropDir(Signal sig, SymbolSet &symbols)
{
	foreach_const (FrameList, ppFrame, GetFrameList()) {
		const Frame *pFrame = *ppFrame;
		if (pFrame->IsType(ENVTYPE_Class) || pFrame->IsType(ENVTYPE_Instance)) {
			foreach_const (ValueMap, iter, pFrame->GetValueMap()) {
				symbols.insert(iter->first);
			}
		}
	}
	return true;
}

// assignment
void Class::Prepare()
{
	AScript_AssignMethod(Object, istype);		// primitive method
	AScript_AssignMethod(Object, tonumber);		// primitive method
	AScript_AssignMethod(Object, tostring);		// primitive method
	AScript_AssignMethodEx(Object, setprop_X,	"setprop!");
	AScript_AssignMethodEx(Object, getprop_X,	"getprop!");
	AScript_AssignMethodEx(Object, call_X,		"call!");
	AScript_AssignMethod(Object, clone);
	AScript_AssignMethod(Object, __iter__);
}

//-----------------------------------------------------------------------------
// Class_Nil
//-----------------------------------------------------------------------------
Class_Nil::Class_Nil(Environment *pEnvOuter) : Class(pEnvOuter)
{
}

bool Class_Nil::CastFrom(Environment &env, Signal sig, Value &value)
{
	return false;
}

//-----------------------------------------------------------------------------
// Class_Symbol
//-----------------------------------------------------------------------------
Class_Symbol::Class_Symbol(Environment *pEnvOuter) : Class(pEnvOuter)
{
}

bool Class_Symbol::CastFrom(Environment &env, Signal sig, Value &value)
{
	if (value.IsString()) {
		value = Value(Symbol::Add(value.GetString()));
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
// Class_Boolean
//-----------------------------------------------------------------------------
Class_Boolean::Class_Boolean(Environment *pEnvOuter) : Class(pEnvOuter)
{
}

bool Class_Boolean::CastFrom(Environment &env, Signal sig, Value &value)
{
	if (value.IsList()) {
		return true;	// ?????
	} else {
		value = Value(value.GetBoolean());
		return true;
	}
}

//-----------------------------------------------------------------------------
// Class_Number
//-----------------------------------------------------------------------------
Class_Number::Class_Number(Environment *pEnvOuter) : Class(pEnvOuter)
{
}

bool Class_Number::CastFrom(Environment &env, Signal sig, Value &value)
{
	bool allowPartFlag = false;
	bool successFlag;
	Number num = value.ToNumber(allowPartFlag, successFlag);
	if (successFlag) {
		value = Value(num);
		return true;
	} else {
		sig.SetError(ERR_ValueError, "failed to convert to a number");
		return false;
	}
}

//-----------------------------------------------------------------------------
// Class_Complex
//-----------------------------------------------------------------------------
Class_Complex::Class_Complex(Environment *pEnvOuter) : Class(pEnvOuter)
{
}

bool Class_Complex::CastFrom(Environment &env, Signal sig, Value &value)
{
	if (value.IsNumber()) {		// cast number to complex
		return true;
	}
	return false;
}

}
