/*
  NOTE:
  The early netatalk 2.x was based on UCS-2.
  UCS-2 don't support chars above U+10000.
  Recent netatalk is based on UTF-16.
  UTF-16 can support chars above U+10000, using Surrogate Pair.
  However, Surrogate Pair is complex, dirty, filthy and disagreeable.
  There might still be latent bugs...
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <arpa/inet.h>
#include <atalk/logger.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <atalk/unicode.h>
#include <atalk/byteorder.h>

#include "precompose.h"

/*!
 * @brief Convert a string to lower case.
 * @returns True if any char is converted
 * @note surrogate pair support
 */
int strlower_w(ucs2_t *s)
{
    int ret = 0;

    while (*s) {
        if ((0xD800 <= *s) && (*s < 0xDC00)) {
            if ((0xDC00 <= s[1]) && (s[1] < 0xE000)) {
                uint32_t s_sp = (uint32_t) * s << 16 | (uint32_t)s[1];
                uint32_t v_sp = tolower_sp(s_sp);

                if (v_sp != s_sp) {
                    *s = v_sp >> 16;
                    s++;
                    *s = v_sp & 0xFFFF;
                    ret = 1;
                }
            }
        } else {
            ucs2_t v = tolower_w(*s);

            if (v != *s) {
                *s = v;
                ret = 1;
            }
        }

        s++;
    }

    return ret;
}

/*!
 * @brief Convert a string to upper case.
 * @returns True if any char is converted
 * @note surrogate pair support
 */
int strupper_w(ucs2_t *s)
{
    int ret = 0;

    while (*s) {
        if ((0xD800 <= *s) && (*s < 0xDC00)) {
            if ((0xDC00 <= s[1]) && (s[1] < 0xE000)) {
                uint32_t s_sp = (uint32_t) * s << 16 | (uint32_t)s[1];
                uint32_t v_sp = toupper_sp(s_sp);

                if (v_sp != s_sp) {
                    *s = v_sp >> 16;
                    s++;
                    *s = v_sp & 0xFFFF;
                    ret = 1;
                }
            }
        } else {
            ucs2_t v = toupper_w(*s);

            if (v != *s) {
                *s = v;
                ret = 1;
            }
        }

        s++;
    }

    return ret;
}

/*!
 * @brief wide strlen()
 *
 * Count the number of characters in a UTF-16 string.
 * @note one surrogate pair is two characters.
 */
size_t strlen_w(const ucs2_t *src)
{
    size_t len;

    for (len = 0; *src++; len++) ;

    return len;
}

/*!
 * @brief wide strnlen()
 *
 * Count up to max number of characters in a UTF-16 string.
 * @note one surrogate pair is two characters.
 */

size_t strnlen_w(const ucs2_t *src, size_t max)
{
    size_t len;

    for (len = 0; *src++ && (len < max); len++) ;

    return len;
}

/*!
 * @brief wide strchr()
 * @note hi and lo of surrogate pair are separately processed.
 */
ucs2_t *strchr_w(const ucs2_t *s, ucs2_t c)
{
    while (*s != 0) {
        if (c == *s) {
            return (ucs2_t *)s;
        }

        s++;
    }

    if (c == *s) {
        return (ucs2_t *)s;
    }

    return NULL;
}

/*!
 * @brief wide strcasechr()
 * @note separately process BMP and surrogate pair
 */
ucs2_t *strcasechr_w(const ucs2_t *s, ucs2_t c)
{
    while (*s != 0) {
        if (tolower_w(c) == tolower_w(*s)) {
            return (ucs2_t *)s;
        }

        s++;
    }

    if (c == *s) {
        return (ucs2_t *)s;
    }

    return NULL;
}

/*!
 * @brief sp strcasechr()
 * @note separately process BMP and surrogate pair
 */
ucs2_t *strcasechr_sp(const ucs2_t *s, uint32_t c_sp)
{
    if (*s == 0) {
        return NULL;
    }

    while (s[1] != 0) {
        if (tolower_sp(c_sp) == tolower_sp((uint32_t) * s << 16 | (uint32_t)s[1])) {
            return (ucs2_t *)s;
        }

        s++;
    }

    return NULL;
}

/*!
 * @brief wide strcmp()
 * @note no problem of surrogate pair
 */
int strcmp_w(const ucs2_t *a, const ucs2_t *b)
{
    while (*b && *a == *b) {
        a++;
        b++;
    }

    return *a - *b;
    /* warning: if *a != *b and both are not 0 we retrun a random
       greater or lesser than 0 number not realted to which
       string is longer */
}

/*!
 * @brief wide strncmp()
 * @note no problem of surrogate pair
 */
int strncmp_w(const ucs2_t *a, const ucs2_t *b, size_t len)
{
    size_t n = 0;

    while ((n < len) && *b && *a == *b) {
        a++;
        b++;
        n++;
    }

    return (len - n) ? (*a - *b) : 0;
}

/*!
 * @brief wide strstr()
 * @note no problem of surrogate pair
 */
ucs2_t *strstr_w(const ucs2_t *s, const ucs2_t *ins)
{
    ucs2_t *r;
    size_t inslen;

    if (!s || !*s || !ins || !*ins) {
        return NULL;
    }

    inslen = strlen_w(ins);
    r = (ucs2_t *)s;

    while ((r = strchr_w(r, *ins))) {
        if (strncmp_w(r, ins, inslen) == 0) {
            return r;
        }

        r++;
    }

    return NULL;
}

/*!
 * @brief wide strcasestr()
 * @note surrogate pair support
 */
ucs2_t *strcasestr_w(const ucs2_t *s, const ucs2_t *ins)
{
    ucs2_t *r;
    size_t inslen;

    if (!s || !*s || !ins || !*ins) {
        return NULL;
    }

    inslen = strlen_w(ins);
    r = (ucs2_t *)s;

    if ((0xD800 <= *ins) && (*ins < 0xDC00)) {
        if ((0xDC00 <= ins[1]) && (ins[1] < 0xE000)) {
            uint32_t ins_sp = (uint32_t) * ins << 16 | (uint32_t)ins[1];

            while ((r = strcasechr_sp(r, ins_sp))) {
                if (strncasecmp_w(r, ins, inslen) == 0) {
                    return r;
                }

                r++;
            }
        } else {
            return NULL; /* illegal sequence */
        }
    } else {
        while ((r = strcasechr_w(r, *ins))) {
            if (strncasecmp_w(r, ins, inslen) == 0) {
                return r;
            }

            r++;
        }
    }

    return NULL;
}

/*!
 * @brief wide strcasecmp()
 *
 * case insensitive string comparison
 * @note surrogate pair support
 */
int strcasecmp_w(const ucs2_t *a, const ucs2_t *b)
{
    int ret;

    while (*a && *b) {
        if ((0xD800 <= *a) && (*a < 0xDC00)) {
            if ((ret = tolower_sp((uint32_t) * a << 16 | (uint32_t)a[1]) - tolower_sp((
                           uint32_t) * b << 16 | (uint32_t)b[1]))) {
                return ret;
            }

            a++;
            b++;

            if (!(*a && *b)) {
                return (tolower_w(*a) - tolower_w(*b));    /* avoid buffer over run */
            }
        } else {
            if ((ret = tolower_w(*a) - tolower_w(*b))) {
                return ret;
            }
        }

        a++;
        b++;
    }

    return (tolower_w(*a) - tolower_w(*b));
}

/*!
 * @brief wide strncasecmp()
 *
 * case insensitive string comparison, length limited
 * @note compare up to 'len+1' if 'len' isolate surrogate pair
 */
int strncasecmp_w(const ucs2_t *a, const ucs2_t *b, size_t len)
{
    size_t n = 0;
    int ret;

    while ((n < len) && *a && *b) {
        if ((0xD800 <= *a) && (*a < 0xDC00)) {
            if ((ret = tolower_sp((uint32_t) * a << 16 | (uint32_t)a[1]) - tolower_sp((
                           uint32_t) * b << 16 | (uint32_t)b[1]))) {
                return ret;
            }

            a++;
            b++;
            n++;

            if (!((n < len) && *a && *b)) {
                return (tolower_w(*a) - tolower_w(*b));
            }
        } else {
            if ((ret = tolower_w(*a) - tolower_w(*b))) {
                return ret;
            }
        }

        a++;
        b++;
        n++;
    }

    return (len - n) ? (tolower_w(*a) - tolower_w(*b)) : 0;
}

/*!
 * @brief wide strndup()
 *
 * duplicate string
 * @note not check isolation of surrogate pair
 * @note if len == 0 then duplicate the whole string
 */
ucs2_t *strndup_w(const ucs2_t *src, size_t len)
{
    ucs2_t *dest;

    if (!len) {
        len = strlen_w(src);
    }

    dest = (ucs2_t *)malloc((len + 1) * sizeof(ucs2_t));

    if (!dest) {
        LOG(log_error, logtype_default, "strdup_w: out of memory!");
        return NULL;
    }

    memcpy(dest, src, len * sizeof(ucs2_t));
    dest[len] = 0;
    return dest;
}

/*!
 * @brief wide strdup()
 *
 * duplicate string
 * @note no problem of surrogate pair
 */
ucs2_t *strdup_w(const ucs2_t *src)
{
    return strndup_w(src, 0);
}

/*!
 * binary search for pre|decomposition
 */
static ucs2_t do_precomposition(unsigned int base, unsigned int comb)
{
    int min = 0;
    int max = PRECOMP_COUNT - 1;
    int mid;
    uint32_t sought = (base << 16) | comb, that;

    /* binary search */
    while (max >= min) {
        mid = (min + max) / 2;
        that = (precompositions[mid].base << 16) | (precompositions[mid].comb);

        if (that < sought) {
            min = mid + 1;
        } else if (that > sought) {
            max = mid - 1;
        } else {
            return precompositions[mid].replacement;
        }
    }

    /* no match */
    return 0;
}

/* ------------------------ */
static uint32_t do_precomposition_sp(unsigned int base_sp, unsigned int comb_sp)
{
    int min = 0;
    int max = PRECOMP_SP_COUNT - 1;
    int mid;
    uint64_t sought_sp = ((uint64_t)base_sp << 32) | (uint64_t)comb_sp, that_sp;

    /* binary search */
    while (max >= min) {
        mid = (min + max) / 2;
        that_sp = ((uint64_t)precompositions_sp[mid].base_sp << 32) | ((
                      uint64_t)precompositions_sp[mid].comb_sp);

        if (that_sp < sought_sp) {
            min = mid + 1;
        } else if (that_sp > sought_sp) {
            max = mid - 1;
        } else {
            return precompositions_sp[mid].replacement_sp;
        }
    }

    /* no match */
    return 0;
}

/* -------------------------- */
static uint32_t do_decomposition(ucs2_t base)
{
    int min = 0;
    int max = DECOMP_COUNT - 1;
    int mid;
    uint32_t sought = base;
    uint32_t result, that;

    /* binary search */
    while (max >= min) {
        mid = (min + max) / 2;
        that = decompositions[mid].replacement;

        if (that < sought) {
            min = mid + 1;
        } else if (that > sought) {
            max = mid - 1;
        } else {
            result = (decompositions[mid].base << 16) | (decompositions[mid].comb);
            return result;
        }
    }

    /* no match */
    return 0;
}

/* -------------------------- */
static uint64_t do_decomposition_sp(unsigned int base_sp)
{
    int min = 0;
    int max = DECOMP_SP_COUNT - 1;
    int mid;
    uint32_t sought_sp = base_sp;
    uint32_t that_sp;
    uint64_t result_sp;

    /* binary search */
    while (max >= min) {
        mid = (min + max) / 2;
        that_sp = decompositions_sp[mid].replacement_sp;

        if (that_sp < sought_sp) {
            min = mid + 1;
        } else if (that_sp > sought_sp) {
            max = mid - 1;
        } else {
            result_sp = ((uint64_t)decompositions_sp[mid].base_sp << 32) | ((
                            uint64_t)decompositions_sp[mid].comb_sp);
            return result_sp;
        }
    }

    /* no match */
    return 0;
}

/*!
 * @brief pre|decomposition
 *
 *  we can't use static, this stuff needs to be reentrant
 *  static char comp[MAXPATHLEN +1];
 *
 *  We don't implement Singleton and Canonical Ordering.
 *  We ignore CompositionExclusions.txt
 *  because they cause the problem of the roundtrip
 *  such as Dancing Icon.
 *
 *  exclude U2000-U2FFF, UFE30-UFE4F and U2F800-U2FA1F ranges
 *  in precompose.h from composition according to AFP 3.x spec
 */
size_t precompose_w(ucs2_t *name, size_t inplen, ucs2_t *comp, size_t *outlen)
{
    size_t i;
    ucs2_t base, comb;
    uint32_t base_sp, comb_sp;
    ucs2_t *in, *out;
    ucs2_t lindex, vindex;
    ucs2_t result;
    uint32_t result_sp;
    size_t o_len = *outlen;

    if (!inplen || (inplen & 1) || inplen > o_len) {
        return (size_t) -1;
    }

    i = 0;
    in  = name;
    out = comp;
    base = *in;

    while (*outlen > 2) {
        i += 2;

        if (i == inplen) {
            *out = base;
            out++;
            *out = 0;
            *outlen -= 2;
            return o_len - *outlen;
        }

        in++;
        comb = *in;
        result = 0;

        /* Non-Combination Character */
        if (comb < 0x300) ;
        /* Unicode Standard Annex #15 A10.3 Hangul Composition */
        /* Step 1: leading consonant, vowel */
        else if ((VBASE <= comb) && (comb <= VBASE + VCOUNT)) {
            if ((LBASE <= base) && (base < LBASE + LCOUNT)) {
                result = 1;
                lindex = base - LBASE;
                vindex = comb - VBASE;
                base = SBASE + (lindex * VCOUNT + vindex) * TCOUNT;
            }
        }
        /* Step 2: leading vowel, trailing consonant */
        else if ((TBASE < comb) && (comb < TBASE + TCOUNT)) {
            if ((SBASE <= base) && (base < SBASE + SCOUNT)
                    && (((base - SBASE) % TCOUNT) == 0)) {
                result = 1;
                base += comb - TBASE;
            }
        }
        /* Binary Search for Surrogate Pair */
        else if ((0xD800 <= base) && (base < 0xDC00)) {
            if ((0xDC00 <= comb) && (comb < 0xE000) && (i + 6 <= inplen)) {
                base_sp = ((uint32_t)base << 16) | (uint32_t)comb;

                do {
                    comb_sp = ((uint32_t)in[1] << 16) | (uint32_t)in[2];

                    if ((result_sp = do_precomposition_sp(base_sp, comb_sp))) {
                        base_sp = result_sp;
                        i += 4;
                        in += 2;
                    }
                } while ((i + 6 <= inplen) && result_sp) ;

                *out = base_sp >> 16;
                out++;
                *outlen -= 2;

                if (*outlen <= 2) {
                    errno = E2BIG;
                    return (size_t) -1;
                }

                *out = base_sp & 0xFFFF;
                out++;
                *outlen -= 2;
                i += 2;

                if (i == inplen) {
                    out++;
                    *out = 0;
                    return o_len - *outlen;
                }

                in++;
                base = *in;
                result = 1;
            }
        }
        /* Binary Search for BMP */
        else if ((result = do_precomposition(base, comb))) {
            base = result;
        }

        if (!result) {
            *out = base;
            out++;
            *outlen -= 2;
            base = comb;
        }
    }

    errno = E2BIG;
    return (size_t) -1;
}

/* --------------- */
size_t decompose_w(ucs2_t *name, size_t inplen, ucs2_t *comp, size_t *outlen)
{
    size_t i;
    size_t comblen;
    ucs2_t base, comb[COMBBUFLEN];
    uint32_t base_sp;
    ucs2_t sindex, tjamo;
    ucs2_t *in, *out;
    unsigned int result;
    uint64_t result_sp;
    size_t o_len = *outlen;

    if (!inplen || (inplen & 1)) {
        return (size_t) -1;
    }

    i = 0;
    in  = name;
    out = comp;

    while (i < inplen) {
        base = *in;
        comblen = 0;

        /* check ASCII first. this is frequent. */
        if (base <= 0x007f) ;
        /* Unicode Standard Annex #15 A10.2 Hangul Decomposition */
        else if ((SBASE <= base) && (base < SBASE + SCOUNT)) {
            sindex = base - SBASE;
            base = LBASE + sindex / NCOUNT;
            comb[COMBBUFLEN - 2] = VBASE + (sindex % NCOUNT) / TCOUNT;

            /* <L,V> */
            if ((tjamo = TBASE + sindex % TCOUNT) == TBASE) {
                comb[COMBBUFLEN - 1] = comb[COMBBUFLEN - 2];
                comblen = 1;
            }
            /* <L,V,T> */
            else {
                comb[COMBBUFLEN - 1] = tjamo;
                comblen = 2;
            }
        }
        /* Binary Search for Surrogate Pair */
        else if ((0xD800 <= base) && (base < 0xDC00)) {
            if (i + 2 < inplen) {
                base_sp = ((uint32_t)base << 16) | (uint32_t)in[1];

                do {
                    if (!(result_sp = do_decomposition_sp(base_sp))) {
                        break;
                    }

                    comblen += 2;
                    base_sp = result_sp >> 32;
                    comb[COMBBUFLEN - comblen] = (result_sp >> 16) & 0xFFFF; /* hi */
                    comb[COMBBUFLEN - comblen + 1] = result_sp & 0xFFFF;    /* lo */
                } while (comblen < MAXCOMBSPLEN);

                if (*outlen < (comblen + 1) << 1) {
                    errno = E2BIG;
                    return (size_t) -1;
                }

                *out = base_sp >> 16;   /* hi */
                out++;
                *outlen -= 2;
                base = base_sp & 0xFFFF; /* lo */
                i += 2;
                in++;
            }
        }
        /* Binary Search for BMP */
        else {
            do {
                if (!(result = do_decomposition(base))) {
                    break;
                }

                comblen++;
                base = result  >> 16;
                comb[COMBBUFLEN - comblen] = result & 0xFFFF;
            } while ((0x007f < base) && (comblen < MAXCOMBLEN));
        }

        if (*outlen < (comblen + 1) << 1) {
            errno = E2BIG;
            return (size_t) -1;
        }

        *out = base;
        out++;
        *outlen -= 2;

        while (comblen > 0) {
            *out = comb[COMBBUFLEN - comblen];
            out++;
            *outlen -= 2;
            comblen--;
        }

        i += 2;
        in++;
    }

    *out = 0;
    return o_len - *outlen;
}
