//
// Object_String
//

#include "ascript/Object_String.h"
#include "ascript/Object_Codec.h"
#include "ascript/Expr.h"
#include "ascript/Formatter.h"

namespace AScript {

//-----------------------------------------------------------------------------
// Object_String
//-----------------------------------------------------------------------------
Object_String::Object_String(const Object_String &obj) : Object(obj), _str(obj._str)
{
}

Object_String::~Object_String()
{
}

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

Value Object_String::IndexGet(Signal sig, const Value &valueIdx)
{
	if (!valueIdx.IsNumber()) {
		sig.SetError(ERR_IndexError, "index must be a number for string");
		return Value::Null;
	}
	int idx = valueIdx.GetInt();
	int len = static_cast<int>(Length(_str.c_str()));
	if (idx >= 0) {
		if (idx >= len) {
			sig.SetError(ERR_IndexError, "index is out of range");
			return Value::Null;
		}
		return Value(*this, PickChar(_str, idx).c_str());
	} else {
		if (-idx > len) {
			sig.SetError(ERR_IndexError, "index is out of range");
			return Value::Null;
		}
		return Value(*this, PickChar(_str, len + idx).c_str());
	}
}

void Object_String::IndexSet(Signal sig, const Value &valueIdx, const Value &value)
{
	sig.SetError(ERR_IndexError, "replacement of string is not supported yet");
}

Iterator *Object_String::CreateIterator(Signal sig)
{
	return new IteratorEach(Object_String::Reference(this), -1, false);
}

String Object_String::ToString(Signal sig, bool exprFlag)
{
	if (exprFlag) {
		String str;
		str += '"';
		EscapeString(str, _str.c_str());
		str += '"';
		return str;
	}
	return _str;
}

//-----------------------------------------------------------------------------
// Object_String::IteratorEach
//-----------------------------------------------------------------------------
Object_String::IteratorEach::IteratorEach(Object_String *pObj, int cntMax, bool utf32Flag) :
	Iterator(false), _pObj(pObj), _cnt(cntMax), _cntMax(cntMax), _utf32Flag(utf32Flag),
	_pCur(pObj->GetStringSTL().begin())
{
}

Object_String::IteratorEach::~IteratorEach()
{
	Object::Delete(_pObj);
}

bool Object_String::IteratorEach::DoNext(Environment &env, Signal sig, Value &value)
{
	const String &str = _pObj->GetStringSTL();
	if (_pCur == str.end()) return false;
	if (_utf32Flag) {
		unsigned long codeUTF32;
		_pCur = NextUTF32(str, _pCur, codeUTF32);
		value = Value(codeUTF32);
	} else {
		String::const_iterator pLeft = _pCur;
		if (_cnt == 0) {
			_pCur = str.end();
		} else {
			_pCur = NextChar(str, _pCur);
			if (_cnt > 0) _cnt--;
		}
		value = Value(env, String(pLeft, _pCur).c_str());
	}
	return true;
}

String Object_String::IteratorEach::ToString(Signal sig) const
{
	return String("<iterator:string:each>");
}

//-----------------------------------------------------------------------------
// Object_String::IteratorLine
//-----------------------------------------------------------------------------
Object_String::IteratorLine::IteratorLine(Object_String *pObj,
											int cntMax, bool includeEOLFlag) :
			Iterator(false), _pObj(pObj), _cnt(cntMax), _cntMax(cntMax),
			_includeEOLFlag(includeEOLFlag), _pCur(pObj->GetStringSTL().begin())
{
}

Object_String::IteratorLine::~IteratorLine()
{
	Object::Delete(_pObj);
}

bool Object_String::IteratorLine::DoNext(Environment &env, Signal sig, Value &value)
{
	const String &str = _pObj->GetStringSTL();
	if (_pCur == str.end() || _cnt == 0) return false;
	String::const_iterator pLeft = _pCur;
	String::const_iterator pNext = str.end();
	for ( ; _pCur != str.end(); _pCur++) {
		if (*_pCur == '\r') {
			pNext = _pCur + 1;
			if (pNext != str.end() && *pNext == '\n') pNext++;
			break;
		} else if (*_pCur == '\n') {
			pNext = _pCur + 1;
			break;
		}
	}
	value = Value(env, String(pLeft, _includeEOLFlag? pNext : _pCur).c_str());
	_pCur = pNext;
	if (_cnt > 0) _cnt--;
	return true;
}

String Object_String::IteratorLine::ToString(Signal sig) const
{
	return String("<iterator>");
}

//-----------------------------------------------------------------------------
// Object_String::IteratorSplit
//-----------------------------------------------------------------------------
Object_String::IteratorSplit::IteratorSplit(Object_String *pObj, const char *sep,
											int cntMax, bool ignoreCaseFlag) :
	Iterator(false), _pObj(pObj), _sep(sep), _cnt(cntMax), _cntMax(cntMax),
	_ignoreCaseFlag(ignoreCaseFlag), _doneFlag(false),
	_pCur(pObj->GetStringSTL().begin())
{
}

Object_String::IteratorSplit::~IteratorSplit()
{
	Object::Delete(_pObj);
}

bool Object_String::IteratorSplit::DoNext(Environment &env, Signal sig, Value &value)
{
	const String &str = _pObj->GetStringSTL();
	if (_doneFlag) {
		return false;
	} else if (_pCur == str.end()) {
		value = Value(env, "");
		_doneFlag = true;
	} else if (_cnt == 0) {
		value = Value(env, String(_pCur, str.end()).c_str());
		_pCur = str.end();
		_doneFlag = true;
	} else {
		String::const_iterator pRight =
							FindString(_pCur, str.end(), _sep, _ignoreCaseFlag);
		value = Value(env, String(_pCur, pRight).c_str());
		if (pRight == str.end()) {
			_pCur = str.end();
			_doneFlag = true;
		} else {
			_pCur = pRight + _sep.size();
		}
		if (_cnt > 0) _cnt--;
	}
	return true;
}

String Object_String::IteratorSplit::ToString(Signal sig) const
{
	return String("<iterator>");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_String
//-----------------------------------------------------------------------------
// string#len()
AScript_DeclareMethod(String, len)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Returns the length of the string in characters.");
}

AScript_ImplementMethod(String, len)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(static_cast<unsigned int>(Length(pSelf->GetString())));
}

// string#isempty()
AScript_DeclareMethod(String, isempty)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Returns true if the string is empty.");
}

AScript_ImplementMethod(String, isempty)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(pSelf->GetStringSTL().empty());
}

// string#capitalize()
AScript_DeclareMethod(String, capitalize)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp(
	"Returns a string copied from the original one but with the first character\n"
	"capitalized.");
}

AScript_ImplementMethod(String, capitalize)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Capitalize(pSelf->GetString()).c_str());
}

// string#lower()
AScript_DeclareMethod(String, lower)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Returns a string of lowercase characters of the original one");
}

AScript_ImplementMethod(String, lower)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Lower(pSelf->GetString()).c_str());
}

// string#upper()
AScript_DeclareMethod(String, upper)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Returns a string of uppercase characters of the original one");
}

AScript_ImplementMethod(String, upper)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Upper(pSelf->GetString()).c_str());
}

// string#strip():[both,left,right]
AScript_DeclareMethod(String, strip)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareAttr(AScript_Symbol(both));
	DeclareAttr(AScript_Symbol(left));
	DeclareAttr(AScript_Symbol(right));
	SetHelp(
	"Returns a string that removes space characters on left, right or both of\n"
	"the original one. An attribute :both removes spaces on both sides, :left on left\n"
	"and :right on right. An attribut :both is the default behaviour.");
}

AScript_ImplementMethod(String, strip)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Strip(pSelf->GetString(), args.GetAttrs()).c_str());
}

// string#align(len:number, padding:string => " "):map:[center,left,right]
AScript_DeclareMethod(String, align)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "len", VTYPE_Number);
	DeclareArg(env, "padding", VTYPE_String, OCCUR_Once, false, false, new Expr_String(" "));
	DeclareAttr(AScript_Symbol(center));
	DeclareAttr(AScript_Symbol(left));
	DeclareAttr(AScript_Symbol(right));
	SetHelp(
	"Returns a string aligned in left, right or center within a specified length.\n"
	"An attribute :center aligns the string in center, :left in left and :right in right\n"
	"An attribute :center is the default behaviour.\n"
	"It fills a padding area with a character specified by an argument padding,\n"
	"and a white space is used when itt is omitted");
}


AScript_ImplementMethod(String, align)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	size_t len = args.GetSizeT(0);
	const char *padding = args.GetString(1);
	if (Length(padding) != 1) {
		sig.SetError(ERR_ValueError, "padding must consist of a single character");
		return Value::Null;
	}
	String str;
	if (args.IsSet(AScript_Symbol(right))) {
		str = RJust(pSelf->GetString(), len, padding);
	} else if (args.IsSet(AScript_Symbol(left))) {
		str = LJust(pSelf->GetString(), len, padding);
	} else {
		str = Center(pSelf->GetString(), len, padding);
	}
	return Value(env, str.c_str());
}

// string#left(len?:number):map
AScript_DeclareMethod(String, left)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "len", VTYPE_Number, OCCUR_ZeroOrOnce);
	SetHelp("Returns a copy of the string in len characters from its left side");
}

AScript_ImplementMethod(String, left)
{
	if (!args.IsNumber(0)) return args.GetSelf();
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Left(pSelf->GetString(), args.GetSizeT(0)).c_str());
}

// string#right(len?:number):map
AScript_DeclareMethod(String, right)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "len", VTYPE_Number, OCCUR_ZeroOrOnce);
	SetHelp("Returns a copy of the string in len characters from its right side");
}

AScript_ImplementMethod(String, right)
{
	if (!args.IsNumber(0)) return args.GetSelf();
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Right(pSelf->GetString(), args.GetSizeT(0)).c_str());
}

// string#mid(pos:number => 0, len?:number):map
AScript_DeclareMethod(String, mid)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pos", VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareArg(env, "len", VTYPE_Number, OCCUR_ZeroOrOnce);
	SetHelp(
	"Returns a copy of part of the string in len characters starting from pos.\n"
	"If an argument len is omitted, it returns a string from pos to its end.\n"
	"Number of an argument pos starts from zero.\n"
	"Example:\n"
	">>> \"Hello world\".mid(3, 2)\n"
	"lo\n"
	">>> \"Hello world\".mid(5)\n"
	"world");
}

AScript_ImplementMethod(String, mid)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Middle(pSelf->GetString(), args.GetInt(0),
						args.IsNumber(1)? args.GetInt(1) : -1).c_str());
}

// string#find(sub:string, pos?:number => 0):map:[icase,rev]
AScript_DeclareMethod(String, find)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "sub",	VTYPE_String);
	DeclareArg(env, "pos",	VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareAttr(AScript_Symbol(icase));
	DeclareAttr(AScript_Symbol(rev));
	SetHelp(
	"Finds a sub string from the string and returns its position.\n"
	"Number of position starts from zero. You can specify a position to start\n"
	"finding by an argument pos. It returns nil if finding fails.\n"
	"With an attribute :icase, case of characters are ignored while finding.\n"
	"When an attribute :rev is specified, finding starts from tail of the string\n");
}

AScript_ImplementMethod(String, find)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return FindString(env, sig, pSelf->GetString(), args.GetString(0),
									args.GetInt(1), args.GetAttrs());
}

// string#startswith(prefix:string, pos:number => 0):map:[icase]
AScript_DeclareMethod(String, startswith)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "prefix",	VTYPE_String);
	DeclareArg(env, "pos",		VTYPE_Number, OCCUR_Once, false, false, new Expr_Value(0));
	DeclareAttr(AScript_Symbol(icase));
	SetHelp(
	"Returns true if the string starts with prefix.\n"
	"You can specify a top position for the matching by an argument pos.\n"
	"With an attribute :icase, case of characters are ignored while finding.");
}

AScript_ImplementMethod(String, startswith)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return StartsWith(pSelf->GetString(), args.GetString(0),
					args.GetInt(1), args.IsSet(AScript_Symbol(icase)));
}

// string#endswith(suffix:string, endpos?:number):map:[icase]
AScript_DeclareMethod(String, endswith)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "suffix",	VTYPE_String);
	DeclareArg(env, "endpos",	VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(icase));
	SetHelp(
	"Returns true if the string ends with suffix.\n"
	"You can specify a bottom position for the matching by an argument endpos.\n"
	"With an attribute :icase, case of characters are ignored while finding.");
}

AScript_ImplementMethod(String, endswith)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	const char *str = pSelf->GetString();
	if (args.IsNumber(1)) {
		return EndsWith(str, args.GetString(0),
					args.GetInt(1), args.IsSet(AScript_Symbol(icase)));
	} else {
		return EndsWith(str, args.GetString(0),
					args.IsSet(AScript_Symbol(icase)));
	}
}

// string#replace(sub:string, replace:string, count?:number):map:[icase]
AScript_DeclareMethod(String, replace)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "sub",		VTYPE_String);
	DeclareArg(env, "replace",	VTYPE_String);
	DeclareArg(env, "count",	VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(icase));
	SetHelp(
	"Returns a string that substitutes sub strings in the string with replace.\n"
	"An argument count limits the maximum number of substitution\n"
	"and there's no limit if it's omitted.\n"
	"With an attribute :icase,	case of characgters are ignored while finding.");
}

AScript_ImplementMethod(String, replace)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	String result = Replace(pSelf->GetString(),
			args.GetString(0), args.GetString(1),
			args.IsNumber(2)? args.GetInt(2) : -1, args.GetAttrs());
	return Value(env, result.c_str());
}

// string#split(sep?:string, count?:number):[icase] {block?}
AScript_DeclareMethod(String, split)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "sep", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "count", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(icase));
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp(
	"Creates an iterator generating sub strings extracted from the original one\n"
	"separated by a specified string sep.\n"
	"With an attribute :icase, case of characgters are ignored while finding.\n"
	ITERATOR_HELP
	"Block parameter format: |sub:string, idx:number|");
}

AScript_ImplementMethod(String, split)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	Object_String *pObj = Object_String::Reference(pSelf);
	int maxSplit = args.IsNumber(1)? args.GetInt(1) : -1;
	Iterator *pIterator = NULL;
	if (args.IsString(0) && *args.GetString(0) != '\0') {
		const char *sep = args.GetString(0);
		bool ignoreCaseFlag = args.IsSet(AScript_Symbol(icase));
		pIterator = new Object_String::IteratorSplit(
										pObj, sep, maxSplit, ignoreCaseFlag);
	} else {
		pIterator = new Object_String::IteratorEach(pObj, maxSplit, false);
	}
	return ReturnIterator(env, sig, args, pIterator);
}

// string#each():[utf32] {block?}
AScript_DeclareMethod(String, each)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareAttr(AScript_Symbol(utf32));
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp(
	"Creates an iterator generating strings of each character in the original one.\n"
	ITERATOR_HELP
	"Block parameter format: |char:string, idx:number|");
}

AScript_ImplementMethod(String, each)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	Object_String *pObj = Object_String::Reference(pSelf);
	bool utf32Flag = args.IsSet(AScript_Symbol(utf32));
	Iterator *pIterator = new Object_String::IteratorEach(pObj, -1, utf32Flag);
	return ReturnIterator(env, sig, args, pIterator);
}

// string#eachline(nlines?:number):[chop] {block?}
// conrresponding to file#readlines()
AScript_DeclareMethod(String, eachline)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "nlines", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(chop));
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp(
	"Creates an iterator generating strings of each line in the original one.\n"
	"In default, end-of-line characters are involved in the result,\n"
	"and you can eliminates them by specifying :chop attribute.\n"
	ITERATOR_HELP
	"Block parameter format: |line:string, idx:number|");
}

AScript_ImplementMethod(String, eachline)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	Object_String *pObj = Object_String::Reference(pSelf);
	int maxSplit = args.IsNumber(0)? args.GetInt(0) : -1;
	bool includeEOLFlag = !args.IsSet(AScript_Symbol(chop));
	return ReturnIterator(env, sig, args,
				new Object_String::IteratorLine(pObj, maxSplit, includeEOLFlag));
}

// string#format(values*):map
AScript_DeclareMethod(String, format)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "values", VTYPE_Any, OCCUR_ZeroOrMore);
	SetHelp(
	"Parses the content of the string object as a format specifier similar to\n"
	"C language's printf and returns a formatted string of argument values.");
}

AScript_ImplementMethod(String, format)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, Formatter::Format(sig,
						pSelf->GetString(), args.GetList(0)).c_str());
}

// string#escapehtml()
AScript_DeclareMethod(String, escapehtml)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp(
	"Returns a string that converts characters into escape sequences.");
}

AScript_ImplementMethod(String, escapehtml)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, EscapeHtml(pSelf->GetString(), false).c_str());
}

// string#unescapehtml()
AScript_DeclareMethod(String, unescapehtml)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp(
	"Returns a string that reverts escaped sequences into characters.");
}

AScript_ImplementMethod(String, unescapehtml)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	return Value(env, UnescapeHtml(pSelf->GetString()).c_str());
}

// string#print(stream?:stream):void
AScript_DeclareMethodPrimitive(String, print)
{
	SetMode(RSLTMODE_Void, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(String, print)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	if (args.IsInstanceOf(0, VTYPE_Stream)) {
		args.GetStream(0).Print(sig, pSelf->GetString());
	} else {
		Stream *pConsole = env.GetConsole(false);
		if (pConsole == NULL) return Value::Null;
		pConsole->Print(sig, pSelf->GetString());
	}
	return Value::Null;
}

// string#println(stream?:stream):void
AScript_DeclareMethodPrimitive(String, println)
{
	SetMode(RSLTMODE_Void, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(String, println)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	if (args.IsInstanceOf(0, VTYPE_Stream)) {
		args.GetStream(0).Println(sig, pSelf->GetString());
	} else {
		Stream *pConsole = env.GetConsole(false);
		if (pConsole == NULL) return Value::Null;
		pConsole->Println(sig, pSelf->GetString());
	}
	return Value::Null;
}

// string#encode(codec:codec)
AScript_DeclareMethodPrimitive(String, encode)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "codec", VTYPE_Codec);
}

AScript_ImplementMethod(String, encode)
{
	Object_String *pSelf = Object_String::GetSelfObj(args);
	Object_Codec *pObjCodec = dynamic_cast<Object_Codec *>(args.GetObject(0));
	Binary buff;
	if (!pObjCodec->GetEncoder()->Encode(sig, buff, pSelf->GetString())) {
		return Value::Null;
	}
	Value result;
	result.InitAsBinary(env, buff);
	return result;
}

// Assignment
Class_String::Class_String(Environment *pEnvOuter) : Class(pEnvOuter)
{
	AScript_AssignMethod(String, len);
	AScript_AssignMethod(String, isempty);
	AScript_AssignMethod(String, capitalize);
	AScript_AssignMethod(String, lower);
	AScript_AssignMethod(String, upper);
	AScript_AssignMethod(String, strip);
	AScript_AssignMethod(String, align);
	AScript_AssignMethod(String, left);
	AScript_AssignMethod(String, right);
	AScript_AssignMethod(String, mid);
	AScript_AssignMethod(String, find);
	AScript_AssignMethod(String, startswith);
	AScript_AssignMethod(String, endswith);
	AScript_AssignMethod(String, replace);
	AScript_AssignMethod(String, split);
	AScript_AssignMethod(String, each);
	AScript_AssignMethod(String, eachline);
	AScript_AssignMethod(String, format);
	AScript_AssignMethod(String, escapehtml);
	AScript_AssignMethod(String, unescapehtml);
	AScript_AssignMethod(String, print);
	AScript_AssignMethod(String, println);
	AScript_AssignMethod(String, encode);
}

bool Class_String::CastFrom(Environment &env, Signal sig, Value &value)
{
	value = Value(env, value.ToString(sig, false).c_str());
	return !sig.IsSignalled();
}

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

}
