#include "main.h"
#include "printf.h"
#include "bitmap_fonts.h"
#include "bitmap_disp_buttons.h"
#include "bitmap_wave.h"
#include "tim.h"
#include "SEGGER_RTT.h"

#include "disp_layout.h"

#define ABS(x) ((x) < 0 ? -(x) : (x))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

#define SEVEN_DIGIT 7
#define FOUR_DIGIT 3
#define THREE_DIGIT 2

const uint8_t btn_pos_x[DISP_BTN_MAX] = {0, 26, 52, 78, 104};
const uint8_t marker_pos_7dig[SEVEN_DIGIT] = {76, 84, 90, 96, 105, 111, 118};
const uint8_t marker_pos_4dig[FOUR_DIGIT] = {82, 94, 100};
const uint8_t marker_pos_3dig[THREE_DIGIT] = {82, 88};

// clang-format off
const GFX_bitmap_t marker_down = {05, 03, (uint8_t[]){0x01,0x03,0x07,0x03,0x01}};// res: 5x3(8)   - 5 bytes
const GFX_bitmap_t marker_up = {05, 03, (uint8_t[]){0x04,0x06,0x07,0x06,0x04}}; // res: 5x3(8)   - 5 bytes

// clang-format on

static void _drawFreqValue(GFX_display_t *disp, uint32_t freq, uint8_t pos_y)
{
    uint8_t data[8];
    snprintf((char *)data, 8, "%07u", freq);
    DISP_writeString(disp, &font5x7Info, (uint8_t[]){data[0], 0}, 76, pos_y, BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t[]){data[1], data[2], data[3], 0}, 84, pos_y, BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t[]){data[4], data[5], data[6], 0}, 105, pos_y, BM_NORMAL);
}

static void _drawAmplValue(GFX_display_t *disp, uint16_t ampl, uint8_t pos_y)
{
    uint8_t data[8];
    snprintf((char *)data, 8, "%02d.%02d V", ampl / 100, ampl % 100);
    DISP_writeString(disp, &font5x7Info, data, 76, pos_y, BM_NORMAL);
}

static void _drawOffsValue(GFX_display_t *disp, int16_t offs, uint8_t pos_y)
{
    uint8_t data[8];
    snprintf((char *)data, 8, "%+01d.%02d V", offs / 100, ABS(offs) % 100);
    if (offs < 0)
    {
        data[0] = '-';
    }
    DISP_writeString(disp, &font5x7Info, data, 76, pos_y, BM_NORMAL);
}

static void _drawPhasValue(GFX_display_t *disp, uint16_t phase, uint8_t pos_y)
{
    uint8_t data[8];
    snprintf((char *)data, 8, "%03u'", phase);
    DISP_writeString(disp, &font5x7Info, data, 76, pos_y, BM_NORMAL);
}

// static void _drawDutyValue(GFX_display_t *disp, uint8_t duty, uint8_t pos_y)
// {
//     uint8_t data[8];
//     snprintf((char *)data, 8, "%03u%%", duty);
//     DISP_writeString(disp, &font5x7Info, data, 76, pos_y, BM_NORMAL);
// }

static void _blinkButtons(GFX_display_t *disp, APP_data_t *app_data)
{
    // input: display buttons, invert
    BITMAP_buttonName_t bitmap_idx;
    for (LAY_dispBtn_t btn_idx = DISP_BTN_1; btn_idx < DISP_BTN_MAX; btn_idx++)
    {
        if (!(app_data->button_blink & (1 << btn_idx)))
        {
            continue;
        }

        bitmap_idx = CTRL_getBitmapName(btn_idx);
        if (app_data->timer_blink[btn_idx] > 0)
        {
            DISP_drawBitmap(disp, &bitmap_btnBottom[bitmap_idx + 1], btn_pos_x[btn_idx], 53, BM_NORMAL);
            app_data->timer_blink[btn_idx]--;
        }
        else
        {
            DISP_drawBitmap(disp, &bitmap_btnBottom[bitmap_idx], btn_pos_x[btn_idx], 53, BM_NORMAL);
            app_data->button_blink &= ~(1 << btn_idx);
        }
    }
}

static void _drawButtons(GFX_display_t *disp, APP_data_t *app_data)
{
    // input: display buttons, invert
    BITMAP_buttonName_t bitmap_idx;
    DISP_clearRegion(disp, 0, 53, disp->width, 11);

    for (LAY_dispBtn_t btn_idx = DISP_BTN_1; btn_idx < DISP_BTN_MAX; btn_idx++)
    {
        bitmap_idx = CTRL_getBitmapName(btn_idx);
        DISP_drawBitmap(disp, &bitmap_btnBottom[bitmap_idx], btn_pos_x[btn_idx], 53, BM_NORMAL);
    }

    app_data->disp_update &= ~(UPDATE_BUTTON);
}

static uint8_t _calcMarkerPos(uint32_t value, uint32_t max)
{
    return ((61 * value) + (max / 2)) / max;
    // return (value < 0) ? ((62 * value) - (max / 2)) / max : ((62 * value) + (max / 2)) / max;
}

static uint8_t _calcPhaseShiftInPxl(uint32_t value, uint32_t max)
{
    return ((48 * value) + (max / 2)) / max;
}

static int32_t _calcVerticalShiftInPxl(int16_t value, int16_t max)
{
    return (value < 0) ? ((12 * value) - (max / 2)) / max : ((12 * value) + (max / 2)) / max;
}

static uint8_t _sizeOfCharArray(char *array)
{
    uint8_t size = 0;
    while (*array++)
    {
        size++;
    }
    return size;
}

static void _drawMeterHelper(GFX_display_t *disp, char *min, char *max, uint8_t pos)
{
    uint8_t max_size = _sizeOfCharArray(max);
    DISP_drawPixel(disp, 64, 40, GFX_WHITE);
    DISP_drawPixel(disp, 125, 40, GFX_WHITE);
    DISP_drawHorizontalLine(disp, 64, 41, 62, GFX_WHITE);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)min, 62, 43, BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)max, 127 - (6 * max_size), 43, BM_NORMAL);
    // marker 0% - 62, 100% - 123
    DISP_drawBitmap(disp, &marker_down, 62 + pos, 35, BM_NORMAL);
}

static void _drawFreqValueHelper(GFX_display_t *disp, uint32_t freq, uint8_t focus_digit)
{
    DISP_clearRegion(disp, 64, 13, 66, 40);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"F:", 64, 20, BM_NORMAL);
    _drawFreqValue(disp, freq, 20);
    DISP_drawBitmap(disp, &marker_down, marker_pos_7dig[(SEVEN_DIGIT - 1) - focus_digit], 15, BM_NORMAL);
}

static void _drawAmplValueHelper(GFX_display_t *disp, uint32_t ampl, uint8_t focus_digit)
{
    DISP_clearRegion(disp, 64, 13, 66, 40);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"A:", 64, 20, BM_NORMAL);
    _drawAmplValue(disp, ampl, 20);
    DISP_drawBitmap(disp, &marker_down, marker_pos_4dig[(FOUR_DIGIT - 1) - focus_digit], 15, BM_NORMAL);
}

static void _drawOffsValueHelper(GFX_display_t *disp, int32_t offs, uint8_t focus_digit)
{
    DISP_clearRegion(disp, 64, 13, 66, 40);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"O:", 64, 20, BM_NORMAL);
    _drawOffsValue(disp, offs, 20);
    DISP_drawBitmap(disp, &marker_down, marker_pos_4dig[(FOUR_DIGIT - 1) - focus_digit], 15, BM_NORMAL);
}

static void _drawPhaseValueHelper(GFX_display_t *disp, uint32_t phase, uint8_t focus_digit)
{
    DISP_clearRegion(disp, 64, 13, 66, 40);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"P:", 64, 20, BM_NORMAL);
    _drawPhasValue(disp, phase, 20);
    DISP_drawBitmap(disp, &marker_down, marker_pos_3dig[(THREE_DIGIT - 1) - focus_digit], 15, BM_NORMAL);
}

static void _drawFuncGenValues(GFX_display_t *disp, APP_data_t *app_data)
{
    dds_gen_t *fun_gen = (dds_gen_t *)app_data->curr_gen;
    switch (app_data->curr_state_lay)
    {
    case LAY_FREQ:
        _drawFreqValueHelper(disp, fun_gen->settings.frequency, app_data->freq_focus_digit);
        _drawMeterHelper(disp, "1", "1M", _calcMarkerPos(fun_gen->settings.frequency, MAX_FREQ));
        break;
    case LAY_AMPL:
        _drawAmplValueHelper(disp, fun_gen->settings.amplitude, app_data->ampl_focus_digit);
        _drawMeterHelper(disp, "0V", "5V", _calcMarkerPos(fun_gen->settings.amplitude, MAX_VOLT_POS));
        break;
    case LAY_OFFS:
        _drawOffsValueHelper(disp, fun_gen->settings.offset, app_data->offs_focus_digit);
        _drawMeterHelper(disp, "-5V", "+5V", _calcMarkerPos(MAX_VOLT_POS + fun_gen->settings.offset, MAX_VOLT_POS * 2));
        break;
    case LAY_PHAS:
        _drawPhaseValueHelper(disp, fun_gen->settings.phase, app_data->phas_focus_digit);
        _drawMeterHelper(disp, "0", "360", _calcMarkerPos(fun_gen->settings.phase, MAX_PHAS));
        break;
    case LAY_MAIN:
        DISP_clearRegion(disp, 62, 13, 66, 40);
        DISP_writeString(disp, &font5x7Info, (uint8_t *)"F:", 64, 13, BM_NORMAL);
        DISP_writeString(disp, &font5x7Info, (uint8_t *)"A:", 64, 23, BM_NORMAL);
        DISP_writeString(disp, &font5x7Info, (uint8_t *)"O:", 64, 33, BM_NORMAL);
        DISP_writeString(disp, &font5x7Info, (uint8_t *)"P:", 64, 43, BM_NORMAL);

        _drawFreqValue(disp, fun_gen->settings.frequency, 13);
        _drawAmplValue(disp, fun_gen->settings.amplitude, 23);
        _drawOffsValue(disp, fun_gen->settings.offset, 33);
        _drawPhasValue(disp, fun_gen->settings.phase, 43);
        break;

    default:
        // focus
        break;
    }
}

static void _drawPwmGenValues(GFX_display_t *disp, APP_data_t *app_data)
{
    // GEN_pwm_t *pwm_gen = app_data->curr_gen;
}

static void _drawValues(GFX_display_t *disp, APP_data_t *app_data)
{
    // input: frequency(value), amplitiude(value), offset(value), phase(value), duty(value), invert(info), position(value)
    switch (app_data->curr_gen->type)
    {
    case GEN_FG_TYPE:
        _drawFuncGenValues(disp, app_data);
        break;
    case GEN_PWM_TYPE:
        _drawPwmGenValues(disp, app_data);
        break;

    default:
        break;
    }

    app_data->disp_update &= ~(UPDATE_VALUE);
}

static void _drawOffsetLine(GFX_display_t *disp, int16_t offs, uint32_t ampl)
{
    int8_t vert_shift = _calcVerticalShiftInPxl(offs, ampl);

    vert_shift = MIN(vert_shift, 20);
    vert_shift = MAX(vert_shift, -19);

    // ULOG_DEBUG("<OFFSLINE> shift: %i", vert_shift);
    DISP_drawHorizontalLine(disp, 2, 31 + vert_shift, 57, GFX_WHITE);
}

// static void _drawMaxAmpLine(GFX_display_t *disp, int16_t offs, uint32_t ampl)
// {
//     DISP_drawHorizontalLine(disp, 2, 12, 57, GFX_WHITE);
//     DISP_drawBitmap(disp, &marker_up, 0, 13, BM_NORMAL);

//     DISP_drawHorizontalLine(disp, 2, 51, 57, GFX_WHITE);
//     DISP_drawBitmap(disp, &marker_down, 0, 48, BM_NORMAL);
// }

static void _drawFunGenGraph(GFX_display_t *disp, APP_data_t *app_data)
{
    dds_gen_t *fun_gen = (dds_gen_t *)app_data->curr_gen;
    DISP_clearRegion(disp, 3, 12, 57, 40);

    switch (fun_gen->settings.wave)
    {
    case GEN_SIN:
        DISP_drawBitmapShift(disp, &wave[WAVE_SIN], 6, 19, _calcPhaseShiftInPxl(fun_gen->settings.phase, 360), BM_NORMAL);
        break;
    case GEN_TRI:
        DISP_drawBitmapShift(disp, &wave[WAVE_TRI], 6, 19, _calcPhaseShiftInPxl(fun_gen->settings.phase, 360), BM_NORMAL);
        break;
    case GEN_SQR:
        DISP_drawBitmapShift(disp, &wave[WAVE_SQR], 6, 19, _calcPhaseShiftInPxl(fun_gen->settings.phase, 360), BM_NORMAL);
        break;
    default:
        break;
    }

    _drawOffsetLine(disp, fun_gen->settings.offset, fun_gen->settings.amplitude);
    // _drawMaxAmpLine(disp, fun_gen->settings.offset, fun_gen->settings.amplitude);
}
static void _drawPwmGenGraph(GFX_display_t *disp, APP_data_t *app_data)
{
}

static void _drawGraph(GFX_display_t *disp, APP_data_t *app_data)
{
    // input: graph type(sin, triangle, squere, pwm), frequency(info), amplitiude(value), offset(value), phase(value), invert(pwm),
    DISP_drawVerticalLine(disp, 2, 12, 40, GFX_WHITE);

    switch (app_data->curr_gen->type)
    {
    case GEN_FG_TYPE:
        _drawFunGenGraph(disp, app_data);
        break;
    case GEN_PWM_TYPE:
        _drawPwmGenGraph(disp, app_data);
        break;

    default:
        break;
    }

    app_data->disp_update &= ~(UPDATE_GRAPH);
}

static void _drawChannels(GFX_display_t *disp, APP_data_t *app_data)
{
    // input: active channel
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"CH1", 2, 1, CHANNEL1 == app_data->curr_channel ? BM_INVERSE : BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"CH2", 23, 2, CHANNEL2 == app_data->curr_channel ? BM_INVERSE : BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"CH3", 44, 2, CHANNEL3 == app_data->curr_channel ? BM_INVERSE : BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"CH4", 65, 2, CHANNEL4 == app_data->curr_channel ? BM_INVERSE : BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"CH5", 86, 2, CHANNEL5 == app_data->curr_channel ? BM_INVERSE : BM_NORMAL);
    DISP_writeString(disp, &font5x7Info, (uint8_t *)"CH6", 107, 2, CHANNEL6 == app_data->curr_channel ? BM_INVERSE : BM_NORMAL);
    DISP_drawHorizontalLine(disp, 0, 10, 127, GFX_WHITE);

    app_data->disp_update &= ~(UPDATE_CHANNEL);
}

void LAY_drawDisplayLayout(GFX_display_t *disp, APP_data_t *app_data)
{
    if (app_data->disp_update & UPDATE_CHANNEL)
        _drawChannels(disp, app_data);

    if (app_data->disp_update & UPDATE_VALUE)
        _drawValues(disp, app_data);

    if (app_data->disp_update & UPDATE_GRAPH)
        _drawGraph(disp, app_data);

    if (app_data->disp_update & UPDATE_BUTTON)
        _drawButtons(disp, app_data);

    if (app_data->button_blink)
        _blinkButtons(disp, app_data);
}
