//
// Object_List
//

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

namespace AScript {

// Implementation of Object_Dict
bool Object_List::IsList() const { return true; }

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

Value Object_List::GetByIndex(Signal sig, const Value &valueIdx) const
{
	if (valueIdx.IsNumber()) {
		size_t idx = static_cast<size_t>(valueIdx.GetNumber());
		if (idx < GetList().size()) {
			return GetList()[idx];
		} else {
			sig.SetError(ERR_IndexError, _T("index is out of range"));
		}
	} else {
		sig.SetError(ERR_IndexError, _T("index must be a number for List"));
	}
	return Value::Null;
}

void Object_List::SetByIndex(Signal sig, const Value &valueIdx, const Value &value)
{
	if (valueIdx.IsNumber()) {
		size_t idx = static_cast<size_t>(valueIdx.GetNumber());
		if (idx < GetList().size()) {
			GetList()[idx] = value;
		} else {
			sig.SetError(ERR_IndexError, _T("index is out of range"));
		}
	} else {
		sig.SetError(ERR_IndexError, _T("index must be a number for List"));
	}
}

String Object_List::ToString(Signal sig, bool exprFlag)
{
	String str;
	const ValueList &valList = GetList();
	str += _T("[");
	foreach_const (ValueList, pValue, valList) {
		if (pValue != valList.begin()) str += _T(", ");
		str += pValue->ToString(sig);
	}
	str += _T("]");
	return str;
}

void Object_List::DoPadding(Signal sig, ValueList &valList, const Value &value)
{
	if (sig.IsSignalled()) return;	// detect stack overflow
	int sizeMax = -1;
	foreach (ValueList, pValue, valList) {
		if (pValue->IsList()) {
			int size = static_cast<int>(pValue->GetList().size());
			if (sizeMax < size) sizeMax = size;
		}
	}
	if (sizeMax < 0) return;
	foreach (ValueList, pValue, valList) {
		if (pValue->IsList()) {
			ValueList &valListElem = pValue->GetList();
			int sizeToPadding = sizeMax - static_cast<int>(valListElem.size());
			while (sizeToPadding-- > 0) {
				valListElem.push_back(value);
			}
			DoPadding(sig, valListElem, value);
		}
	}
}

void Object_List::ValueVisitorEx::Visit(Signal sig, const Value &value)
{
	ASSUME(_env, value.IsNumber());
	size_t idx = static_cast<size_t>(value.GetNumber());
	if (std::find(_indexList.begin(), _indexList.end(), idx) != _indexList.end()) {
		// nothing to do
	} else if (idx < _valList.size()) {
		_indexList.push_back(idx);
	} else {
		sig.SetError(ERR_IndexError, _T("index is out of range"));
	}
}

// Constructor: List(func?:Function) {block?}
Object_List::Constructor::Constructor(Environment &env, const TCHAR *name) :
							ConstructorBase(name, env.GetClass_List())
{
	DeclareArg(env, _T("func"), VTYPE_Function, false, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

Value Object_List::Constructor::DoEval(Environment &env, Signal sig, Context &context) const
{
	const Expr_Block *pExprBlock = context.GetBlock();
	const Value &valueFunc = context.GetValue(0);
	Value result;
	if (pExprBlock == NULL) {
		result.InitAsList(env);
	} else if (valueFunc.IsFunction()) {
		const Function *pFunc = valueFunc.GetFunction();
		size_t cntArgs = pFunc->GetDeclList().size();
		if (cntArgs == 0) {
			sig.SetError(ERR_TypeError, _T("function '%s' needs no argument"), pFunc->GetName());
			return Value::Null;
		}
		Environment envLister(&env, ENVTYPE_Lister);
		Value valueRaw = pExprBlock->GetExprList().ExecForList(envLister, sig, false);
		if (sig.IsSignalled() || !valueRaw.IsList()) return Value::Null;
		ValueList &valList = result.InitAsList(env);
		foreach_const (ValueList, pValue, valueRaw.GetList()) {
			if (!pValue->IsList()) {
				sig.SetError(ERR_SyntaxError, _T("invalid format in list initializer"));
				return Value::Null;
			}
			Value valueElem = pFunc->Eval(env, sig, Context(pValue->GetList()));
			valList.push_back(valueElem);
		}
	} else {
		Environment envLister(&env, ENVTYPE_Lister);
		result = pExprBlock->GetExprList().ExecForList(envLister, sig, false);
	}
	return result;
}

// List#count(value)
AScript_DeclareMethod(List, count)
{
	DeclareArg(env, _T("value"), VTYPE_AnyType);
}

AScript_ImplementMethod(List, count)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	size_t cnt = 0;
	const Value &value = context.GetValue(0);
	if (value.IsFunction()) {
		const Function *pFunc = value.GetFunction();
		foreach_const (ValueList, pValue, pSelf->GetList()) {
			Value valueFlag = pFunc->Eval(env, sig,
						Context(context, ValueList(*pValue), Value::Null));
			if (sig.IsSignalled()) return Value::Null;
			if (valueFlag.GetBoolean()) cnt++;
		}
	} else {
		foreach_const (ValueList, pValue, pSelf->GetList()) {
			if (Value::Compare(*pValue, value) == 0) cnt++;
		}
	}
	return Value(static_cast<Number>(cnt));
}

// List#get(index):map
AScript_DeclareMethod(List, get)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("index"), VTYPE_AnyType);
}

AScript_ImplementMethod(List, get)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	return pSelf->GetByIndex(sig, context.GetValue(0));
}

// List#first()
AScript_DeclareMethod(List, first)
{
}

AScript_ImplementMethod(List, first)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	if (valList.empty()) {
		sig.SetError(ERR_ValueError, _T("list is empty"));
		return Value::Null;
	}
	return valList.front();
}

// List#last()
AScript_DeclareMethod(List, last)
{
}

AScript_ImplementMethod(List, last)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	if (valList.empty()) {
		sig.SetError(ERR_ValueError, _T("list is empty"));
		return Value::Null;
	}
	return valList.back();
}

// List#join(sep:string)
AScript_DeclareMethod(List, join)
{
	DeclareArg(env, _T("sep"), VTYPE_String, false, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(List, join)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	return Value(env, Join(valList,
			context.IsString(0)? context.GetString(0) : _T("")).c_str());
}

// List#each() {block}
AScript_DeclareMethod(List, each)
{
	DeclareBlock(OCCUR_Once);
}

AScript_ImplementMethod(List, each)
{
	Value result;
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	const Expr_Block *pExprBlock = context.GetBlock();
	const Function *pFuncBlock = GetBlockFunction(env, sig, context);
	if (pFuncBlock == NULL) return Value::Null;
	Environment envBlock(&env, ENVTYPE_Block);
	ResultListComposer resultListComposer(env, context, result);
	int idx = 0;
	foreach (ValueList, pValue, valList) {
		Value value = pFuncBlock->Eval(envBlock, sig,
				Context(ValueList(*pValue, Value(static_cast<Number>(idx)))));
		idx++;
		AScript_BlockSignalHandlerInLoop(sig, value, Value::Null)
		resultListComposer.Store(value);
	}
	return result;
}

// List#clear!()
AScript_DeclareMethod(List, clear_X)
{
}

AScript_ImplementMethod(List, clear_X)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	pSelf->GetList().clear();
	return context.GetSelf();
}

// List#shuffle!()
AScript_DeclareMethod(List, shuffle_X)
{
}

AScript_ImplementMethod(List, shuffle_X)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	std::random_shuffle(valList.begin(), valList.end(), RandomNumberGenerator());
	return context.GetSelf();
}

// List#add!(elem+)
AScript_DeclareMethod(List, add_X)
{
	DeclareArg(env, _T("elem"), VTYPE_AnyType, false, OCCUR_OnceOrMore);
}

AScript_ImplementMethod(List, add_X)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	foreach_const (ValueList, pValue, context.GetList(0)) {
		valList.push_back(*pValue);
	}
	return context.GetSelf();
}

// List#append!(elem*)
AScript_DeclareMethod(List, append_X)
{
	DeclareArg(env, _T("elem"), VTYPE_AnyType, false, OCCUR_ZeroOrMore);
}

AScript_ImplementMethod(List, append_X)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	foreach_const (ValueList, pValue, context.GetList(0)) {
		if (pValue->IsList()) {
			foreach_const (ValueList, pValueSub, pValue->GetList()) {
				valList.push_back(*pValueSub);
			}
		} else {
			valList.push_back(*pValue);
		}
	}
	return context.GetSelf();
}

// List#erase!(idx+:number)
AScript_DeclareMethod(List, erase_X)
{
	DeclareArg(env, _T("idx"), VTYPE_Number, false, OCCUR_OnceOrMore);
}

AScript_ImplementMethod(List, erase_X)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	ValueList &valList = pSelf->GetList();
	Object_List::ValueVisitorEx visitor(env, valList);
	foreach_const (ValueList, pValue, context.GetList(0)) {
		pValue->Accept(sig, visitor);
	}
	Object_List::IndexList &indexList = visitor.GetIndexList();
	if (!indexList.empty()) {
		std::sort(indexList.begin(), indexList.end());
		size_t offset = 0;
		foreach_const (Object_List::IndexList, pIdx, indexList) {
			size_t idx = *pIdx;
			valList.erase(valList.begin() + idx - offset);
			offset++;
		}
	}
	return context.GetSelf();
}

// List#padding!(value)
AScript_DeclareMethod(List, padding_X)
{
	DeclareArg(env, _T("value"), VTYPE_AnyType);
}

AScript_ImplementMethod(List, padding_X)
{
	Object_List *pSelf = Object_List::GetSelfObj(context);
	Object_List::DoPadding(sig, pSelf->GetList(), context.GetValue(0));
	if (sig.IsSignalled()) return Value::Null;
	return context.GetSelf();
}

// Assignment
Class_List::Class_List(Class *pClassSuper, const Symbol *pSymbol) :
												Class(pClassSuper, pSymbol)
{
	AScript_AssignMethod(List, count);
	AScript_AssignMethod(List, get);
	AScript_AssignMethod(List, first);
	AScript_AssignMethod(List, last);
	AScript_AssignMethod(List, join);
	AScript_AssignMethod(List, each);
	AScript_AssignMethodEx(List, clear_X, _T("clear!"));
	AScript_AssignMethodEx(List, shuffle_X, _T("shuffle!"));
	AScript_AssignMethodEx(List, add_X, _T("add!"));
	AScript_AssignMethodEx(List, append_X, _T("append!"));
	AScript_AssignMethodEx(List, erase_X, _T("erase!"));
	AScript_AssignMethodEx(List, padding_X, _T("padding!"));
}

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

}
