//-----------------------------------------------------------------------------
// AScript csv module
//-----------------------------------------------------------------------------
#include <ascript.h>

AScript_BeginModule(csv)

#define DEFAULT_FORMAT "%g"

AScript_DeclarePrivSymbol(format)

static const char *GetFormat(Environment &env);

static void PutValue(Environment &env, Signal sig, Stream &stream,
									const char *format, const Value &value);

//-----------------------------------------------------------------------------
// Reader
//-----------------------------------------------------------------------------
class Reader {
public:
	bool ReadLine(Environment &env, Signal sig, ValueList &valList);
	virtual char NextChar(Signal sig) = 0;
};

bool Reader::ReadLine(Environment &env, Signal sig, ValueList &valList)
{
	enum {
		STAT_LineTop, STAT_FieldTop, STAT_Field, STAT_Quoted, STAT_QuotedEnd,
	} stat = STAT_LineTop;
	String field;
	char ch = '\0';
	bool rtn = true;
	bool eatNextChar = true;
	for (;;) {
		if (eatNextChar) {
			while ((ch = NextChar(sig)) == '\r') ;
			if (sig.IsSignalled()) return false;
		}
		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 == '"') {
				stat = STAT_Quoted;
			} else if (ch == '\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 == ',') {
				valList.push_back(Value(env, field.c_str()));
				stat = STAT_FieldTop;
			} else if (ch == '\n' || ch == '\0') {
				valList.push_back(Value(env, field.c_str()));
				break;
			} else {
				field.push_back(ch);
			}
		} else if (stat == STAT_Quoted) {
			if (ch == '"') {
				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 == '"') {
				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;
}

//-----------------------------------------------------------------------------
// ReaderStream
//-----------------------------------------------------------------------------
class ReaderStream : public Reader {
private:
	Stream &_stream;
public:
	ReaderStream(Stream &stream) : _stream(stream) {}
	virtual char NextChar(Signal sig);
};

char ReaderStream::NextChar(Signal sig)
{
	int ch = _stream.GetChar(sig);
	return (ch < 0)? '\0' : static_cast<char>(static_cast<unsigned char>(ch));
}

//-----------------------------------------------------------------------------
// ReaderString
//-----------------------------------------------------------------------------
class ReaderString : public Reader {
private:
	const char *_strp;
public:
	ReaderString(const char *str) : _strp(str) {}
	virtual char NextChar(Signal sig);
};

char ReaderString::NextChar(Signal sig)
{
	char ch = *_strp;
	if (ch != '\0') _strp++;
	return ch;
}

//-----------------------------------------------------------------------------
// Iterator_readlines
//-----------------------------------------------------------------------------
class Iterator_readlines : public Iterator {
private:
	Object_Stream *_pObj;
	ReaderStream _reader;
public:
	inline Iterator_readlines(Object_Stream *pObj) :
			Iterator(false), _pObj(pObj), _reader(pObj->GetStream()) {}
	virtual ~Iterator_readlines();
	virtual bool DoNext(Environment &env, Signal sig, Value &value);
	virtual String ToString(Signal sig) const;
};

Iterator_readlines::~Iterator_readlines()
{
	Object::Delete(_pObj);
}

bool Iterator_readlines::DoNext(Environment &env, Signal sig, Value &value)
{
	ValueList &valList = value.InitAsList(*_pObj);
	if (_reader.ReadLine(*_pObj, sig, valList)) return true;
	value = Value::Null;
	return false;
}

String Iterator_readlines::ToString(Signal sig) const
{
	return String("<iterator:csv.read>");
}

//-----------------------------------------------------------------------------
// AScript module functions: csv
//-----------------------------------------------------------------------------
// csv.parse(str:string):map
AScript_DeclareFunction(parse)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "str", VTYPE_String);
}

AScript_ImplementFunction(parse)
{
	Value result;
	ValueList &valList = result.InitAsList(env);
	ReaderString reader(args.GetString(0));
	if (!reader.ReadLine(env, sig, valList) || sig.IsSignalled()) return Value::Null;
	return result;
}

// csv.read(stream:stream) {block?}
AScript_DeclareFunction(read)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(read)
{
	Object_Stream *pObjStream = args.GetStreamObj(0);
	Iterator *pIterator =
			new Iterator_readlines(Object_Stream::Reference(pObjStream));
	if (args.IsRsltMulti()) {
		return ReturnIterator(env, sig, args, pIterator);
	}
	Value value;
	pIterator->Next(env, sig, value);
	Iterator::Delete(pIterator);
	if (sig.IsSignalled()) return Value::Null;
	return value;
}

// csv.write(stream:stream, elem[])
AScript_DeclareFunction(write)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "elem", VTYPE_Any, OCCUR_Once, true);
}

AScript_ImplementFunction(write)
{
	Stream &stream = args.GetStream(0);
	const char *format = GetFormat(GetEnvScope());
	const ValueList &valList = args.GetList(1);
	foreach_const (ValueList, pValue, valList) {
		if (pValue != valList.begin()) {
			stream.PutChar(sig, ',');
			if (sig.IsSignalled()) return Value::Null;
		}
		PutValue(env, sig, stream, format, *pValue);
		if (sig.IsSignalled()) return Value::Null;
	}
	stream.PutChar(sig, '\n');
	return Value::Null;
}

// csv.writes(stream:stream, iter:iterator)
AScript_DeclareFunction(writes)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "iter", VTYPE_Iterator);
}

AScript_ImplementFunction(writes)
{
	Stream &stream = args.GetStream(0);
	Iterator *pIterator = args.GetIterator(1);
	const char *format = GetFormat(GetEnvScope());
	Value value;
	while (pIterator->Next(env, sig, value)) {
		if (!value.IsList()) {
			sig.SetError(ERR_ValueError, "each element must be a list");
			return Value::Null;
		}
		const ValueList &valList = value.GetList();
		foreach_const (ValueList, pValue, valList) {
			if (pValue != valList.begin()) {
				stream.PutChar(sig, ',');
				if (sig.IsSignalled()) return Value::Null;
			}
			if (pValue->IsList()) {
				const ValueList &valListSub = pValue->GetList();
				foreach_const (ValueList, pValueSub, valListSub) {
					if (pValueSub != valListSub.begin()) {
						stream.PutChar(sig, ',');
						if (sig.IsSignalled()) return Value::Null;
					}
					PutValue(env, sig, stream, format, *pValueSub);
					if (sig.IsSignalled()) return Value::Null;
				}
			} else {
				PutValue(env, sig, stream, format, *pValue);
				if (sig.IsSignalled()) return Value::Null;
			}
		}
		stream.PutChar(sig, '\n');
		if (sig.IsSignalled()) return Value::Null;
	}
	if (sig.IsSignalled()) return Value::Null;
	return Value::Null;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Stream
//-----------------------------------------------------------------------------
// stream#csvread() {block?}
AScript_DeclareMethod(Stream, csvread)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, csvread)
{
	Object_Stream *pSelf = Object_Stream::GetSelfObj(args);
	Iterator *pIterator =
			new Iterator_readlines(Object_Stream::Reference(pSelf));
	if (args.IsRsltMulti()) {
		return ReturnIterator(env, sig, args, pIterator);
	}
	Value value;
	pIterator->Next(env, sig, value);
	Iterator::Delete(pIterator);
	if (sig.IsSignalled()) return Value::Null;
	return value;
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(format);
	// function assignment
	AScript_AssignFunction(parse);
	AScript_AssignFunction(read);
	AScript_AssignFunction(write);
	AScript_AssignFunction(writes);
	// method assignment to stream type
	AScript_AssignMethodTo(VTYPE_Stream, Stream, csvread);
	// value assignment
	AScript_AssignValue(format, Value(env, DEFAULT_FORMAT));
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// Utilities
//-----------------------------------------------------------------------------
static const char *GetFormat(Environment &env)
{
	const Value *pValue = env.LookupValue(AScript_PrivSymbol(format), false);
	return (pValue != NULL && pValue->IsString())?
									pValue->GetString() : DEFAULT_FORMAT;
}

static void PutValue(Environment &env, Signal sig, Stream &stream,
										const char *format, const Value &value)
{
	String str;
	if (value.IsInvalid()) {
		return;
	} else if (value.IsNumber()) {
		str = Formatter::Format(sig, format, ValueList(value));
	} else if (value.IsComplex()) {
		str = Formatter::Format(sig, format, ValueList(value));
	} else if (value.IsString()) {
		str += '"';
		for (const char *p = value.GetString(); *p != '\0'; p++) {
			char ch = *p;
			str += ch;
			if (ch == '"') str += ch;
		}
		str += '"';
	} else {
		sig.SetError(ERR_TypeError, "can't output in CSV format");
		return;
	}
	stream.Print(sig, str.c_str());
}

AScript_EndModule(csv, csv)

AScript_RegisterModule(csv)
