//
// Object_File
//

#include "AScript.h"

namespace AScript {

AScript_DeclarePrivSymbol(set);
AScript_DeclarePrivSymbol(cur);
AScript_DeclarePrivSymbol(end);
AScript_DeclarePrivSymbol(chop);

// implementation of Object_File
bool Object_File::IsFile() const { return true; }

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

bool Object_File::IsWritable(Signal sig)
{
	if (_file.IsWritable()) return true;
	sig.SetError(ERR_IOError, _T("%s has not been opened for write"),
									ToString(sig, false).c_str());
	return false;
}

bool Object_File::IsReadable(Signal sig)
{
	if (_file.IsReadable()) return true;
	sig.SetError(ERR_IOError, _T("%s has not been opened for read"),
									ToString(sig, false).c_str());
	return false;
}

String Object_File::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += _T("<file:");
	str += _file.GetFileName();
	str += _T(">");
	return str;
}

// Constructor: File(filename, mode?):map {block?}
Object_File::Constructor::Constructor(Environment &env, const TCHAR *name) :
							ConstructorBase(name, env.GetClass_File())
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("filename"), VTYPE_String);
	DeclareArg(env, _T("mode"), VTYPE_String, false, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

Value Object_File::Constructor::DoEval(Environment &env, Signal sig, Context &context) const
{
	const ValueList &valListArg = context.GetArgs();
	ASSUME(env, valListArg.size() == 2);
	const TCHAR *fileName = valListArg[0].GetString();
	const TCHAR *mode = valListArg[1].IsString()? valListArg[1].GetString() : _T("rt");
	Value result;
	Object_File *pObj = result.InitAsFile(env);
	pObj->GetFile().Open(sig, fileName, mode);
	if (sig.IsSignalled()) return Value::Null;
	if (context.IsBlockSpecified()) {
		do { // do-while is required for AScript_BlockSignalHandlerInLoop()
			const Function *pFuncBlock = GetBlockFunction(env, sig, context);
			if (pFuncBlock == NULL) return Value::Null;
			result = pFuncBlock->Eval(env, sig, Context(ValueList(result)));
			AScript_BlockSignalHandlerInLoop(sig, result, Value::Null)
		} while (0);
	}
	return result;
}

// File#close()
AScript_DeclareMethod(File, close)
{
}

AScript_ImplementMethod(File, close)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	const ValueList &valListArg = context.GetArgs();
	File &file = pSelf->GetFile();
	file.Close();
	return Value::Null;
}

AScript_DeclareMethod(File, seek)
{
	DeclareArg(env, _T("offset"), VTYPE_Number);
	DeclareArg(env, _T("origin"), VTYPE_Symbol, false, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(File, seek)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	int origin = SEEK_SET;
	if (context.GetValue(1).IsSymbol()) {
		const Symbol *pSymbol = context.GetSymbol(1);
		origin =
			(pSymbol->IsIdentical(AScript_PrivSymbol(set)))? SEEK_SET :
			(pSymbol->IsIdentical(AScript_PrivSymbol(cur)))? SEEK_CUR :
			(pSymbol->IsIdentical(AScript_PrivSymbol(end)))? SEEK_END : SEEK_SET;
	}
	int rtn = pSelf->GetFile().Seek(context.GetLong(0), origin); // fseek()
	return Value::Null;
}

// File#read()
AScript_DeclareMethod(File, read)
{
}

AScript_ImplementMethod(File, read)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	return Value::Null;
}

// File#readline():[chop]
AScript_DeclareMethod(File, readline)
{
	DeclareAttr(AScript_PrivSymbol(chop));
}

AScript_ImplementMethod(File, readline)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	if (!pSelf->IsReadable(sig)) return Value::Null;
	int cntChars = 4096;	// tentative
	bool includeEOLFlag = !context.IsSet(AScript_PrivSymbol(chop));
	File &file = pSelf->GetFile();
	String str;
	while (cntChars-- > 0) {
		int ch = file.GetChar();
		if (ch < 0) break;
		if (ch == _T('\n')) {
			if (includeEOLFlag) str += _T('\n');
			return Value(env, str.c_str());
		}
		str += ch;
	}
	if (str.empty()) return Value::Null;
	return Value(env, str.c_str());
}

// File#readlines(nlines?:number):[chop] {block?}
AScript_DeclareMethod(File, readlines)
{
	DeclareAttr(AScript_PrivSymbol(chop));
	DeclareArg(env, _T("nlines"), VTYPE_Number, false, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(File, readlines)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	if (!pSelf->IsReadable(sig)) return Value::Null;
	int cntChars = 4096;	// tentative
	bool includeEOLFlag = !context.IsSet(AScript_PrivSymbol(chop));
	File &file = pSelf->GetFile();
	Value result;
	String str;
	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);
	int nLinesMax = context.IsNumber(0)? context.GetNumber(0) : -1;
	if (nLinesMax == 0) {
		result.InitAsList(env);
		return result;
	}
	int nLines = 0;
	for (;;) {
		int ch = file.GetChar();
		if (ch < 0) break;
		if (ch == _T('\n')) {
			if (includeEOLFlag) str += _T('\n');
			Value value(env, str.c_str());
			if (pFuncBlock != NULL) {
				value = pFuncBlock->Eval(envBlock, sig,
					Context(ValueList(value, Value(static_cast<Number>(nLines)))));
				AScript_BlockSignalHandlerInLoop(sig, value, Value::Null)
			}
			resultListComposer.Store(value);
			str.clear();
			nLines++;
			if (nLines == nLinesMax) break;
		} else {
			str += ch;
		}
	}
	if (nLines != nLinesMax && !str.empty()) {
		do {
			Value value(env, str.c_str());
			if (pFuncBlock != NULL) {
				value = pFuncBlock->Eval(envBlock, sig,
						Context(ValueList(value, Value(static_cast<Number>(nLines)))));
				AScript_BlockSignalHandlerInLoop(sig, value, Value::Null)
			}
			resultListComposer.Store(value);
		} while (0);
	}
	return result;
}

// File#stat()
AScript_DeclareMethod(File, stat)
{
}

AScript_ImplementMethod(File, stat)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	FileStat fileStat = pSelf->GetFile().GetFileStatByHandle(sig);
	if (sig.IsSignalled()) return Value::Null;
	Object_FileStat *pObj = new Object_FileStat(env.GetClass_FileStat(), fileStat);
	return Value(pObj, VTYPE_FileStat);
}

// File#print(value):map
AScript_DeclareMethod(File, print)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("value"), VTYPE_AnyType);
}

AScript_ImplementMethod(File, print)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	if (!pSelf->IsWritable(sig)) return Value::Null;
	File &file = pSelf->GetFile();
	file.PutString(context.GetValue(0).ToString(sig, false).c_str());
	return Value::Null;
}

// File#println(values*)
AScript_DeclareMethod(File, println)
{
	DeclareArg(env, _T("values"), VTYPE_AnyType, false, OCCUR_ZeroOrMore);
}

AScript_ImplementMethod(File, println)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	if (!pSelf->IsWritable(sig)) return Value::Null;
	File &file = pSelf->GetFile();
	foreach_const (ValueList, pValue, context.GetList(0)) {
		file.PutString(pValue->ToString(sig, false).c_str());
	}
	file.PutString(_T("\n"));
	return Value::Null;
}

// File#printf(format, values*):map
AScript_DeclareMethod(File, printf)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("format"), VTYPE_String);
	DeclareArg(env, _T("values"), VTYPE_AnyType, false, OCCUR_ZeroOrMore);
}

AScript_ImplementMethod(File, printf)
{
	Object_File *pSelf = Object_File::GetSelfObj(context);
	if (!pSelf->IsWritable(sig)) return Value::Null;
	File &file = pSelf->GetFile();
	File::Formatter(file).Format(sig, context.GetString(0), context.GetList(1));
	return Value::Null;
}

// Assignment
Class_File::Class_File(Class *pClassSuper, const Symbol *pSymbol) :
												Class(pClassSuper, pSymbol)
{
	AScript_RealizePrivSymbol(set);
	AScript_RealizePrivSymbol(cur);
	AScript_RealizePrivSymbol(end);
	AScript_RealizePrivSymbol(chop);
	AScript_AssignMethod(File, close);
	AScript_AssignMethod(File, seek);
	AScript_AssignMethod(File, read);
	AScript_AssignMethod(File, readline);
	AScript_AssignMethod(File, readlines);
	AScript_AssignMethod(File, stat);
	AScript_AssignMethod(File, print);
	AScript_AssignMethod(File, println);
	AScript_AssignMethod(File, printf);
}

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

}
