///////////////////////////////////////////////////////////////////////////////
//
//                          IMPORTANT NOTICE
//
// The following open source license statement does not apply to any
// entity in the Exception List published by FMSoft.
//
// For more information, please visit:
//
// https://www.fmsoft.cn/exception-list
//
//////////////////////////////////////////////////////////////////////////////
/*
 *   This file is part of MiniGUI, a mature cross-platform windowing 
 *   and Graphics User Interface (GUI) support system for embedded systems
 *   and smart IoT devices.
 * 
 *   Copyright (C) 2002~2018, Beijing FMSoft Technologies Co., Ltd.
 *   Copyright (C) 1998~2002, WEI Yongming
 * 
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 * 
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 * 
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *   Or,
 * 
 *   As this program is a library, any link to this program must follow
 *   GNU General Public License version 3 (GPLv3). If you cannot accept
 *   GPLv3, you need to be licensed from FMSoft.
 * 
 *   If you have got a commercial license of this program, please use it
 *   under the terms and conditions of the commercial license.
 * 
 *   For more information about the commercial license, please refer to
 *   <http://www.minigui.com/en/about/licensing-policy/>.
 */

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "common.h"
#include "minigui.h"
#include "gdi.h"

#ifdef _MGCHARSET_UNICODE

#include "unicode-decomp.h"
#include "unicode-comp.h"

#define CC_PART1(Page, Char) \
    ((combining_class_table_part1[Page] >= UCHAR_MAX_TABLE_INDEX) \
     ? (combining_class_table_part1[Page] - UCHAR_MAX_TABLE_INDEX) \
     : (cclass_data[combining_class_table_part1[Page]][Char]))

#define CC_PART2(Page, Char) \
    ((combining_class_table_part2[Page] >= UCHAR_MAX_TABLE_INDEX) \
     ? (combining_class_table_part2[Page] - UCHAR_MAX_TABLE_INDEX) \
     : (cclass_data[combining_class_table_part2[Page]][Char]))

#define COMBINING_CLASS(Char) \
    (((Char) <= UCHAR_LAST_CHAR_PART1) \
     ? CC_PART1 ((Char) >> 8, (Char) & 0xff) \
     : (((Char) >= 0xe0000 && (Char) <= UCHAR_LAST_CHAR) \
         ? CC_PART2 (((Char) - 0xe0000) >> 8, (Char) & 0xff) \
         : 0))

int UCharCombiningClass (Uchar32 uc)
{
    return COMBINING_CLASS (uc);
}

/* constants for hangul syllable [de]composition */
#define SBase 0xAC00 
#define LBase 0x1100 
#define VBase 0x1161 
#define TBase 0x11A7
#define LCount 19 
#define VCount 21
#define TCount 28
#define NCount (VCount * TCount)
#define SCount (LCount * NCount)

void UCharCanonicalOrdering (Uchar32 *string, int len)
{
    int i;
    int swap = 1;

    while (swap)
    {
        int last;
        swap = 0;
        last = COMBINING_CLASS (string[0]);
        for (i = 0; i < len - 1; ++i)
        {
            int next = COMBINING_CLASS (string[i + 1]);
            if (next != 0 && last > next)
            {
                int j;
                /* Percolate item leftward through string.  */
                for (j = i + 1; j > 0; --j)
                {
                    Uchar32 t;
                    if (COMBINING_CLASS (string[j - 1]) <= next)
                        break;
                    t = string[j];
                    string[j] = string[j - 1];
                    string[j - 1] = t;
                    swap = 1;
                }
                /* We're re-entering the loop looking at the old
                   character again.  */
                next = last;
            }
            last = next;
        }
    }
}

/* http://www.unicode.org/unicode/reports/tr15/#Hangul
 * r should be null or have sufficient space. Calling with r == NULL will
 * only calculate the result_len; however, a buffer with space for three
 * characters will always be big enough. */
static void decompose_hangul (Uchar32 s, Uchar32 *r, int *result_len)
{
    int SIndex = s - SBase;
    int TIndex = SIndex % TCount;

    if (r)
    {
        r[0] = LBase + SIndex / NCount;
        r[1] = VBase + (SIndex % NCount) / TCount;
    }

    if (TIndex)
    {
        if (r)
            r[2] = TBase + TIndex;
        *result_len = 3;
    }
    else
        *result_len = 2;
}

/* returns a pointer to a null-terminated UTF-8 string */
static const unsigned char * find_decomposition (Uchar32 ch, BOOL compat)
{
    int start = 0;
    int end = TABLESIZE (decomp_table);

    if (ch >= decomp_table[start].ch &&
            ch <= decomp_table[end - 1].ch) {
        while (TRUE) {
            int half = (start + end) / 2;
            if (ch == decomp_table[half].ch) {
                int offset;

                if (compat) {
                    offset = decomp_table[half].compat_offset;
                    if (offset == UCHAR_NOT_PRESENT_OFFSET)
                        offset = decomp_table[half].canon_offset;
                }
                else {
                    offset = decomp_table[half].canon_offset;
                    if (offset == UCHAR_NOT_PRESENT_OFFSET)
                        return NULL;
                }

                return &(decomp_expansion_string[offset]);
            }
            else if (half == start)
                break;
            else if (ch > decomp_table[half].ch)
                start = half;
            else
                end = half;
        }
    }

    return NULL;
}

/* L,V => LV and LV,T => LVT  */
static BOOL combine_hangul (Uchar32 a, Uchar32 b, Uchar32 *result)
{
    int LIndex = a - LBase;
    int SIndex = a - SBase;

    int VIndex = b - VBase;
    int TIndex = b - TBase;

    if (0 <= LIndex && LIndex < LCount
            && 0 <= VIndex && VIndex < VCount) {
        *result = SBase + (LIndex * VCount + VIndex) * TCount;
        return TRUE;
    }
    else if (0 <= SIndex && SIndex < SCount && (SIndex % TCount) == 0
            && 0 < TIndex && TIndex < TCount) {
        *result = a + TIndex;
        return TRUE;
    }

    return FALSE;
}

#define CI(Page, Char) \
    ((compose_table[Page] >= UCHAR_MAX_TABLE_INDEX) \
     ? (compose_table[Page] - UCHAR_MAX_TABLE_INDEX) \
     : (compose_data[compose_table[Page]][Char]))

#define COMPOSE_INDEX(Char) \
    (((Char >> 8) > (COMPOSE_TABLE_LAST)) ? 0 : CI((Char) >> 8, (Char) & 0xff))

static BOOL combine (Uchar32  a, Uchar32  b, Uchar32 *result)
{
    unsigned short index_a, index_b;

    if (combine_hangul (a, b, result))
        return TRUE;

    index_a = COMPOSE_INDEX(a);

    if (index_a >= COMPOSE_FIRST_SINGLE_START && index_a < COMPOSE_SECOND_START)
    {
        if (b == compose_first_single[index_a - COMPOSE_FIRST_SINGLE_START][0])
        {
            *result = compose_first_single[index_a - COMPOSE_FIRST_SINGLE_START][1];
            return TRUE;
        }
        else
            return FALSE;
    }

    index_b = COMPOSE_INDEX(b);

    if (index_b >= COMPOSE_SECOND_SINGLE_START)
    {
        if (a == compose_second_single[index_b - COMPOSE_SECOND_SINGLE_START][0])
        {
            *result = compose_second_single[index_b - COMPOSE_SECOND_SINGLE_START][1];
            return TRUE;
        }
        else
            return FALSE;
    }

    if (index_a >= COMPOSE_FIRST_START && index_a < COMPOSE_FIRST_SINGLE_START &&
            index_b >= COMPOSE_SECOND_START && index_b < COMPOSE_SECOND_SINGLE_START)
    {
        Uchar32 res = compose_array[index_a - COMPOSE_FIRST_START][index_b - COMPOSE_SECOND_START];

        if (res)
        {
            *result = res;
            return TRUE;
        }
    }

    return FALSE;
}

static BOOL decompose_hangul_step (Uchar32  ch, Uchar32 *a, Uchar32 *b)
{
    int SIndex, TIndex;

    if (ch < SBase || ch >= SBase + SCount)
        return FALSE;  /* not a hangul syllable */

    SIndex = ch - SBase;
    TIndex = SIndex % TCount;

    if (TIndex) {
        /* split LVT -> LV,T */
        *a = ch - TIndex;
        *b = TBase + TIndex;
    }
    else {
        /* split LV -> L,V */
        *a = LBase + SIndex / NCount;
        *b = VBase + (SIndex % NCount) / TCount;
    }

    return TRUE;
}

BOOL UCharDecompose (Uchar32 ch, Uchar32 *a, Uchar32 *b)
{
    int start = 0;
    int end = TABLESIZE (decomp_step_table);

    if (decompose_hangul_step (ch, a, b))
        return TRUE;

    /* TODO use bsearch() */
    if (ch >= decomp_step_table[start].ch &&
            ch <= decomp_step_table[end - 1].ch)
    {
        while (TRUE)
        {
            int half = (start + end) / 2;
            const decomposition_step *p = &(decomp_step_table[half]);
            if (ch == p->ch)
            {
                *a = p->a;
                *b = p->b;
                return TRUE;
            }
            else if (half == start)
                break;
            else if (ch > p->ch)
                start = half;
            else
                end = half;
        }
    }

    *a = ch;
    *b = 0;

    return FALSE;
}

BOOL UCharCompose (Uchar32 a, Uchar32 b, Uchar32 *ch)
{
    if (combine (a, b, ch))
        return TRUE;

    *ch = 0;
    return FALSE;
}

#define UTF8_COMPUTE(Char, Mask, Len)       \
if (Char < 128)                             \
{                                           \
    Len = 1;                                \
    Mask = 0x7f;                            \
}                                           \
else if ((Char & 0xe0) == 0xc0)             \
{                                           \
    Len = 2;                                \
    Mask = 0x1f;                            \
}                                           \
else if ((Char & 0xf0) == 0xe0)             \
{                                           \
    Len = 3;                                \
    Mask = 0x0f;                            \
}                                           \
else if ((Char & 0xf8) == 0xf0)             \
{                                           \
    Len = 4;                                \
    Mask = 0x07;                            \
}                                           \
else if ((Char & 0xfc) == 0xf8)             \
{                                           \
    Len = 5;                                \
    Mask = 0x03;                            \
}                                           \
else if ((Char & 0xfe) == 0xfc)             \
{                                           \
    Len = 6;                                \
    Mask = 0x01;                            \
}                                           \
else                                        \
Len = -1;

#define UTF8_LENGTH(Char)                   \
    ((Char) < 0x80 ? 1 :                    \
     ((Char) < 0x800 ? 2 :                  \
      ((Char) < 0x10000 ? 3 :               \
       ((Char) < 0x200000 ? 4 :             \
        ((Char) < 0x4000000 ? 5 : 6)))))


#define UTF8_GET(Result, Chars, Count, Mask, Len)       \
    (Result) = (Chars)[0] & (Mask);                     \
for ((Count) = 1; (Count) < (Len); ++(Count))           \
{                                                       \
    if (((Chars)[(Count)] & 0xc0) != 0x80)              \
    {                                                   \
        (Result) = -1;                                  \
        break;                                          \
    }                                                   \
    (Result) <<= 6;                                     \
    (Result) |= ((Chars)[(Count)] & 0x3f);              \
}

static Uchar32 utf8_to_uchar32 (const unsigned char *p)
{
    int i, mask = 0, len;
    Uchar32 result;
    unsigned char c = (unsigned char) *p;

    UTF8_COMPUTE (c, mask, len);
    if (len == -1)
        return (Uchar32)-1;
    UTF8_GET (result, p, i, mask, len);

    return result;
}

#define UNICODE_VALID(Char)                   \
    ((Char) < 0x110000 &&                     \
     (((Char) & 0xFFFFF800) != 0xD800))

static const unsigned char utf8_skip_data[256] = {
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};

#define utf8_next_char(p) ((p) + utf8_skip_data[*(p)])

static int utf8_strlen (const unsigned char *p, int max)
{
    int len = 0;
    const unsigned char *start = p;
    if (p == NULL && max == 0)
        return 0;

    if (max < 0) {
        while (*p) {
            p = utf8_next_char (p);
            ++len;
        }
    }
    else {
        if (max == 0 || !*p)
            return 0;

        p = utf8_next_char (p);

        while (p - start < max && *p) {
            ++len;
            p = utf8_next_char (p);
        }

        /* only do the last len increment if we got a complete
         * char (don't count partial chars)
         */
        if (p - start <= max)
            ++len;
    }

    return len;
}

int UCharFullyDecompose (Uchar32  ch, BOOL compat, Uchar32 *result, int result_len)
{
    const unsigned char *decomp;
    const unsigned char *p;

    /* Hangul syllable */
    if (ch >= SBase && ch < SBase + SCount) {
        int len, i;
        Uchar32 buffer[3];
        decompose_hangul (ch, result ? buffer : NULL, &len);
        if (result)
            for (i = 0; i < len && i < result_len; i++)
                result[i] = buffer[i];
        return len;
    }
    else if ((decomp = find_decomposition (ch, compat)) != NULL)
    {
        /* Found it.  */
        int len, i;

        len = utf8_strlen (decomp, -1);

        for (p = decomp, i = 0; i < len && i < result_len; p = utf8_next_char (p), i++)
            result[i] = utf8_to_uchar32 (p);

        return len;
    }

    /* Does not decompose */
    if (result && result_len >= 1)
        *result = ch;
    return 1;
}

#endif /* _MGCHARSET_UNICODE */

