/****************************************************************************
 * KONOHA COPYRIGHT, LICENSE NOTICE, AND DISCRIMER  
 * 
 * Copyright (c) 2005-2008, Kimio Kuramitsu <kimio at ynu.ac.jp>
 *           (c) 2008-      Konoha Software Foundation  
 * All rights reserved.
 * 
 * You may choose one of the following two licenses when you use konoha. 
 * See www.konohaware.org/license.html for further information.
 * 
 * (1) GNU General Public License 2.0      (with    KONOHA_UNDER_GPL2)
 * (2) Konoha Software Foundation License 1.0
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *  
 ****************************************************************************/

/* ************************************************************************ */

#include"commons.h"

/* ************************************************************************ */

#ifdef __cplusplus 
extern "C" {
#endif

/* ======================================================================== */
/* [constructor] */

/* ======================================================================== */
/* [method] */

/* @method Boolean! String.equals(String! s) */

INLINE
knh_bool_t knh_String_equals(String *o, knh_bytes_t s)
{
	return (o->size == s.len && knh_strncmp((char*)o->str, (char*)s.buf, s.len) == 0);
}

/* ------------------------------------------------------------------------ */

/* @method Boolean! String.startsWith(String! s) */

INLINE
knh_bool_t knh_String_startsWith(String *b, knh_bytes_t s)
{
	return knh_bytes_startsWith(knh_String_tobytes(b), s);
}

/* ------------------------------------------------------------------------ */
/* @method Boolean! String.endsWith(String! s) */

INLINE
knh_bool_t knh_String_endsWith(String *b, knh_bytes_t s)
{
	return knh_bytes_endsWith(knh_String_tobytes(b), s);
}

/* ------------------------------------------------------------------------ */

static
knh_index_t knh_bytes_indexOf(knh_bytes_t base, knh_bytes_t delim)
{
	int i, j;
	for (i = 0; i <= base.len - delim.len; i++) {
		j = 0;
		while ((j < delim.len) && (base.buf[i+j] == delim.buf[j])) {
			j++;
		}
		if (j == delim.len) return i;
	}
	return -1;
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] Int! String.indexOf(String! s) */

METHOD knh__String_indexOf(Ctx *ctx, knh_sfp_t *sfp)
{
	METHOD_RETURN(ctx, sfp, new_Int(ctx, knh_bytes_indexOf(knh_String_tobytes(sfp[0].s), knh_String_tobytes(sfp[1].s))));
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC|NULLBASE] Int! String.getSize() */

METHOD knh__String_getSize(Ctx *ctx, knh_sfp_t *sfp)
{
	if(IS_NULL(sfp[0].o)) {
		METHOD_RETURN(ctx, sfp, knh_tInt[0 - KNH_TINT_MIN]);
	}
	else {
		String *s = (String*)sfp[0].o;
		if(knh_String_isASCII(s)) {
			METHOD_RETURN(ctx, sfp, new_Int(ctx, knh_String_strlen(s)));
		}
		else {
			METHOD_RETURN(ctx, sfp, new_Int(ctx, knh_bytes_mlen(knh_String_tobytes(s))));
		}
	}
}
	
/* ------------------------------------------------------------------------ */
/* @method[STATIC|NULLBASE] String! String.opAdd:2(Any v) */

METHOD knh__String_opAdd__2(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_wbuf_t cb = knh_Context_wbuf(ctx);
	knh_format(ctx, cb.w, METHODN__s, sfp[0].o, KNH_NULL);
	knh_format(ctx, cb.w, METHODN__s, sfp[1].o, KNH_NULL);
	METHOD_RETURN(ctx, sfp, new_String__wbuf(ctx, cb));
}

/* ------------------------------------------------------------------------ */
/* @method[VARARGS|STATIC|NULLBASE] String! String.opAdd(Any v) */

METHOD knh__String_opAdd(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_sfp_t *v = sfp + 1;
	knh_vargc_t ac = knh_sfp_argc(ctx, v);
	int i;
	knh_wbuf_t cb = knh_Context_wbuf(ctx);
	knh_format(ctx, cb.w, METHODN__s, sfp[0].o, KNH_NULL);
	for(i = 0; i < ac; i++) {
		knh_format(ctx, cb.w, METHODN__s, v[i].o, KNH_NULL);
	}
	METHOD_RETURN(ctx, sfp, new_String__wbuf(ctx, cb));
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.opSub:2(String! t) */

METHOD knh__String_opSub__2(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_bytes_t base = knh_String_tobytes(sfp[0].s);
	knh_bytes_t t = knh_String_tobytes(sfp[1].s);
	knh_uchar_t c = t.buf[0];
	knh_wbuf_t wbuf = knh_Context_wbuf(ctx);
	knh_index_t i;
	for(i = 0; i < base.len; i++) {
		if(base.buf[i] == c) {
			int j;
			for(j = 1; j < t.len; j++) {
				if(base.buf[i+j] != t.buf[j]) break;
			}
			if(j == t.len) {
				i += t.len - 1;
				continue;
			}
		}
		knh_Bytes_putc(ctx, wbuf.ba, base.buf[i]);
	}
	if(base.len == knh_wbuf_size(wbuf)) {
		knh_wbuf_clear(wbuf);
		METHOD_RETURN(ctx, sfp, sfp[0].o);
	}
	else {
		METHOD_RETURN(ctx, sfp, new_String__wbuf(ctx, wbuf));
	}
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.replace:once(String! o, String! n) */

METHOD knh__String_replace__once(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_bytes_t base = knh_String_tobytes(sfp[0].s);
	knh_bytes_t old = knh_String_tobytes(sfp[1].s);
	knh_bytes_t new = knh_String_tobytes(sfp[2].s);
	knh_index_t c;
	int i;
	if ((c = knh_bytes_indexOf(base, old)) != -1) {
		knh_wbuf_t wbuf = knh_Context_wbuf(ctx);
		for(i = 0; i < c; i++)
			knh_Bytes_putc(ctx, wbuf.ba, base.buf[i]);
		for(i = 0;i < new.len; i++)
			knh_Bytes_putc(ctx, wbuf.ba, new.buf[i]);
		for(i = c + old.len; i < base.len; i++)
			knh_Bytes_putc(ctx, wbuf.ba, base.buf[i]);
		METHOD_RETURN(ctx, sfp, new_String__wbuf(ctx, wbuf));
	}
	METHOD_RETURN(ctx, sfp, sfp[0].o);
}

/* ------------------------------------------------------------------------ */

INLINE static
int knh_bytes_equals_(knh_bytes_t base, size_t s, knh_bytes_t os)
{
	size_t i;
	for(i = 1; i < os.len; i++) {
		if(base.buf[s+i] != os.buf[i]) return 0;
	}
	return 1;
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.replace(String! o, String! n) */

METHOD knh__String_replace(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_bytes_t base = knh_String_tobytes(sfp[0].s);
	knh_bytes_t os = knh_String_tobytes(sfp[1].s);
	knh_bytes_t ns = knh_String_tobytes(sfp[2].s);

	knh_wbuf_t wbuf = knh_Context_wbuf(ctx);
	
	int s = 0, i, ch = os.buf[0];
	for(i = 0; i < base.len - os.len; i++) {
		if(base.buf[i] == ch && knh_bytes_equals_(base, i, os)) {
			knh_bytes_t hs = {base.buf + s, i - s};
			knh_Bytes_write(ctx, wbuf.ba, hs);
			knh_Bytes_write(ctx, wbuf.ba, ns);
			i += os.len; s = i;
		}
	}
	if(s == 0) {
		METHOD_RETURN(ctx, sfp, sfp[0].o);
	}
	else {
		knh_bytes_t hs = {base.buf + s, base.len - s};
		knh_Bytes_write(ctx, wbuf.ba, hs);
		METHOD_RETURN(ctx, sfp, new_String__wbuf(ctx, wbuf));
	}
}


/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.opMul:2(Int! n) */

METHOD knh__String_opMul__2(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_int_t n = (sfp[1].i)->value;
	if(n <= 0) {
		METHOD_RETURN(ctx, sfp, TS_EMPTY);
	}else if(n == 1) {
		METHOD_RETURN(ctx, sfp, sfp[0].o);
	}
	else {
		knh_bytes_t base = knh_String_tobytes(sfp[0].s);
		knh_wbuf_t wbuf = knh_Context_wbuf(ctx);
		knh_int_t i;
		for(i = 0; i < n; i++) {
			knh_Bytes_write(ctx, wbuf.ba, base);
		}
		METHOD_RETURN(ctx, sfp, new_String__wbuf(ctx, wbuf));
	}
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC|NULLBASE] String! String.opMul(Int! n) */

METHOD knh__String_opMul(Ctx *ctx, knh_sfp_t *sfp)
{
	knh__String_opMul__2(ctx, sfp);
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC|NULLBASE] String! String.opDiv:2(String v) */

METHOD knh__String_opDiv__2(Ctx *ctx, knh_sfp_t *sfp)
{
	if(!IS_bString(sfp[0].o)) {
		METHOD_RETURN(ctx, sfp, TS_EMPTY);
	}
	else {
		knh_bytes_t base = knh_String_tobytes(sfp[0].s);
		knh_index_t index = knh_bytes_indexOf(base, knh_String_tobytes(sfp[1].s));
		if(index == -1) {
			METHOD_RETURN(ctx, sfp, sfp[0].o);
		}
		else {
			if(index == 0) {
				METHOD_RETURN(ctx, sfp, TS_EMPTY);
			}
			else {
				base.len = index;
				METHOD_RETURN(ctx, sfp, new_String(ctx, base, sfp[0].s));
			}
		}
	}
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC|NULLBASE] String! String.opDiv(String v) */

METHOD knh__String_opDiv(Ctx *ctx, knh_sfp_t *sfp)
{
	knh__String_opDiv__2(ctx, sfp);
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC|NULLBASE] String! String.opMod(String v) */

METHOD knh__String_opMod(Ctx *ctx, knh_sfp_t *sfp)
{
	if(!IS_bString(sfp[0].o)) {
		METHOD_RETURN(ctx, sfp, TS_EMPTY);
	}
	else {
		knh_bytes_t base = knh_String_tobytes(sfp[0].s);
		knh_bytes_t delim = knh_String_tobytes(sfp[1].s);
		knh_index_t index = knh_bytes_indexOf(base, delim);
		if(index == -1) {
			METHOD_RETURN(ctx, sfp, TS_EMPTY);
		}
		else {
			base.buf = base.buf + (index + delim.len);
			base.len = (base.len - index + delim.len) - 1;
			METHOD_RETURN(ctx, sfp, new_String(ctx, base, sfp[0].s));
		}
	}
}

/* ======================================================================== */
/* @method[STATIC] String! String.get(Int! n) */

METHOD knh__String_get(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_bytes_t base = knh_String_tobytes(sfp[0].s);
	if(knh_String_isASCII(sfp[0].s)) {
		size_t n = knh_array_index(ctx, (sfp[1].i)->value, knh_String_strlen(sfp[0].s));
		base.buf = base.buf + n;
		base.len = 1;
		METHOD_RETURN(ctx, sfp, new_String(ctx, base, sfp[0].s));
	}
	else {
		size_t off = knh_array_index(ctx, (sfp[1].i)->value, knh_bytes_mlen(base));
		knh_bytes_t sub = knh_bytes_mofflen(base, off, 1);
		METHOD_RETURN(ctx, sfp, new_String(ctx, sub, sfp[0].s));
	}
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.opOffset(Int offset, Int len) */

METHOD knh__String_opOffset(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_bytes_t base = knh_String_tobytes(sfp[0].s);
	if(knh_String_isASCII(sfp[0].s)) {
		size_t offset = IS_NULL(sfp[1].o) ? 0 : knh_array_index(ctx, (sfp[1].i)->value, base.len);
		if(IS_NULL(sfp[2].o)) {
			METHOD_RETURN(ctx, sfp, new_String(ctx, knh_bytes_last(base, offset), sfp[0].s));
		}
		else {
			knh_bytes_t sub = knh_bytes_offlen(base, offset, (sfp[2].i)->value);
			METHOD_RETURN(ctx, sfp, new_String(ctx, sub, sfp[0].s));
		}
	}
	else {
		TODO_THROW(ctx);
//		size_t mlen = knh_bytes_mlen(base);
//		size_t offset = IS_NULL(sfp[1].o) ? 0 : knh_array_index(ctx, (sfp[1].i)->value, mlen);
//		if(IS_NULL(sfp[2].o)) {
//			METHOD_RETURN(ctx, sfp, new_String(ctx, knh_bytes_last(base, offset), sfp[0].s));
//		}
//		else {
//			knh_bytes_t sub = knh_bytes_mofflen(base, offset, (sfp[2].i)->value);
//			METHOD_RETURN(ctx, sfp, new_String(ctx, sub, sfp[0].s));
//		}
	}
}

/* ======================================================================== */

String* new_String_bconv(Ctx *ctx, String *s, f_bconv bconv)
{
	knh_bytes_t base = knh_String_tobytes(s);
	Bytes *ba = knh_Context_openBConvBuf(ctx);
	bconv(ctx, KNH_NULL, base, ba);
	if(knh_strncmp((char*)(base.buf), (char*)(ba->buf), base.len) == 0) {
		knh_Context_closeBConvBuf(ctx, ba);
		return s;
	}
	else {
		s = new_String(ctx, knh_Bytes_tobytes(ba), NULL);
		knh_Context_closeBConvBuf(ctx, ba);
		return s;
	}
}

/* ------------------------------------------------------------------------ */
/* @func String:+ */

size_t bconv__toLower(Ctx *ctx, BytesConv *o, knh_bytes_t t, knh_Bytes_t *ba)
{
	int i;
	for(i = 0; i < t.len; i++) {
		if(isupper(t.buf[i])) {
			knh_Bytes_putc(ctx, ba, tolower(t.buf[i]));
		}
		else {
			knh_Bytes_putc(ctx, ba, t.buf[i]);
		}
	}
	return t.len;
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.toLower() */

METHOD knh__String_toLower(Ctx *ctx, knh_sfp_t *sfp)
{
	METHOD_RETURN(ctx, sfp, new_String_bconv(ctx, sfp[0].s, bconv__toLower));
}

/* ------------------------------------------------------------------------ */
/* @func String:+ */

size_t bconv__toUpper(Ctx *ctx, BytesConv *o, knh_bytes_t t, knh_Bytes_t *ba)
{
	int i;
	for(i = 0; i < t.len; i++) {
		if(islower(t.buf[i])) {
			knh_Bytes_putc(ctx, ba, toupper(t.buf[i]));
		}
		else {
			knh_Bytes_putc(ctx, ba, t.buf[i]);
		}
	}
	return t.len;
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.toUpper() */

METHOD knh__String_toUpper(Ctx *ctx, knh_sfp_t *sfp)
{
	METHOD_RETURN(ctx, sfp, new_String_bconv(ctx, sfp[0].s, bconv__toUpper));
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String! String.trim() */

METHOD knh__String_trim(Ctx *ctx, knh_sfp_t *sfp)
{
	knh_bytes_t t = knh_String_tobytes(sfp[0].s);
	size_t i, s = 0;
	for(i = 0; i < t.len; i++) {
		if(!isspace(t.buf[i])) break;
	}
	s = i;
	for(i = t.len - 1; s < i; i--) {
		if(!isspace(t.buf[i])) break;
	}
	if(i - s + 1 == t.len) {
		METHOD_RETURN(ctx, sfp, sfp[0].o)
	}
	t.buf = t.buf + s;
	t.len = i - s + 1;
	METHOD_RETURN(ctx, sfp, new_String(ctx, t, NULL));
}

/* ------------------------------------------------------------------------ */
/* @method[STATIC] String[] String.split(String delim) */

METHOD knh__String_split(Ctx *ctx, knh_sfp_t *sfp)
{
	String *delim = sfp[1].s;
	if(IS_NULL(delim)) {
		TODO_THROW(ctx);
	}
	else if(knh_String_strlen(delim) == 0) {
		if(knh_String_isASCII(sfp[0].s)) {
			size_t i, size = knh_String_strlen(sfp[0].s);
			Array *a = new_Array(ctx, CLASS_String, size);
			knh_bytes_t ch = knh_String_tobytes(sfp[0].s);
			ch.len = 1;
			for(i = 0; i < size; i++) {
				knh_Array_add(ctx, a, new_String(ctx, ch, sfp[0].s));
				ch.buf += 1;
			}
			METHOD_RETURN(ctx, sfp, a);
		}
		else {
			TODO_THROW(ctx);
		}
	}
	else {
		TODO_THROW(ctx);
	}
}

/* ======================================================================== */
/* [movabletext] */

/* @method void String.%s(OutputStream w, Any m) */

INLINE
void knh_String__s(Ctx *ctx, String *b, OutputStream *w, Any *m)
{
	knh_print(ctx, w, knh_String_tobytes(b));
}

/* ------------------------------------------------------------------------ */
/* @method void String.%k(OutputStream w, Any m) */

void knh_String__k(Ctx *ctx, String *o, OutputStream *w, Any *m)
{
	int_byte_t quote = '\'';
	if(knh_Object_cid(o) == CLASS_String) quote = '"';
	knh_putc(ctx, w, quote);
	knh_bytes_t t = knh_String_tobytes(o);
	knh_bytes_t sub = t;
	knh_int_t i, s = 0;
	for(i = 0; i < o->size; i++) {
		switch(t.buf[i]) {
			case '\t' :
				sub.buf = t.buf + s;
				sub.len = i - s;
				knh_print(ctx, w, sub); s = i + 1;
				knh_putc(ctx, w, '\\'); knh_putc(ctx, w, 't'); break ;
			case '\n' :
				sub.buf = t.buf + s;
				sub.len = i - s;
				knh_print(ctx, w, sub); s = i + 1;
				knh_putc(ctx, w, '\\'); knh_putc(ctx, w, 'n'); break ;
			case '\r' :
				sub.buf = t.buf + s;
				sub.len = i - s;
				knh_print(ctx, w, sub); s = i + 1;
				knh_putc(ctx, w, '\\'); knh_putc(ctx, w, 'r'); break ;
			case '\\' : 
				sub.buf = t.buf + s;
				sub.len = i - s;
				knh_print(ctx, w, sub); s = i + 1;
				knh_putc(ctx, w, '\\'); knh_putc(ctx, w, '\\'); break ;
			default:
				if(t.buf[i] == quote) {
					sub.buf = t.buf + s;
					sub.len = i - s;
					knh_print(ctx, w, sub); s = i + 1;
					knh_putc(ctx, w, '\\'); knh_putc(ctx, w, quote); 
				}
		}
	}
	if (s < t.len) {
		sub.buf = t.buf + s;
		sub.len = t.len - s;
		knh_print(ctx, w, sub);
	}
	knh_putc(ctx, w, quote);
}

/* ------------------------------------------------------------------------ */

/* ======================================================================== */
/* [mapping] */


/* ------------------------------------------------------------------------ */

#ifdef __cplusplus
}
#endif
