//
// Object_Function
//

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

namespace AScript {

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

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

Value Object_Function::Call(Environment &env, Signal sig,
									Value &value, ContextExpr &contextExpr)
{
	return _pFunc->EvalExpr(env, sig, contextExpr);
}

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

String Object_Function::ToString(Signal sig, bool exprFlag)
{
	return _pFunc->ToString(sig);
}

// func = Function(`args*) {block}
Object_Function::Constructor::Constructor(Environment &env, const TCHAR *name) :
							ConstructorBase(name, env.GetClass_Function())
{
	DeclareArg(env, _T("args"), VTYPE_Quote, false, OCCUR_ZeroOrMore);
	DeclareBlock(OCCUR_Once);
}

Value Object_Function::Constructor::DoEval(Environment &env, Signal sig, Context &context) const
{
	const Expr *pExprBlock = context.GetBlock();
	ExprList exprArgs;
	foreach_const (ValueList, pValue, context.GetList(0)) {
		exprArgs.push_back(const_cast<Expr *>(pValue->GetExpr()));
	}
	if (exprArgs.empty()) {
		ExprVisitorEx visitor(exprArgs);
		pExprBlock->Accept(visitor);
	}
	FunctionCustom *pFunc = new FunctionCustom(env, AScript_Symbol(_anonymous_),
							pExprBlock->IncRef(), NULL, FUNCTYPE_Function);
	if (!pFunc->CustomDeclare(env, sig, SymbolSet::Null,
			ContextExpr(context.GetAttrs(), SymbolSet::Null,
									NULL, NULL, Value::Null, exprArgs))) {
		Function::Delete(pFunc);
		return Value::Null;
	}
	Value result;
	result.InitAsFunction(env, pFunc);
	return result;
}

bool Object_Function::ExprVisitorEx::Visit(const Expr *pExpr)
{
	if (pExpr->IsFunction()) {
		// avoid searching in a simple lambda inside
		const Expr *pExprCar =
					dynamic_cast<const Expr_Function *>(pExpr)->GetExprCar();
		if (pExprCar->IsSymbol()) {
			const Symbol *pSymbol =
					dynamic_cast<const Expr_Symbol *>(pExprCar)->GetSymbol();
			if (::_tcscmp(pSymbol->GetName(), _T("@")) == 0) return false;
		}
		
	} else if (pExpr->IsSymbol()) {
		const Symbol *pSymbol =
						dynamic_cast<const Expr_Symbol *>(pExpr)->GetSymbol();
		if (pSymbol->GetName()[0] == _T('$') &&
								_symbolSet.find(pSymbol) == _symbolSet.end()) {
			_exprList.push_back(const_cast<Expr *>(pExpr));
			_symbolSet.insert(pSymbol);
		}
	}
	return true;
}

// Function#expr()
AScript_DeclareMethod(Function, expr)
{
}

AScript_ImplementMethod(Function, expr)
{
	Object *pObj = context.GetSelfObj();
	ASSUME(env, pObj != NULL && pObj->IsFunction());
	Value value;
	const Function *pFunc = dynamic_cast<Object_Function *>(pObj)->GetFunction();
	if (!pFunc->IsCustom()) {
		sig.SetError(ERR_ValueError, _T("function '%s' is a built-in function"), pFunc->GetName());
		return Value::Null;
	}
	const FunctionCustom *pFuncCustom = dynamic_cast<const FunctionCustom *>(pFunc);
	value.InitAsExpr(env, pFuncCustom->GetExprBody()->IncRef());
	return value;
}

// Assignment
Class_Function::Class_Function(Class *pClassSuper, const Symbol *pSymbol) :
												Class(pClassSuper, pSymbol)
{
	// 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, expr);
}

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

}
