#define ASCRIPT_DLL_MODULE
//-----------------------------------------------------------------------------
// AScript csv module
//-----------------------------------------------------------------------------
#include "Module_csv.h"

AScript_BeginModule(csv)

static bool ReadLine(Environment &env, Signal sig, File &file, ValueList &valList);
static void PutValue(Environment &env, Signal sig, File &file, const Value &value);

// result = csv.read(file:File)
AScript_DeclareFunction(read)
{
	DeclareArg(env, _T("file"), VTYPE_File);
}

AScript_ImplementFunction(read)
{
	const Value valueEmpty(env, _T(""));
	File &file = context.GetFile(0);
	Value result;
	ValueList &valListCol = result.InitAsList(env);
	ValueList valListFields;
	size_t nLine = 0;
	while (ReadLine(env, sig, file, valListFields)) {
		ValueList::iterator pValueCol = valListCol.begin();
		ValueList::iterator pValueColEnd = valListCol.end();
		foreach_const (ValueList, pValueField, valListFields) {
			if (pValueCol == pValueColEnd) {
				Value valueCol;
				ValueList &valList = valueCol.InitAsList(env, nLine, valueEmpty);
				valListCol.push_back(valueCol);
				valList.push_back(*pValueField);
			} else {
				pValueCol->GetList().push_back(*pValueField);
				pValueCol++;
			}
		}
		for ( ; pValueCol != pValueColEnd; pValueCol++) {
			pValueCol->GetList().push_back(valueEmpty);
		}
		nLine++;
		valListFields.clear();
	}
	return result;
}

// result = csv.readline(file:File)
AScript_DeclareFunction(readline)
{
	DeclareArg(env, _T("file"), VTYPE_File);
}

AScript_ImplementFunction(readline)
{
	File &file = context.GetFile(0);
	Value result;
	ValueList &valList = result.InitAsList(env);
	if (!ReadLine(env, sig, file, valList) || sig.IsSignalled()) return Value::Null;
	return result;
}

// result = csv.readlines(file:File, nlines?:number) {block?}
AScript_DeclareFunction(readlines)
{
	DeclareArg(env, _T("file"), VTYPE_File);
	DeclareArg(env, _T("nlines"), VTYPE_Number, false, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(readlines)
{
	File &file = context.GetFile(0);
	int nLinesMax = context.IsNumber(1)? context.GetNumber(1) : -1;
	Value result;
	if (nLinesMax == 0) {
		result.InitAsList(env);
		return result;
	}
	ResultListComposer resultListComposer(env, context, result);
	// pFuncBlock is set to NULL without error if block is not specified.
	const Function *pFuncBlock = GetBlockFunction(env, sig, context);
	if (sig.IsSignalled()) return Value::Null;
	Environment envBlock(&env, ENVTYPE_Block);
	for (int nLines = 0; nLines != nLinesMax; nLines++) {
		Value valueElem;
		ValueList &valListElem = valueElem.InitAsList(env);
		if (!ReadLine(env, sig, file, valListElem)) break;
		if (sig.IsSignalled()) return Value::Null;
		if (pFuncBlock != NULL) {
			valueElem = pFuncBlock->Eval(envBlock, sig,
				Context(ValueList(valueElem, Value(static_cast<Number>(nLines)))));
			AScript_BlockSignalHandlerInLoop(sig, valueElem, Value::Null)
			//if (sig.IsSignalled()) return Value::Null;
		}
		resultListComposer.Store(valueElem);
	}
	return result;
}

// csv.write(file:File, elems[]+)
AScript_DeclareFunction(write)
{
	DeclareArg(env, _T("file"), VTYPE_File);
	DeclareArg(env, _T("elems"), VTYPE_AnyType, true, OCCUR_OnceOrMore);
}

AScript_ImplementFunction(write)
{
	File &file = context.GetFile(0);
	const ValueList &valList = context.GetList(1);
	if (valList.empty()) return Value::Null;
	ValuePickerList pickerList;
	foreach_const (ValueList, pValue, valList) {
		pickerList.push_back(ValuePicker(pValue->GetList()));
	}
	for ( ; pickerList.IsValidAny(); pickerList.Advance()) {
		foreach_const (ValuePickerList, pPicker, pickerList) {
			if (pPicker != pickerList.begin()) file.PutChar(_T(','));
			if (pPicker->IsValid()) {
				PutValue(env, sig, file, pPicker->GetValue());
				if (sig.IsSignalled()) break;
			}
		}
		file.PutChar(_T('\n'));
	}
	return Value::Null;
}

// csv.writeline(file:File, elem[]):map
AScript_DeclareFunction(writeline)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("file"), VTYPE_File);
	DeclareArg(env, _T("elem"), VTYPE_AnyType, true);
}

AScript_ImplementFunction(writeline)
{
	File &file = context.GetFile(0);
	const ValueList &valList = context.GetList(1);
	foreach_const (ValueList, pValue, valList) {
		if (pValue != valList.begin()) file.PutChar(_T(','));
		PutValue(env, sig, file, *pValue);
	}
	file.PutChar(_T('\n'));
	return Value::Null;
}

static bool ReadLine(Environment &env, Signal sig, File &file, ValueList &valList)
{
	enum {
		STAT_LineTop, STAT_FieldTop, STAT_Field, STAT_Quoted, STAT_QuotedEnd,
	} stat = STAT_LineTop;
	String field;
	int ch;
	bool rtn = true;
	bool eatNextChar = true;
	for (;;) {
		if (eatNextChar) ch = file.GetChar();
		eatNextChar = true;
		if (stat == STAT_LineTop) {
			if (ch < 0) {
				rtn = false;
				break;
			}
			eatNextChar = false;
			stat = STAT_FieldTop;
		} else if (stat == STAT_FieldTop) {
			field.clear();
			if (ch == _T('"')) {
				stat = STAT_Quoted;
			} else if (ch == _T('\n') || ch < 0) {
				valList.push_back(Value(env, field.c_str()));
				break;
			} else {
				eatNextChar = false;
				stat = STAT_Field;
			}
		} else if (stat == STAT_Field) {
			if (ch == _T(',')) {
				valList.push_back(Value(env, field.c_str()));
				stat = STAT_FieldTop;
			} else if (ch == _T('\n') || ch < 0) {
				valList.push_back(Value(env, field.c_str()));
				break;
			} else {
				field.push_back(ch);
			}
		} else if (stat == STAT_Quoted) {
			if (ch == _T('"')) {
				stat = STAT_QuotedEnd;
			} else if (ch < 0) {
				valList.push_back(Value(env, field.c_str()));
				break;
			} else {
				field.push_back(ch);
			}
		} else if (stat == STAT_QuotedEnd) {
			if (ch == _T('"')) {
				field.push_back(ch);
				stat = STAT_Quoted;
			} else if (ch < 0) {
				valList.push_back(Value(env, field.c_str()));
				break;
			} else {
				eatNextChar = false;
				stat = STAT_Field;
			}
		}
	}
	return rtn;
}

static void PutValue(Environment &env, Signal sig, File &file, const Value &value)
{
	if (value.IsInvalid()) {
		// nothing to do
	} else if (value.IsNumberOrComplex()) {
		Value result(env, Formatter::Format(sig, _T("%g"), ValueList(value)).c_str());
		if (sig.IsSignalled()) return;
		file.PutString(result.ToString(sig, false).c_str());
	} else if (value.IsString()) {
		file.PutChar(_T('"'));
		for (const TCHAR *p = value.GetString(); *p != _T('\0'); p++) {
			TCHAR ch = *p;
			file.PutChar(ch);
			if (ch == _T('"')) file.PutChar(ch);
		}
		file.PutChar(_T('"'));
	} else {
		sig.SetError(ERR_TypeError, _T("can't output in CSV format"));
	}
}

// Module entry
AScript_ModuleEntry()
{
	AScript_AssignFunction(read);
	AScript_AssignFunction(readline);
	AScript_AssignFunction(readlines);
	AScript_AssignFunction(write);
	AScript_AssignFunction(writeline);
	AScript_AssignValue(Symbol::Add(_T("format")), Value(env, _T("%.6f")));
}

AScript_EndModule(csv)

AScript_DLLModuleEntry(csv)
