/* see copyright notice in squirrel.h */
#include <squirrel.h>
#include <sqstdstring.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <assert.h>

#define MAX_FORMAT_LEN  20
#define MAX_WFORMAT_LEN 3
#define ADDITIONAL_FORMAT_SPACE (100*sizeof(SQChar))

static SQBool isfmtchr(SQChar ch)
{
    switch(ch) {
    case '-': case '+': case ' ': case '#': case '0': return SQTrue;
    }
    return SQFalse;
}

static SQInteger validate_format(HSQUIRRELVM v, SQChar *fmt, const SQChar *src, SQInteger n,SQInteger &width)
{
    SQChar *dummy;
    SQChar swidth[MAX_WFORMAT_LEN];
    SQInteger wc = 0;
    SQInteger start = n;
    fmt[0] = '%';
    while (isfmtchr(src[n])) n++;
    while (scisdigit(src[n])) {
        swidth[wc] = src[n];
        n++;
        wc++;
        if(wc>=MAX_WFORMAT_LEN)
            return sq_throwerror(v,_SC("width format too long"));
    }
    swidth[wc] = '\0';
    if(wc > 0) {
        width = scstrtol(swidth,&dummy,10);
    }
    else
        width = 0;
    if (src[n] == '.') {
        n++;

        wc = 0;
        while (scisdigit(src[n])) {
            swidth[wc] = src[n];
            n++;
            wc++;
            if(wc>=MAX_WFORMAT_LEN)
                return sq_throwerror(v,_SC("precision format too long"));
        }
        swidth[wc] = '\0';
        if(wc > 0) {
            width += scstrtol(swidth,&dummy,10);

        }
    }
    if (n-start > MAX_FORMAT_LEN )
        return sq_throwerror(v,_SC("format too long"));
    memcpy(&fmt[1],&src[start],((n-start)+1)*sizeof(SQChar));
    fmt[(n-start)+2] = '\0';
    return n;
}

SQRESULT sqstd_format(HSQUIRRELVM v,SQInteger nformatstringidx,SQInteger *outlen,SQChar **output)
{
    const SQChar *format;
    SQChar *dest;
    SQChar fmt[MAX_FORMAT_LEN];
    sq_getstring(v,nformatstringidx,&format);
    SQInteger format_size = sq_getsize(v,nformatstringidx);
    SQInteger allocated = (format_size+2)*sizeof(SQChar);
    dest = sq_getscratchpad(v,allocated);
    SQInteger n = 0,i = 0, nparam = nformatstringidx+1, w = 0;
    //while(format[n] != '\0')
    while(n < format_size)
    {
        if(format[n] != '%') {
            assert(i < allocated);
            dest[i++] = format[n];
            n++;
        }
        else if(format[n+1] == '%') { //handles %%
                dest[i++] = '%';
                n += 2;
        }
        else {
            n++;
            if( nparam > sq_gettop(v) )
                return sq_throwerror(v,_SC("not enough parameters for the given format string"));
            n = validate_format(v,fmt,format,n,w);
            if(n < 0) return -1;
            SQInteger addlen = 0;
            SQInteger valtype = 0;
            const SQChar *ts = NULL;
            SQInteger ti = 0;
            SQFloat tf = 0;
            switch(format[n]) {
            case 's':
                if(SQ_FAILED(sq_getstring(v,nparam,&ts)))
                    return sq_throwerror(v,_SC("string expected for the specified format"));
                addlen = (sq_getsize(v,nparam)*sizeof(SQChar))+((w+1)*sizeof(SQChar));
                valtype = 's';
                break;
            case 'i': case 'd': case 'o': case 'u':  case 'x':  case 'X':
#ifdef _SQ64
                {
                size_t flen = scstrlen(fmt);
                SQInteger fpos = flen - 1;
                SQChar f = fmt[fpos];
                const SQChar *prec = (const SQChar *)_PRINT_INT_PREC;
                while(*prec != _SC('\0')) {
                    fmt[fpos++] = *prec++;
                }
                fmt[fpos++] = f;
                fmt[fpos++] = _SC('\0');
                }
#endif
            case 'c':
                if(SQ_FAILED(sq_getinteger(v,nparam,&ti)))
                    return sq_throwerror(v,_SC("integer expected for the specified format"));
                addlen = (ADDITIONAL_FORMAT_SPACE)+((w+1)*sizeof(SQChar));
                valtype = 'i';
                break;
            case 'f': case 'g': case 'G': case 'e':  case 'E':
                if(SQ_FAILED(sq_getfloat(v,nparam,&tf)))
                    return sq_throwerror(v,_SC("float expected for the specified format"));
                addlen = (ADDITIONAL_FORMAT_SPACE)+((w+1)*sizeof(SQChar));
                valtype = 'f';
                break;
            default:
                return sq_throwerror(v,_SC("invalid format"));
            }
            n++;
            allocated += addlen + sizeof(SQChar);
            dest = sq_getscratchpad(v,allocated);
            switch(valtype) {
            case 's': i += scsprintf(&dest[i],allocated,fmt,ts); break;
            case 'i': i += scsprintf(&dest[i],allocated,fmt,ti); break;
            case 'f': i += scsprintf(&dest[i],allocated,fmt,tf); break;
            };
            nparam ++;
        }
    }
    *outlen = i;
    dest[i] = '\0';
    *output = dest;
    return SQ_OK;
}

static SQInteger _string_printf(HSQUIRRELVM v)
{
    SQChar *dest = NULL;
    SQInteger length = 0;
    if(SQ_FAILED(sqstd_format(v,2,&length,&dest)))
        return -1;
    
    SQPRINTFUNCTION printfunc = sq_getprintfunc(v);
    if(printfunc) printfunc(v,dest);

    return 0;
}

static SQInteger _string_format(HSQUIRRELVM v)
{
    SQChar *dest = NULL;
    SQInteger length = 0;
    if(SQ_FAILED(sqstd_format(v,2,&length,&dest)))
        return -1;
    sq_pushstring(v,dest,length);
    return 1;
}

static void __strip_l(const SQChar *str,const SQChar **start)
{
    const SQChar *t = str;
    while(((*t) != '\0') && scisspace(*t)){ t++; }
    *start = t;
}

static void __strip_r(const SQChar *str,SQInteger len,const SQChar **end)
{
    if(len == 0) {
        *end = str;
        return;
    }
    const SQChar *t = &str[len-1];
    while(t >= str && scisspace(*t)) { t--; }
    *end = t + 1;
}

static SQInteger _string_strip(HSQUIRRELVM v)
{
    const SQChar *str,*start,*end;
    sq_getstring(v,2,&str);
    SQInteger len = sq_getsize(v,2);
    __strip_l(str,&start);
    __strip_r(str,len,&end);
    sq_pushstring(v,start,end - start);
    return 1;
}

static SQInteger _string_lstrip(HSQUIRRELVM v)
{
    const SQChar *str,*start;
    sq_getstring(v,2,&str);
    __strip_l(str,&start);
    sq_pushstring(v,start,-1);
    return 1;
}

static SQInteger _string_rstrip(HSQUIRRELVM v)
{
    const SQChar *str,*end;
    sq_getstring(v,2,&str);
    SQInteger len = sq_getsize(v,2);
    __strip_r(str,len,&end);
    sq_pushstring(v,str,end - str);
    return 1;
}

static SQInteger _string_split(HSQUIRRELVM v)
{
    const SQChar *str,*seps;
    SQChar *stemp;
    sq_getstring(v,2,&str);
    sq_getstring(v,3,&seps);
    SQInteger sepsize = sq_getsize(v,3);
    if(sepsize == 0) return sq_throwerror(v,_SC("empty separators string"));
    SQInteger memsize = (sq_getsize(v,2)+1)*sizeof(SQChar);
    stemp = sq_getscratchpad(v,memsize);
    memcpy(stemp,str,memsize);
    SQChar *start = stemp;
    SQChar *end = stemp;
    sq_newarray(v,0);
    while(*end != '\0')
    {
        SQChar cur = *end;
        for(SQInteger i = 0; i < sepsize; i++)
        {
            if(cur == seps[i])
            {
                *end = 0;
                sq_pushstring(v,start,-1);
                sq_arrayappend(v,-2);
                start = end + 1;
                break;
            }
        }
        end++;
    }
    if(end != start)
    {
        sq_pushstring(v,start,-1);
        sq_arrayappend(v,-2);
    }
    return 1;
}

static SQInteger _string_escape(HSQUIRRELVM v)
{
    const SQChar *str;
    SQChar *dest,*resstr;
    SQInteger size;
    sq_getstring(v,2,&str);
    size = sq_getsize(v,2);
    if(size == 0) {
        sq_push(v,2);
        return 1;
    }
#ifdef SQUNICODE
#if WCHAR_SIZE == 2
    const SQChar *escpat = _SC("\\x%04x");
    const SQInteger maxescsize = 6;
#else //WCHAR_SIZE == 4
    const SQChar *escpat = _SC("\\x%08x");
    const SQInteger maxescsize = 10;
#endif
#else
    const SQChar *escpat = _SC("\\x%02x");
    const SQInteger maxescsize = 4;
#endif
    SQInteger destcharsize = (size * maxescsize); //assumes every char could be escaped
    resstr = dest = (SQChar *)sq_getscratchpad(v,destcharsize * sizeof(SQChar));
    SQChar c;
    SQChar escch;
    SQInteger escaped = 0;
    for(int n = 0; n < size; n++){
        c = *str++;
        escch = 0;
        if(scisprint(c) || c == 0) {
            switch(c) {
            case '\a': escch = 'a'; break;
            case '\b': escch = 'b'; break;
            case '\t': escch = 't'; break;
            case '\n': escch = 'n'; break;
            case '\v': escch = 'v'; break;
            case '\f': escch = 'f'; break;
            case '\r': escch = 'r'; break;
            case '\\': escch = '\\'; break;
            case '\"': escch = '\"'; break;
            case '\'': escch = '\''; break;
            case 0: escch = '0'; break;
            }
            if(escch) {
                *dest++ = '\\';
                *dest++ = escch;
                escaped++;
            }
            else {
                *dest++ = c;
            }
        }
        else {

            dest += scsprintf(dest, destcharsize, escpat, c);
            escaped++;
        }
    }

    if(escaped) {
        sq_pushstring(v,resstr,dest - resstr);
    }
    else {
        sq_push(v,2); //nothing escaped
    }
    return 1;
}

static SQInteger _string_startswith(HSQUIRRELVM v)
{
    const SQChar *str,*cmp;
    sq_getstring(v,2,&str);
    sq_getstring(v,3,&cmp);
    SQInteger len = sq_getsize(v,2);
    SQInteger cmplen = sq_getsize(v,3);
    SQBool ret = SQFalse;
    if(cmplen <= len) {
        ret = memcmp(str,cmp,sq_rsl(cmplen)) == 0 ? SQTrue : SQFalse;
    }
    sq_pushbool(v,ret);
    return 1;
}

static SQInteger _string_endswith(HSQUIRRELVM v)
{
    const SQChar *str,*cmp;
    sq_getstring(v,2,&str);
    sq_getstring(v,3,&cmp);
    SQInteger len = sq_getsize(v,2);
    SQInteger cmplen = sq_getsize(v,3);
    SQBool ret = SQFalse;
    if(cmplen <= len) {
        ret = memcmp(&str[len - cmplen],cmp,sq_rsl(cmplen)) == 0 ? SQTrue : SQFalse;
    }
    sq_pushbool(v,ret);
    return 1;
}

#define SETUP_REX(v) \
    SQRex *self = NULL; \
    sq_getinstanceup(v,1,(SQUserPointer *)&self,0);

static SQInteger _rexobj_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size))
{
    SQRex *self = ((SQRex *)p);
    sqstd_rex_free(self);
    return 1;
}

static SQInteger _regexp_match(HSQUIRRELVM v)
{
    SETUP_REX(v);
    const SQChar *str;
    sq_getstring(v,2,&str);
    if(sqstd_rex_match(self,str) == SQTrue)
    {
        sq_pushbool(v,SQTrue);
        return 1;
    }
    sq_pushbool(v,SQFalse);
    return 1;
}

static void _addrexmatch(HSQUIRRELVM v,const SQChar *str,const SQChar *begin,const SQChar *end)
{
    sq_newtable(v);
    sq_pushstring(v,_SC("begin"),-1);
    sq_pushinteger(v,begin - str);
    sq_rawset(v,-3);
    sq_pushstring(v,_SC("end"),-1);
    sq_pushinteger(v,end - str);
    sq_rawset(v,-3);
}

static SQInteger _regexp_search(HSQUIRRELVM v)
{
    SETUP_REX(v);
    const SQChar *str,*begin,*end;
    SQInteger start = 0;
    sq_getstring(v,2,&str);
    if(sq_gettop(v) > 2) sq_getinteger(v,3,&start);
    if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) {
        _addrexmatch(v,str,begin,end);
        return 1;
    }
    return 0;
}

static SQInteger _regexp_capture(HSQUIRRELVM v)
{
    SETUP_REX(v);
    const SQChar *str,*begin,*end;
    SQInteger start = 0;
    sq_getstring(v,2,&str);
    if(sq_gettop(v) > 2) sq_getinteger(v,3,&start);
    if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) {
        SQInteger n = sqstd_rex_getsubexpcount(self);
        SQRexMatch match;
        sq_newarray(v,0);
        for(SQInteger i = 0;i < n; i++) {
            sqstd_rex_getsubexp(self,i,&match);
            if(match.len > 0)
                _addrexmatch(v,str,match.begin,match.begin+match.len);
            else
                _addrexmatch(v,str,str,str); //empty match
            sq_arrayappend(v,-2);
        }
        return 1;
    }
    return 0;
}

static SQInteger _regexp_subexpcount(HSQUIRRELVM v)
{
    SETUP_REX(v);
    sq_pushinteger(v,sqstd_rex_getsubexpcount(self));
    return 1;
}

static SQInteger _regexp_constructor(HSQUIRRELVM v)
{
    const SQChar *error,*pattern;
    sq_getstring(v,2,&pattern);
    SQRex *rex = sqstd_rex_compile(pattern,&error);
    if(!rex) return sq_throwerror(v,error);
    sq_setinstanceup(v,1,rex);
    sq_setreleasehook(v,1,_rexobj_releasehook);
    return 0;
}

static SQInteger _regexp__typeof(HSQUIRRELVM v)
{
    sq_pushstring(v,_SC("regexp"),-1);
    return 1;
}

#define _DECL_REX_FUNC(name,nparams,pmask) {_SC(#name),_regexp_##name,nparams,pmask}
static const SQRegFunction rexobj_funcs[]={
    _DECL_REX_FUNC(constructor,2,_SC(".s")),
    _DECL_REX_FUNC(search,-2,_SC("xsn")),
    _DECL_REX_FUNC(match,2,_SC("xs")),
    _DECL_REX_FUNC(capture,-2,_SC("xsn")),
    _DECL_REX_FUNC(subexpcount,1,_SC("x")),
    _DECL_REX_FUNC(_typeof,1,_SC("x")),
    {NULL,(SQFUNCTION)0,0,NULL}
};
#undef _DECL_REX_FUNC

#define _DECL_FUNC(name,nparams,pmask) {_SC(#name),_string_##name,nparams,pmask}
static const SQRegFunction stringlib_funcs[]={
    _DECL_FUNC(format,-2,_SC(".s")),
    _DECL_FUNC(printf,-2,_SC(".s")),
    _DECL_FUNC(strip,2,_SC(".s")),
    _DECL_FUNC(lstrip,2,_SC(".s")),
    _DECL_FUNC(rstrip,2,_SC(".s")),
    _DECL_FUNC(split,3,_SC(".ss")),
    _DECL_FUNC(escape,2,_SC(".s")),
    _DECL_FUNC(startswith,3,_SC(".ss")),
    _DECL_FUNC(endswith,3,_SC(".ss")),
    {NULL,(SQFUNCTION)0,0,NULL}
};
#undef _DECL_FUNC


SQInteger sqstd_register_stringlib(HSQUIRRELVM v)
{
    sq_pushstring(v,_SC("regexp"),-1);
    sq_newclass(v,SQFalse);
    SQInteger i = 0;
    while(rexobj_funcs[i].name != 0) {
        const SQRegFunction &f = rexobj_funcs[i];
        sq_pushstring(v,f.name,-1);
        sq_newclosure(v,f.f,0);
        sq_setparamscheck(v,f.nparamscheck,f.typemask);
        sq_setnativeclosurename(v,-1,f.name);
        sq_newslot(v,-3,SQFalse);
        i++;
    }
    sq_newslot(v,-3,SQFalse);

    i = 0;
    while(stringlib_funcs[i].name!=0)
    {
        sq_pushstring(v,stringlib_funcs[i].name,-1);
        sq_newclosure(v,stringlib_funcs[i].f,0);
        sq_setparamscheck(v,stringlib_funcs[i].nparamscheck,stringlib_funcs[i].typemask);
        sq_setnativeclosurename(v,-1,stringlib_funcs[i].name);
        sq_newslot(v,-3,SQFalse);
        i++;
    }
    return 1;
}