/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

#include "mf_rlefont.h"

#ifndef MF_NO_COMPILE

/* Number of reserved codes before the dictionary entries. */
#define DICT_START 24

/* Special reference to mean "fill with zeros to the end of the glyph" */
#define REF_FILLZEROS 16

/* RLE codes */
#define RLE_CODEMASK    0xC0
#define RLE_VALMASK     0x3F
#define RLE_ZEROS       0x00
#define RLE_64ZEROS     0x40
#define RLE_ONES        0x80
#define RLE_SHADE       0xC0

/* Dictionary "fill entries" for encoding bits directly. */
#define DICT_START7BIT  4
#define DICT_START6BIT  132
#define DICT_START5BIT  196
#define DICT_START4BIT  228
#define DICT_START3BIT  244
#define DICT_START2BIT  252

/* Find a pointer to the glyph matching a given character by searching
 * through the character ranges. If the character is not found, return
 * pointer to the default glyph.
 */
static const uint8_t *find_glyph(const struct mf_rlefont_s *font,
                                 uint16_t character)
{
   unsigned i, index;
   const struct mf_rlefont_char_range_s *range;
   for (i = 0; i < font->char_range_count; i++)
   {
       range = &font->char_ranges[i];
       index = character - range->first_char;
       if (character >= range->first_char && index < range->char_count)
       {
           unsigned offset = range->glyph_offsets[index];
           return &range->glyph_data[offset];
       }
   }

   return 0;
}

/* Structure to keep track of coordinates of the next pixel to be written,
 * and also the bounds of the character. */
struct renderstate_r
{
    int16_t x_begin;
    int16_t x_end;
    int16_t x;
    int16_t y;
    int16_t y_end;
    mf_pixel_callback_t callback;
    void *state;
};

/* Call the callback to write one pixel to screen, and advance to next
 * pixel position. */
static void write_pixels(struct renderstate_r *rstate, uint16_t count,
                         uint8_t alpha)
{
    uint8_t rowlen;
    
    /* Write row-by-row if the run spans multiple rows. */
    while (rstate->x + count >= rstate->x_end)
    {
        rowlen = rstate->x_end - rstate->x;
        rstate->callback(rstate->x, rstate->y, rowlen, alpha, rstate->state);
        count -= rowlen;
        rstate->x = rstate->x_begin;
        rstate->y++;
    }
    
    /* Write the remaining part */
    if (count)
    {
        rstate->callback(rstate->x, rstate->y, (uint8_t)count, alpha, rstate->state); /* support KeilC251 */
        rstate->x += count;
    }
}

/* Skip the given number of pixels (0 alpha) */
static void skip_pixels(struct renderstate_r *rstate, uint16_t count)
{
    rstate->x += count;
    while (rstate->x >= rstate->x_end)
    {
        rstate->x -= rstate->x_end - rstate->x_begin;
        rstate->y++;
    }
}

/* Decode and write out a RLE-encoded dictionary entry. */
static void write_rle_dictentry(const struct mf_rlefont_s *font,
                                struct renderstate_r *rstate,
                                uint8_t index)
{
    uint16_t offset = font->dictionary_offsets[index];
    uint16_t length = font->dictionary_offsets[index + 1] - offset;
    uint16_t i;
    
    for (i = 0; i < length; i++)
    {
        uint8_t _code_ = font->dictionary_data[offset + i];
        if ((_code_ & RLE_CODEMASK) == RLE_ZEROS)
        {
            skip_pixels(rstate, _code_ & RLE_VALMASK);
        }
        else if ((_code_ & RLE_CODEMASK) == RLE_64ZEROS)
        {
            skip_pixels(rstate, ((_code_ & RLE_VALMASK) + 1) * 64);
        }
        else if ((_code_ & RLE_CODEMASK) == RLE_ONES)
        {
            write_pixels(rstate, (_code_ & RLE_VALMASK) + 1, 255);
        }
        else if ((_code_ & RLE_CODEMASK) == RLE_SHADE)
        {
            uint8_t count, alpha;
            count = ((_code_ & RLE_VALMASK) >> 4) + 1;
            alpha = ((_code_ & RLE_VALMASK) & 0xF) * 0x11;
            write_pixels(rstate, count, alpha);
        }
    }
}

/* Get bit count for the "fill entries" */
static uint8_t fillentry_bitcount(uint8_t index)
{
    if (index >= DICT_START2BIT)
        return 2;
    else if (index >= DICT_START3BIT)
        return 3;
    else if (index >= DICT_START4BIT)
        return 4;
    else if (index >= DICT_START5BIT)
        return 5;
    else if (index >= DICT_START6BIT)
        return 6;
    else
        return 7;
}

/* Decode and write out a direct binary codeword */
static void write_bin_codeword(const struct mf_rlefont_s *font,
                                struct renderstate_r *rstate,
                                uint8_t _code_)
{
    uint8_t bitcount = fillentry_bitcount(_code_);
    uint8_t byte = _code_ - DICT_START7BIT;
    uint8_t runlen = 0;
    UNUSED(font);/* support KeilC251 */
    
    while (bitcount--)
    {
        if (byte & 1)
        {
            runlen++;
        }
        else 
        {
            if (runlen)
            {
                write_pixels(rstate, runlen, 255);
                runlen = 0;
            }
            
            skip_pixels(rstate, 1);
        }
        
        byte >>= 1;
    }
    
    if (runlen)
        write_pixels(rstate, runlen, 255);
}

/* Decode and write out a reference codeword */
static void write_ref_codeword(const struct mf_rlefont_s *font,
                                struct renderstate_r *rstate,
                                uint8_t _code_)
{
    if (_code_ <= 15)
    {
        write_pixels(rstate, 1, (uint8_t)(0x11 * _code_));/* support KeilC251 */
    }
    else if (_code_ == REF_FILLZEROS)
    {
        /* Fill with zeroes to end */
        rstate->y = rstate->y_end;
    }
    else if (_code_ < DICT_START)
    {
        /* Reserved */
    }
    else if (_code_ < DICT_START + font->rle_entry_count)
    {
        write_rle_dictentry(font, rstate, (uint8_t)(_code_ - DICT_START));
    }
    else
    {
        write_bin_codeword(font, rstate, _code_);
    }
}

/* Decode and write out a reference encoded dictionary entry. */
static void write_ref_dictentry(const struct mf_rlefont_s *font,
                                struct renderstate_r *rstate,
                                uint8_t index)
{
    uint16_t offset = font->dictionary_offsets[index];
    uint16_t length = font->dictionary_offsets[index + 1] - offset;
    uint16_t i;
    
    for (i = 0; i < length; i++)
    {
        uint8_t _code_ = font->dictionary_data[offset + i];
        write_ref_codeword(font, rstate, _code_);
    }
}

/* Decode and write out an arbitrary glyph codeword */
static void write_glyph_codeword(const struct mf_rlefont_s *font,
                                struct renderstate_r *rstate,
                                uint8_t _code_)
{
    if (_code_ >= DICT_START + font->rle_entry_count &&
        _code_ < DICT_START + font->dict_entry_count)
    {
        write_ref_dictentry(font, rstate, (uint8_t)(_code_ - DICT_START));/* support KeilC251 */
    }
    else
    {
        write_ref_codeword(font, rstate, _code_);
    }
}


uint8_t mf_rlefont_render_character(const struct mf_font_s *font,
                                    int16_t x0, int16_t y0,
                                    uint16_t character,
                                    mf_pixel_callback_t callback,
                                    void *state)
{
    const uint8_t *p;
    uint8_t width;
    
    struct renderstate_r rstate;
    rstate.x_begin = x0;
    rstate.x_end = x0 + font->width;
    rstate.x = x0;
    rstate.y = y0;
    rstate.y_end = y0 + font->height;
    rstate.callback = callback;
    rstate.state = state;
    
   	p = find_glyph((struct mf_rlefont_s*)font, character);
    if (!p)
        return 0;
    
    width = *p++;
    while (rstate.y < rstate.y_end)
    {
        write_glyph_codeword((struct mf_rlefont_s*)font, &rstate, *p++);
    }
    
    return width;
}

uint8_t mf_rlefont_character_width(const struct mf_font_s *font,
                                   uint16_t character)
{
    const uint8_t *p;
    p = find_glyph((struct mf_rlefont_s*)font, character);
    if (!p)
        return 0;
    
    return *p;
}

#endif //MF_NO_COMPILE
