#pragma once

#include "font.h"

enum TB_MODES
{
    TB_DISABLED = 0,
    TB_STATIC,
    TB_REPEAT,
    TB_LOOP,
    TB_LOOP_WITH_DELAY,
    TB_BOUNCE,
};

class textbox
{
private:
    /* data */
    uint8_t cord_x;
    uint8_t cord_y;
    uint8_t w;
    uint8_t mode;
    uint16_t speed;
    uint16_t delay;
    int16_t offset;
    bool direction;
    bool state;
    mData color;
    char text[64];
    uint8_t textlen;
    uint16_t textwidth;
    uint8_t letter[8];
    uint64_t langMask;
    bool native;
    void getLetter(uint8_t i, bool skipBitmap = 0);
    uint32_t timer0;

public:
    textbox(void);
    void setup(char _text[], uint64_t _langMask,
        mData _color,
        uint8_t _mode,
        uint8_t _x, uint8_t _y, uint8_t _w,
        uint8_t _reciprocal_speed = 0, uint16_t _delay = 0);
    uint8_t render(void);
    void disable(void);
    void changeCords(uint8_t newx, uint8_t newy);
    void resetTimers(void);
};

textbox::textbox(void)
{
    cord_x = 0;
    cord_y = 0;
    w = 0;
    mode = 0;
    speed = 0;
    delay = 0;
    memset(text, '\0', 64 * sizeof(char));
    langMask = 0;
    // If 96th symbol is font_null there is no native language except for English
    native = pgm_read_byte(font[96]) != 0;
}

void textbox::setup(char _text[], uint64_t _langMask,
    mData _color,
    uint8_t _mode,
    uint8_t _x, uint8_t _y, uint8_t _w,
    uint8_t _reciprocal_speed = 0, uint16_t _delay = 0)
{
    // Copy string
    memset(text, '\0', 64 * sizeof(char));
    strcpy(text, _text);
    // Calculating width
    textlen = strlen(text);
    langMask = _langMask;
    textwidth = 0;
    for (uint16_t i = 0; i < textlen; i++)
    {
        getLetter(i, true);
        textwidth += letter[0] + 1;
    }
    textwidth--;

    // Defining other vars
    color = _color;
    mode = _mode;
    cord_x = _x;
    cord_y = _y;
    w = _w;
    offset = 0;
    switch (mode)
    {
    case TB_LOOP:
        delay = 0;
        goto NO_DELAY;
    case TB_REPEAT:
    case TB_LOOP_WITH_DELAY:
    case TB_BOUNCE:
        delay = _delay;
    NO_DELAY:
        speed = _reciprocal_speed;
        break;
    case TB_DISABLED:
    case TB_STATIC:
    default:
        speed = 0;
        delay = 0;
        break;
    }

    // Reset timer and render
    timer0 = 0;
    render();
}

uint8_t textbox::render(void)
{
    uint8_t r = 1; // Return 1 if rendered, 2 if rendered with new cycle
    if (mode != TB_DISABLED)
    {
        // If static or kickstart
        if (timer0 == 0 || mode == TB_STATIC)
        {
            offset = 0 + (mode == TB_REPEAT) * w + 1 * (mode == TB_LOOP);
            direction = 0;
            timer0 = millis();
        }
        else
        {
            if ((millis() - timer0) > (((mode != TB_LOOP) && !state) ? delay : speed))
            {
                state = 1;
                timer0 = millis();
                if (mode == TB_BOUNCE)
                {
                    if ((!direction && offset == -textwidth + w) || (direction && offset == 0))
                    {
                        r = direction + 1;
                        direction = !direction;
                        state = 0;
                    }
                    else
                        offset += direction ? 1 : -1;
                }
                else
                {
                    offset += -1;
                    if (offset < ((int16_t)-textwidth - 2 * (mode == TB_LOOP || mode == TB_LOOP_WITH_DELAY) - w * (mode == TB_REPEAT)))
                    {
                        offset = 0 + (mode == TB_REPEAT) * w;
                        state = 0;
                        r = 2;
                    }
                }
            }
            else
                // Draw if kickstart
                if(state != 0) return 0;
        }
    }
    // Do nothing if disabled
    else return 0;
    // Clear spot
    for (uint8_t i = cord_x; (i < cord_x + w) && (i < WIDTH); i++)
        for (uint8_t j = cord_y; (j < cord_y + 7) && (j < HEIGHT); j++)
            matrix.set(i, j, mBlack);
    // Init letter counter, start X and Y
    uint8_t index = 0;
    int16_t positionX = cord_x + offset;
    int16_t positionY = cord_y;
    // Draw while in bounds
    while ((positionX < cord_x + w) && (index < textlen))
    {
        // Recieve letter
        getLetter(index);
        // Relative X and Y
        uint8_t x = 0;
        uint8_t y = letter[1];
        // X bound
        uint8_t maxX = letter[0];
        // Run thougth bytes
        for (uint8_t i = 0; (i < letter[2]) && (y < 7); i++)
        {
            // Run througth bits
            for (int8_t j = 7; (j >= 0) && (y < 7); j--)
            {
                // Set pixel
                bool state = bitRead(letter[i + 3], j);
                int16_t mx = x + positionX;
                int16_t my = HEIGHT - 1 - y - positionY;
                if (state && mx >= cord_x && mx < cord_x + w && my >= cord_y && my < cord_y + 7)
                    matrix.set(mx, my, color);
                // Next pixel
                x++;
                if (x >= maxX)
                {
                    x = 0;
                    y++;
                }
            }
        }
        // Shift X and index
        positionX += maxX + 1;
        index++;
        // Make a loop
        if((mode == TB_LOOP || mode == TB_LOOP_WITH_DELAY) && index == textlen)
        {
            positionX++;
            index = 0;
        }
    }
    return r;
}

void textbox::disable(void)
{
    setup("", 0, 0, TB_DISABLED, 0, 0, 0);
}

void textbox::getLetter(uint8_t i, bool skipBitmap = 0)
{
    // Letter and it's position in ASCII table
    char c = text[i];
    uint8_t ctoi = (uint8_t)c;
    // Language (0 - English, 1 - Native)
    bool lang = bitRead(langMask, i);
    // Clear place for letter in RAM
    memset(letter, 0, 8 * sizeof(uint8_t));
    uint8_t index;
    // Translate ASCII index to local
    if (ctoi >= 32 && ctoi <= 126)
        index = ctoi - 31 + 95 * lang * native;
    else
        index = 0;
    // Read properties from semeric number
    uint8_t readed = pgm_read_byte(font[index]);
    letter[0] = readed / 49 + 1; // width
    letter[1] = readed % 7;      // offset
    letter[2] = readed / 7 % 7;  // bytes
    // Read bitmaps
    if (!skipBitmap)
        for (uint8_t i = 0; i < letter[2]; i++)
            letter[i + 3] = pgm_read_byte(font[index] + (i + 1) * sizeof(uint8_t));
}

void textbox::changeCords(uint8_t newx, uint8_t newy)
{
    cord_x = newx;
    cord_y = newy;
}

void textbox::resetTimers(void)
{
    timer0 = 0;
}