#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "xshelper.h"

STATIC_INLINE
SV *
decode_uri_component(SV *uri){
    SV *result;
    int slen, dlen;
    U8 buf[8], *dst, *src, *bp;
    int i, hi, lo;

    /* because of our usage, suri is guaranteed to be defined */
    /* if (suri == &PL_sv_undef) return newSV(0); */
    /* if (!SvPOK(suri)) return newSV(0); */
    /* because of our usage, it's okay to make possibly destructive calls */
    /*
    uri  = sv_2mortal(newSVsv(suri)); * make a copy to make func($1) work */
    slen = SvPOK(uri) ? SvCUR(uri) : 0;
    dlen = 0;
    result = newSV(slen + 1);
   
    SvPOK_on(result);
    dst  = (U8 *)SvPV_nolen(result);
    src  = (U8 *)SvPV_nolen(uri);

    for (i = 0; i < slen; i++){
    if (src[i] == '+'){
        dst[dlen++] = ' ';
    } else if (src[i] == '%'){
        if (isxdigit(src[i+1]) && isxdigit(src[i+2])){
        strncpy((char *)buf, (char *)(src + i + 1), 2);
        buf[2] = '\0'; /* @kazuho++ */
        hi = strtol((char *)buf, NULL, 16);
        dst[dlen++] = hi;
        i += 2;
        }
        else if(src[i+1] == 'u'
            && isxdigit(src[i+2]) && isxdigit(src[i+3])
            && isxdigit(src[i+4]) && isxdigit(src[i+5])){
        strncpy((char *)buf, (char *)(src + i + 2), 4);
        buf[4] = '\0'; /* RT#39135 */
        hi = strtol((char *)buf, NULL, 16);
        i += 5;
        if (hi < 0xD800  || 0xDFFF < hi){
            bp = uvchr_to_utf8((U8 *)buf, (UV)hi);
            strncpy((char *)(dst+dlen), (char *)buf, bp - buf);
            dlen += bp - buf;
        }else{
            if (0xDC00 <= hi){ /* invalid */
            warn("U+%04X is an invalid surrogate hi\n", hi);
            }else{
            i++;
            if(src[i] == '%' && src[i+1] == 'u'
               && isxdigit(src[i+2]) && isxdigit(src[i+3])
               && isxdigit(src[i+4]) && isxdigit(src[i+5])){
                strncpy((char *)buf, (char *)(src + i + 2), 4);
                lo = strtol((char *)buf, NULL, 16);
                i += 5;
                if (lo < 0xDC00 || 0xDFFF < lo){
                warn("U+%04X is an invalid lo surrogate", lo);
                }else{
                lo += 0x10000
                    + (hi - 0xD800) * 0x400 -  0xDC00;
                bp = uvchr_to_utf8((U8 *)buf, (UV)lo);
                strncpy((char *)(dst+dlen), (char *)buf, bp - buf);
                dlen += bp - buf;
                }
            }else{
                warn("lo surrogate is missing for U+%04X", hi);
            }
            }
        }
        }else{
        dst[dlen++] = '%';
        }
    }
    else{
        dst[dlen++] = src[i];
    }
    }

    dst[dlen] = '\0'; /*  for sure; */
    SvCUR_set(result, dlen);
    return result;
}

/* split on "=".
   *key contains the key, &key_len contains the length,
   *value contains the value, &valye_len contains the length.
   if "=" is not found, key = the string, value = ''
*/
STATIC_INLINE
void
split_kv(char *start, char *end, char **key, size_t *key_len, char **value, size_t *value_len) {
    char *cur = start;
    int found_eq = 0;
    while (cur != end) {
        if (*cur == '=') {
            found_eq = 1;
            *key = start;
            *key_len = cur - start;
            cur++;
            break;
        }
        cur++;
    }
    if (found_eq) {
        *value = cur;
        *value_len = end - cur;
    } else {
        *key = start;
        *key_len = end - start;
        *value_len = 0;
    }
}

MODULE = Text::QueryString     PACKAGE = Text::QueryString

PROTOTYPES: DISABLE

void
parse(self, qs)
        SV *self;
        char *qs;
    PREINIT:
        char *cur = qs;
        char *prev = qs;
        char *key, *value;
        size_t key_len, value_len;
    PPCODE:
        PERL_UNUSED_VAR(self);
        if (qs == NULL) { /* sanity */
            XSRETURN(0);
        }

        /* First, chop chop until end of string or & or ; */
        while (*cur != '\0') {
            if (*cur == '&' || *cur == ';') {
                /* found end of this pair. look for an = sign */
                split_kv(prev, cur, &key, &key_len, &value, &value_len);
                mXPUSHs(decode_uri_component(sv_2mortal(newSVpvn(key, key_len))));
                mXPUSHs(decode_uri_component(sv_2mortal(newSVpvn(value, value_len))));
                cur++;
                prev = cur;
            } else {
                cur++;
            }
        }

        /* do we have something leftover? */
        if (prev != cur) {
            split_kv(prev, cur, &key, &key_len, &value, &value_len);
            mXPUSHs(decode_uri_component(sv_2mortal(newSVpvn(key, key_len))));
            mXPUSHs(decode_uri_component(sv_2mortal(newSVpvn(value, value_len))));
        }