#include "OLEDUIPlus.h"
#include <fonts/Dialog_bold_10.h>
//#include <graphics/images.h>
#include <graphics/glyphs.h>

#define MENU_TIMEOUT 10000

#define ONOFF_Y_SPEED 4 //3
#define ONOFF_X_DOWN 8  //6
#define ONOFF_X_UP 12   //10

// pocatecni/rozsvecovaci a zhasinaci animace
typedef struct {
    int height;
    int lineWidth;
    bool closing;
    bool doingLine;
    bool lineClosing;
    bool active;
} crt_anim_t;

static crt_anim_t _anim;
static bool _displayon = false; // priznak, ze na OLED je videt zobrazeni (neni ve stavu spanku)
static oledon _oledon = nullptr;
static oledoff _oledoff = nullptr;

void OLEDUIPlus::goToFrame(uint8_t frame)
{

    _frameptr++;
    _frameptr %= sizeof(_frameseq);
    _frameseq[_frameptr] = frame;
    transitionToFrame(frame); // aktivujeme spravnou obrazovku
}

void OLEDUIPlus::goToPrevFrame(void)
{

    _frameptr--;
    _frameptr %= sizeof(_frameseq);
    transitionToFrame(_frameseq[_frameptr]); // aktivujeme predchozi obrazovku
}

void OLEDUIPlus::begin(OLEDDisplay *display, kbdRead rKbd, uint32_t timeout, FrameCallback frames[], size_t framesCount, OverlayCallback overlays[], size_t overlaysCount)
{

    _kbd = rKbd;
    _oled = display;
    _frames = frames;
    _framesCount = framesCount;
    _displayon = true;
    setTargetFPS(30);
    setFrameAnimation(SLIDE_UP);
    setFrames(frames, framesCount);
    setOverlays(overlays, overlaysCount);
    disableAutoTransition();
    disableAllIndicators();
    init(display);
    startAnim(1);
    _oledtimeout = timeout;
    if (0 != _oledtimeout)
        _oledTiming.setOneshot(_oledtimeout);
}

// Spusteni rozsvecovaci/zhasinaci animace
void OLEDUIPlus::startAnim(int open)
{

    if (0 != open) {
        _anim.closing = false;
        _anim.doingLine = true;
        _anim.height = _oled->height() / 2;
        _anim.lineClosing = false;
        _anim.lineWidth = 0;
        _oled->displayOn();
        _displayon = true;
    } else {
        _anim.closing = true;
        _anim.doingLine = false;
        _anim.height = 0;
        _anim.lineClosing = true;
        _anim.lineWidth = _oled->width();
    }
    _anim.active = true;
}

void onOffOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{

    if (_anim.active) {
        uint8_t height = _anim.height;
        uint8_t lineWidth = _anim.lineWidth;

        if (!_anim.doingLine) {
            if (_anim.closing)
                height += ONOFF_Y_SPEED;
            else
                height -= ONOFF_Y_SPEED;

            if (height >= display->getHeight() / 2) {
                if (_anim.closing) {
                    height = display->getHeight() / 2;
                    _anim.doingLine = true;
                } else {
                    height = 0;
                    _anim.active = false;
                }
            }
        } else {
            if (_anim.lineClosing)
                lineWidth -= ONOFF_X_DOWN;
            else
                lineWidth += ONOFF_X_UP;

            if (lineWidth >= display->getWidth()) {
                if (_anim.lineClosing)
                    lineWidth = 0;
                else
                    lineWidth = display->getWidth();

                if (!_anim.lineClosing)
                    _anim.doingLine = false;

                if (_anim.lineClosing && _anim.closing) {
                    _anim.active = false;
                    display->displayOff();
                    if (_oledoff != nullptr)
                        _oledoff();
                    _displayon = false;
                }

                _anim.lineClosing = !_anim.lineClosing;
            }
        }
        // Full rows
        uint8_t *oledBuffer = display->buffer;
        uint8_t rows = height / 8;
        for (uint8_t i = 0; i < rows; i++) {
            memset(&oledBuffer[i * display->getWidth()], 0, display->getWidth());
            memset(&oledBuffer[display->getBufferSize() - display->getWidth() - (i * display->getWidth())], 0, display->getWidth());
        }

        uint8_t prows = height % 8;
        if (prows) {
            // Partial rows & edge line
            uint32_t idxStart = rows * display->getWidth();
            uint32_t idxEnd = ((display->getBufferSize() - 1) - idxStart);
            uint8_t a = (255 << prows);
            uint8_t b = (255 >> prows);
            uint8_t c = (1 << prows);
            uint8_t d = (128 >> prows);
            for (uint32_t i = 0; i < display->getWidth(); i++) {
                oledBuffer[idxStart] = (oledBuffer[idxStart] & a) | c;
                idxStart++;

                oledBuffer[idxEnd] = (oledBuffer[idxEnd] & b) | d;
                idxEnd--;
            }
        } else if (height) {
            // Edge line
            uint32_t pos = ((uint8_t)(display->getWidth() - lineWidth) / 2) + ((uint8_t)(display->getHeight() - height) / 8) * display->getWidth();
            memset(&oledBuffer[pos], 0x01, lineWidth);

            if (height != display->getHeight() / 2) {
                pos = (height / 8) * display->getWidth();
                for (uint32_t x = 0; x < display->getWidth(); x++)
                    oledBuffer[pos + x] |= 0x01;
            }
        }
        _anim.height = height;
        _anim.lineWidth = lineWidth;
    }
}

bool OLEDUIPlus::loop(void)
{
    bool result = false;

    if (_displayon) {
// obsluha puvodniho Ui objektu
        if (_timing.expired()) {
            int rtb = update();

            if (rtb > 0) {
                _timing.set(rtb);
            }
            result = true;
        }
// obsluha casovace blikani
        if (_blink.expired()) {
            _blink.set(500);
            _visible = !_visible;
        }
// obsluha zhasinani displeje
        if (0 != _oledtimeout) {
            if (_oledTiming.expired()) {
                startAnim(0);
            }
        }
    } else {
        handleKbd();
    }
    return result;
}

void OLEDUIPlus::drawMenu(OLEDDisplay * display, const menu_t *menu, int *top, int *selected, int16_t x, int16_t y)
{
    char line[64];
    const menuEntry_t *me = (const menuEntry_t *)pgm_read_ptr(&menu->entries);
    int elementsCount = pgm_read_byte(&menu->entriesCount);
    int width = _oled->getWidth();

    strcpy_P(line, (char *)pgm_read_ptr(&menu->title)); // nadpis menu
    display->setFont(Dialog_bold_10);
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    uint16_t height = display->getTextHeight();
    display->setColor(WHITE);
    display->drawString((width / 2) + x, 0 + y, line);
    display->drawHorizontalLine(0 + x, 0 + y + height, width);
    for (int i = 0; i < MENU_ROWS_ON_OLED; i++) {
        display->setColor(WHITE);
        display->setTextAlignment(TEXT_ALIGN_LEFT);
        display->setFont(Dialog_bold_10);
        if ((*top + i) < elementsCount) {
            if ((*top + i) == *selected) {
                display->fillRect(0 + x, height + (i * height) + y + 1, width, height);
                display->setColor(BLACK);
                //if (FIXED == ui.state.frameState) {
                ce checkEntry = (ce)pgm_read_ptr(&me[*top + i].checkEntry);
                if (nullptr != checkEntry)
                    (checkEntry)();
                //}
            }
            strcpy_P(line, (char *)pgm_read_ptr(&me[*top + i].entryText));
            display->drawString(8 + x, height + (i * height) + y + 1, line);
            de drawEntry = (de)pgm_read_ptr(&me[*top + i].drawEntry);
            if (nullptr != drawEntry) {
                (drawEntry)(display, 8 + x, height + (i * height) + y + 1, NULL);
            }
            ve validEntry = (ve)pgm_read_ptr(&me[*top + i].validEntry);
            if (nullptr != validEntry) {
                bool valid = (validEntry)((void *)pgm_read_ptr(&me[*top + i].usrData));
                if (!valid) {
                    display->ditherArea(0 + x, height + (i * height) + y + 1, width, height);
                }
            }
            display->setTextAlignment(TEXT_ALIGN_LEFT);
            display->setFont(ICONS8x8);
            if (0 == i && *top != 0) {
                display->drawString(0 + x, height + y + 2, String("4")); // sipka nahoru
            }
            if ((MENU_ROWS_ON_OLED - 1) == i && (*top + i) < (elementsCount - 1)) {
                display->drawString(0 + x, height + (i * height) + y + 4, String("5")); // sipka dolu
            }
        }
    }
}

void OLEDUIPlus::goToMainFrame(void)
{

    goToFrame(_mainFrame);
}

int OLEDUIPlus::handleKbd(void)
{
    int retval = KEY_NONE;

    if (nullptr != _kbd)
        retval = _kbd();

    if (KEY_NONE != retval) {
        if (extendOn())
            retval = KEY_NONE; // musime nejdriv rozsvitit displej (ignorujeme stisk)
    }
    return retval;
}

int OLEDUIPlus::extendOn(void)
{
    int retval = 0; // nebylo nutne rozsvitit

    if (0 != _oledtimeout) {
        _oledTiming.setOneshot(_oledtimeout); // prodlouzime delku svitu OLED
        if (false == _displayon) {
            if (_oledon != nullptr)
                _oledon();
            startAnim(1);
            retval = 1; // musime rozsvitit displej
        }
    }
    _inMenu.setOneshot(MENU_TIMEOUT); // prodluzujeme timeout setrvani v menu
    return retval;
}

bool OLEDUIPlus::blinkIsVisible(void)
{

    return _visible;
}

bool OLEDUIPlus::menuExpired(void)
{

    return _inMenu.expired();
}

uint8_t OLEDUIPlus::getFrameIndex(FrameCallback cb)
{

    for (size_t i = 0; i < _framesCount; i++) {
        if (_frames[i] == cb)
            return (uint8_t)i;
    }
    return 0xff;
}

void OLEDUIPlus::setMainFrame(FrameCallback cb)
{

    _mainFrame = getFrameIndex(cb);
}

void OLEDUIPlus::setTimeout(uint32_t ms)
{

    _oledtimeout = ms;
    extendOn();
}

bool OLEDUIPlus::isVisible(void)
{

    return _displayon;
}

#if defined(USE_OTA)
void OLEDUIPlus::OTAStart(void)
{

    if (_oled) {
        if (false == _displayon)
            _oled->displayOn();
        _oled->clear();
        _oled->setFont(ArialMT_Plain_10);
        _oled->setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
        _oled->drawString(_oled->getWidth() / 2, _oled->getHeight() / 2 - 10, "OTA Update");
        _oled->display();
    }
}

void OLEDUIPlus::OTAProgress(unsigned int progress, unsigned int total)
{

    if (_oled) {
        _oled->drawProgressBar(4, 32, 120, 8, progress / (total / 100));
        _oled->display();
    }
}

void OLEDUIPlus::OTAEnd(void)
{

    if (_oled) {
        _oled->clear();
        _oled->setFont(ArialMT_Plain_10);
        _oled->setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
        _oled->drawString(_oled->getWidth() / 2, _oled->getHeight() / 2, "Restart");
        _oled->display();
    }
}
#endif

void OLEDUIPlus::setOffCb(oledoff oloff)
{

    _oledoff = oloff;
}

void OLEDUIPlus::setOnCb(oledon olon)
{

    _oledon = olon;
}

OLEDUIPlus ui;
