//
// Object_Stream
//

#include "ascript/Directory.h"
#include "ascript/Object_Stream.h"
#include "ascript/Object_Binary.h"

namespace AScript {

AScript_DeclarePrivSymbol(set);
AScript_DeclarePrivSymbol(cur);

//-----------------------------------------------------------------------------
// Object_Stream
//-----------------------------------------------------------------------------
Object_Stream::~Object_Stream()
{
	Stream::Delete(_pStream);
}

Value Object_Stream::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_Symbol(stat))) {
		Object *pObj = GetStream().GetStatObj(sig);
		if (pObj != NULL) return Value(pObj);
	} else if (pSymbol->IsIdentical(AScript_Symbol(name))) {
		return Value(env, GetStream().GetName());
	} else if (pSymbol->IsIdentical(AScript_Symbol(readable))) {
		return Value(GetStream().IsReadable());
	} else if (pSymbol->IsIdentical(AScript_Symbol(writable))) {
		return Value(GetStream().IsWritable());
	}
	evaluatedFlag = false;
	return Value::Null;
}

Iterator *Object_Stream::CreateIterator(Signal sig)
{
	return new IteratorLine(Object_Stream::Reference(this), -1, true);
}

String Object_Stream::ToString(Signal sig, bool exprFlag)
{
	String str;
	Stream &stream = GetStream();
	str += "<stream:";
	if (stream.IsReadable()) str += "R";
	if (stream.IsWritable()) str += "W";
	if (stream.IsCodecInstalled()) {
		str += ":";
		str += stream.GetEncoding();
		Codec_Encoder *pEncoder = stream.GetEncoder();
		if (pEncoder != NULL && pEncoder->IsProcessEOL()) {
			str += ":dosmode";
		}
	}
	if (*stream.GetName() != '\0') {
		str += ":";
		str += stream.GetName();
	}
	str += ">";
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Stream
//-----------------------------------------------------------------------------
// stream#close()
AScript_DeclareMethod(Stream, close)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Stream, close)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	stream.Close();
	return Value::Null;
}

// stream#read(len?:number)
AScript_DeclareMethod(Stream, read)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "len", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, read)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckReadable(sig)) return Value::Null;
	Value result;
	if (args.IsNumber(0)) {
		size_t len = static_cast<size_t>(args.GetNumber(0));
		char *buff = new char [len];
		size_t lenRead = stream.Read(sig, buff, len);
		if (lenRead == 0) {
			delete [] buff;
			return Value::Null;
		}
		result.InitAsBinary(env, Binary(buff, lenRead));
		delete [] buff;
	} else if (stream.IsInfinite()) {
		sig.SetError(ERR_IOError, "specify a reading size for infinite stream");
		return Value::Null;
	} else {
		const int bytesBuff = 4096;
		Object_Binary *pObjBinary = result.InitAsBinary(env);
		Binary &buff = pObjBinary->GetBinary();
		OAL::Memory memory(bytesBuff);
		char *buffSeg = reinterpret_cast<char *>(memory.GetPointer());
		size_t lenRead = stream.Read(sig, buffSeg, bytesBuff);
		if (lenRead == 0) {
			return Value::Null;
		}
		do {
			buff.append(buffSeg, lenRead);
		} while ((lenRead = stream.Read(sig, buffSeg, bytesBuff)) > 0);
		if (sig.IsSignalled()) return Value::Null;
	}
	return result;
}

// stream#peek(len:number)
AScript_DeclareMethod(Stream, peek)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "len", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, peek)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckReadable(sig)) return Value::Null;
	Value result;
	size_t len = static_cast<size_t>(args.GetNumber(0));
	char *buff = new char [len];
	size_t lenRead = stream.Peek(sig, buff, len);
	if (lenRead == 0) {
		delete [] buff;
		return Value::Null;
	}
	result.InitAsBinary(env, Binary(buff, lenRead));
	delete [] buff;
	return result;
}

// stream#write(buff:binary):reduce
AScript_DeclareMethod(Stream, write)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "buff", VTYPE_Binary);
}

AScript_ImplementMethod(Stream, write)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckWritable(sig)) return Value::Null;
	const Binary &binary = args.GetBinary(0);
	stream.Write(sig, binary.c_str(), binary.size());
	return args.GetSelf();
}

// stream#seek(offset:number, origin?:symbol):reduce
AScript_DeclareMethod(Stream, seek)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "offset", VTYPE_Number);
	DeclareArg(env, "origin", VTYPE_Symbol, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, seek)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	Stream::SeekMode seekMode = Stream::SeekSet;
	if (args.GetValue(1).IsSymbol()) {
		const Symbol *pSymbol = args.GetSymbol(1);
		if (pSymbol->IsIdentical(AScript_PrivSymbol(set))) {
			seekMode = Stream::SeekSet;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(cur))) {
			seekMode = Stream::SeekCur;
		} else {
			sig.SetError(ERR_ValueError, "invalid seek mode %s", pSymbol->GetName());
			return Value::Null;
		}
	}
	stream.Seek(sig, args.GetLong(0), seekMode);
	return args.GetSelf();
}

// stream#tell()
AScript_DeclareMethod(Stream, tell)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Stream, tell)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	return Value(static_cast<unsigned long>(stream.Tell()));
}

// stream#compare(stream:stream):map
AScript_DeclareMethod(Stream, compare)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "stream", VTYPE_Stream);
}

AScript_ImplementMethod(Stream, compare)
{
	Stream &stream1 = Object_Stream::GetSelfObj(args)->GetStream();
	Stream &stream2 = args.GetStream(0);
	bool sameFlag = stream1.Compare(sig, stream2);
	if (sig.IsSignalled()) return Value::Null;
	return Value(sameFlag);
}

// stream#readto(stream:stream):map:reduce
AScript_DeclareMethod(Stream, readto)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "stream", VTYPE_Stream);
}

AScript_ImplementMethod(Stream, readto)
{
	Stream &streamSrc = Object_Stream::GetSelfObj(args)->GetStream();
	Stream &streamDst = args.GetStream(0);
	if (!streamSrc.ReadToStream(sig, streamDst)) return Value::Null;
	return args.GetSelf();
}

// stream#writefrom(stream:stream):map:reduce
AScript_DeclareMethod(Stream, writefrom)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "stream", VTYPE_Stream);
}

AScript_ImplementMethod(Stream, writefrom)
{
	Stream &streamDst = Object_Stream::GetSelfObj(args)->GetStream();
	Stream &streamSrc = args.GetStream(0);
	if (!streamSrc.ReadToStream(sig, streamDst)) return Value::Null;
	return args.GetSelf();
}

// stream#setencoding(encoding:string, dos_flag?:boolean)
AScript_DeclareMethod(Stream, setencoding)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "encoding", VTYPE_String);
	DeclareArg(env, "dos_flag", VTYPE_Boolean, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, setencoding)
{
	Object_Stream *pSelf = Object_Stream::GetSelfObj(args);
	const char *encoding = args.GetString(0);
	bool processEOLFlag = args.IsBoolean(1)? args.GetBoolean(1) : true;
	if (!pSelf->GetStream().InstallCodec(encoding, processEOLFlag)) {
		sig.SetError(ERR_CodecError, "unsupported encoding '%s'", encoding);
		return Value::Null;
	}
	return Value::Null;
}

// stream#dosmode(dos_flag:boolean):reduce
AScript_DeclareMethod(Stream, dosmode)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "dos_flag", VTYPE_Boolean);
}

AScript_ImplementMethod(Stream, dosmode)
{
	Object_Stream *pSelf = Object_Stream::GetSelfObj(args);
	Codec_Encoder *pEncoder = pSelf->GetStream().GetEncoder();
	if (pEncoder != NULL) {
		pEncoder->SetProcessEOLFlag(args.GetBoolean(0));
	}
	return args.GetSelf();
}

// stream#readline():[chop]
AScript_DeclareMethod(Stream, readline)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareAttr(AScript_Symbol(chop));
}

AScript_ImplementMethod(Stream, readline)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckReadable(sig)) return Value::Null;
	int cntChars = 4096;	// tentative
	bool includeEOLFlag = !args.IsSet(AScript_Symbol(chop));
	String str;
	while (cntChars-- > 0) {
		int ch = stream.GetChar(sig);
		if (sig.IsSignalled()) return Value::Null;
		if (ch < 0) break;
		if (ch == '\n') {
			if (includeEOLFlag) str += '\n';
			return Value(env, str.c_str());
		}
		str += ch;
	}
	if (str.empty()) return Value::Null;
	return Value(env, str.c_str());
}

// stream#readlines(nlines?:number):[chop] {block?}
// conrresponding to string#splitlines()
AScript_DeclareMethod(Stream, readlines)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareAttr(AScript_Symbol(chop));
	DeclareArg(env, "nlines", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Stream, readlines)
{
	Object_Stream *pSelf = Object_Stream::GetSelfObj(args);
	Stream &stream = pSelf->GetStream();
	if (!stream.CheckReadable(sig)) return Value::Null;
	int nLinesMax = args.IsNumber(0)? static_cast<int>(args.GetNumber(0)) : -1;
	bool includeEOLFlag = !args.IsSet(AScript_Symbol(chop));
	Iterator *pIterator = new Object_Stream::IteratorLine(
				Object_Stream::Reference(pSelf), nLinesMax, includeEOLFlag);
	return ReturnIterator(env, sig, args, pIterator);
}

// stream#readtext()
AScript_DeclareMethod(Stream, readtext)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Stream, readtext)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckReadable(sig)) return Value::Null;
	String str;
	for (;;) {
		int ch = stream.GetChar(sig);
		if (sig.IsSignalled()) return Value::Null;
		if (ch < 0) break;
		str += ch;
	}
	return Value(env, str.c_str());
}

// stream#print(values*):map:void
AScript_DeclareMethod(Stream, print)
{
	SetMode(RSLTMODE_Void, FLAG_Map);
	DeclareArg(env, "values", VTYPE_Any, OCCUR_ZeroOrMore);
}

AScript_ImplementMethod(Stream, print)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckWritable(sig)) return Value::Null;
	foreach_const (ValueList, pValue, args.GetList(0)) {
		String str(pValue->ToString(sig, false));
		if (sig.IsSignalled()) break;
		stream.Print(sig, str.c_str());
		if (sig.IsSignalled()) break;
	}
	return Value::Null;
}

// stream#println(values*):map:void
AScript_DeclareMethod(Stream, println)
{
	SetMode(RSLTMODE_Void, FLAG_Map);
	DeclareArg(env, "values", VTYPE_Any, OCCUR_ZeroOrMore);
}

AScript_ImplementMethod(Stream, println)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckWritable(sig)) return Value::Null;
	foreach_const (ValueList, pValue, args.GetList(0)) {
		String str(pValue->ToString(sig, false));
		if (sig.IsSignalled()) break;
		stream.Print(sig, str.c_str());
		if (sig.IsSignalled()) break;
	}
	stream.Print(sig, "\n");
	return Value::Null;
}

// stream#printf(format, values*):map:void
AScript_DeclareMethod(Stream, printf)
{
	SetMode(RSLTMODE_Void, FLAG_Map);
	DeclareArg(env, "format", VTYPE_String);
	DeclareArg(env, "values", VTYPE_Any, OCCUR_ZeroOrMore);
}

AScript_ImplementMethod(Stream, printf)
{
	Stream &stream = Object_Stream::GetSelfObj(args)->GetStream();
	if (!stream.CheckWritable(sig)) return Value::Null;
	stream.Printf(sig, args.GetString(0), args.GetList(1));
	return Value::Null;
}

// Assignment
Class_Stream::Class_Stream(Environment *pEnvOuter) : Class(pEnvOuter)
{
	AScript_RealizePrivSymbol(set);
	AScript_RealizePrivSymbol(cur);
	AScript_AssignMethod(Stream, close);
	AScript_AssignMethod(Stream, read);
	AScript_AssignMethod(Stream, peek);
	AScript_AssignMethod(Stream, write);
	AScript_AssignMethod(Stream, seek);
	AScript_AssignMethod(Stream, tell);
	AScript_AssignMethod(Stream, compare);
	AScript_AssignMethod(Stream, readto);
	AScript_AssignMethod(Stream, writefrom);
	AScript_AssignMethod(Stream, setencoding);
	AScript_AssignMethod(Stream, dosmode);
	AScript_AssignMethod(Stream, readline);
	AScript_AssignMethod(Stream, readlines);
	AScript_AssignMethod(Stream, readtext);
	AScript_AssignMethod(Stream, print);
	AScript_AssignMethod(Stream, println);
	AScript_AssignMethod(Stream, printf);
}

bool Class_Stream::CastFrom(Environment &env, Signal sig, Value &value)
{
	if (value.IsString()) {
		Stream *pStream = Directory::OpenStream(env, sig,
						value.GetString(), Stream::ATTR_Readable, "utf-8");
		if (sig.IsSignalled()) return false;
		value = Value(new Object_Stream(env, pStream));
		return true;
	} else if (value.IsBinary()) {
		Object_Binary *pObjBinary = Object_Binary::Reference(
							dynamic_cast<Object_Binary *>(value.GetObject()));
		Object *pObj = new Object_Stream(env, new BinaryStream(sig, pObjBinary));
		value = Value(pObj);
		return true;
	}
	return false;
}

Object *Class_Stream::CreateDescendant(Environment &env, Signal sig, Class *pClass)
{
	return NULL;
}

//-----------------------------------------------------------------------------
// Object_Stream::IteratorLine
//-----------------------------------------------------------------------------
Object_Stream::IteratorLine::~IteratorLine()
{
	Object::Delete(_pObj);
}

bool Object_Stream::IteratorLine::DoNext(Environment &env, Signal sig, Value &value)
{
	Stream &stream = _pObj->GetStream();
	String str;
	if (_nLines == _nLinesMax) return false;
	int ch = stream.GetChar(sig);
	if (ch < 0) return false;
	for ( ; ch >= 0; ch = stream.GetChar(sig)) {
		if (ch == '\n') {
			if (_includeEOLFlag) str += '\n';
			break;
		}
		str += ch;
	}
	if (sig.IsSignalled()) return false;
	_nLines++;
	value = Value(*_pObj, str.c_str());
	return true;
}

String Object_Stream::IteratorLine::ToString(Signal sig) const
{
	return String("<iterator:stream#readlines>");
}

}
