#include "types.h"
#include "encoding.h"

// extract unicode character from utf-8
// optionally pass buffer top (points to 1 byte after buffer end)
// returns -1 on error
unichar charFromUTF8( const void *ptr, u32 &size, const void *top )
{
    const u8 *p = (const u8 *)ptr;
    const u8 *e = (const u8 *)top;
    if ( !e )
        e = p + 4;
    if ( e > p )
    {
        if ( *p < 0x80 )
        {
            // ASCII
            size = 1;
            return *p;
        }
        else if ( p + 1 < e && *p >= 0xc2 && *p <= 0xdf && ( p[1] & 0xc0 ) == 0x80 )
        {
            // 2-byte encoded
            size = 2;
            return (p[1] & 0x3f) | ((*p & 0x1f) << 6);
        }
        else if ( p + 2 < e && *p >= 0xe0 && *p <= 0xef && ( p[1] & 0xc0 ) == 0x80 && ( p[2] & 0xc0 ) == 0x80 )
        {
            // 3-byte encoded
            size = 3;
            return (p[2] & 0x3f) | ((p[1] & 0x3f) << 6) | ((*p & 0x0f) << 12);
        }
        else if ( p + 3 < e && *p >= 0xf0 && *p <= 0xf4 && ( p[1] & 0xc0 ) == 0x80 && ( p[2] & 0xc0 ) == 0x80
                  && ( p[3] & 0xc0 ) == 0x80 )
        {
            // 4-byte encoded
            size = 4;
            return (p[3] & 0x3f) | ((p[2] & 0x3f) << 6) | ((p[1] & 0x3f) << 12) | ((*p & 0x07) << 18);
        }
    }
    size = 0;
    return -1;
}

// pack unicode character to utf-8
// optionally pass buffer top (points to 1 byte after buffer end)
// if ptr is 0, number of bytes required to encode ch is returned
// returns 0 on error, otherwise number of bytes written
u32 charToUTF8( unichar ch, void *ptr, const void *top )
{
    u32 res = 0;

    if ( (u32)ch > 0x10ffffU )
        return 0;					// outside valid unicode range
    u8 *p = (u8 *)ptr;
    const u8 *e = (const u8 *)top;
    if ( !e && p )
        e = p + 4;

    if ( ch < 0x80 )
    {
        if ( !p )
            res = 1;
        else if ( p < e )
        {
            *p = (u8)ch;
            res = 1;
        }
    }
    else
        if ( ch < 0x800 )
        {
            if ( !p )
                res = 2;
            else if ( p + 1 < e )
            {
                *p = 0xc0 | (ch >> 6);
                p[1] = 0x80 | (ch & 0x3f );
                res = 2;
            }
        }
        else if ( ch < 0x10000 )
        {
            if ( !p )
                res = 3;
            else if ( p + 2 < e )
            {
                *p = 0xe0 | (ch >> 12 );
                p[1] = 0x80 | ((ch >> 6) & 0x3f );
                p[2] = 0x80 | (ch & 0x3f );
                res = 3;
            }
        }
        else if ( ch < 0x110000 )
        {
            if ( !p )
                res = 4;
            else if ( p + 3 < e )
            {
                *p = 0xe0 | (ch >> 18 );
                p[1] = 0x80 | ((ch >> 12) & 0x3f );
                p[2] = 0x80 | ((ch >> 6) & 0x3f );
                p[3] = 0x80 | (ch & 0x3f );
                res = 4;
            }
        }
    return res;
}
