#include "dtmf_dolphin_dialer.h"

#include <gui/elements.h>

typedef struct DTMFDolphinDialer {
    View* view;
    DTMFDolphinDialerOkCallback callback;
    void* context;
} DTMFDolphinDialer;

typedef struct {
    DTMFDolphinToneSection section;
    uint8_t row;
    uint8_t col;
    float freq1;
    float freq2;
    bool playing;
    uint16_t pulses;
    uint16_t pulse_ms;
    uint16_t gap_ms;
} DTMFDolphinDialerModel;

static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer);
static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer);
static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer);
static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer);
static bool
    dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event);

void draw_button(Canvas* canvas, uint8_t row, uint8_t col, bool invert) {
    uint8_t left = DTMF_DOLPHIN_NUMPAD_X + // ((col + 1) * DTMF_DOLPHIN_BUTTON_PADDING) +
                   (col * DTMF_DOLPHIN_BUTTON_WIDTH);
    // (col * DTMF_DOLPHIN_BUTTON_PADDING);
    uint8_t top = DTMF_DOLPHIN_NUMPAD_Y + // ((row + 1) * DTMF_DOLPHIN_BUTTON_PADDING) +
                  (row * DTMF_DOLPHIN_BUTTON_HEIGHT);
    // (row * DTMF_DOLPHIN_BUTTON_PADDING);

    uint8_t span = dtmf_dolphin_get_tone_span(row, col);

    if(span == 0) {
        return;
    }

    canvas_set_color(canvas, ColorBlack);

    if(invert)
        canvas_draw_rbox(
            canvas,
            left,
            top,
            (DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
            DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
            2);
    else
        canvas_draw_rframe(
            canvas,
            left,
            top,
            (DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
            DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
            2);

    if(invert) canvas_invert_color(canvas);

    canvas_set_font(canvas, FontSecondary);
    // canvas_set_color(canvas, invert ? ColorWhite : ColorBlack);
    canvas_draw_str_aligned(
        canvas,
        left - 1 + (int)((DTMF_DOLPHIN_BUTTON_WIDTH * span) / 2),
        top + (int)(DTMF_DOLPHIN_BUTTON_HEIGHT / 2),
        AlignCenter,
        AlignCenter,
        dtmf_dolphin_data_get_tone_name(row, col));

    if(invert) canvas_invert_color(canvas);
}

void draw_dialer(Canvas* canvas, void* _model) {
    DTMFDolphinDialerModel* model = _model;
    uint8_t max_rows;
    uint8_t max_cols;
    uint8_t max_span;
    dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);

    canvas_set_font(canvas, FontSecondary);

    for(int r = 0; r < max_rows; r++) {
        for(int c = 0; c < max_cols; c++) {
            if(model->row == r && model->col == c)
                draw_button(canvas, r, c, true);
            else
                draw_button(canvas, r, c, false);
        }
    }
}

void update_frequencies(DTMFDolphinDialerModel* model) {
    dtmf_dolphin_data_get_tone_frequencies(&model->freq1, &model->freq2, model->row, model->col);
    dtmf_dolphin_data_get_filter_data(
        &model->pulses, &model->pulse_ms, &model->gap_ms, model->row, model->col);
}

static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
    DTMFDolphinDialerModel* model = _model;
    if(model->playing) {
        // Leverage the prioritized draw callback to handle
        // the DMA so that it doesn't skip.
        dtmf_dolphin_audio_handle_tick();
        // Don't do any drawing if audio is playing.
        canvas_set_font(canvas, FontPrimary);
        elements_multiline_text_aligned(
            canvas,
            canvas_width(canvas) / 2,
            canvas_height(canvas) / 2,
            AlignCenter,
            AlignCenter,
            "Playing Tones");
        return;
    }
    update_frequencies(model);
    uint8_t max_rows = 0;
    uint8_t max_cols = 0;
    uint8_t max_span = 0;
    dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);

    canvas_set_font(canvas, FontPrimary);
    elements_multiline_text(canvas, 2, 10, dtmf_dolphin_data_get_current_section_name());
    canvas_draw_line(
        canvas,
        (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1,
        0,
        (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1,
        canvas_height(canvas));
    elements_multiline_text(canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 10, "Detail");
    canvas_draw_line(
        canvas, 0, DTMF_DOLPHIN_NUMPAD_Y - 3, canvas_width(canvas), DTMF_DOLPHIN_NUMPAD_Y - 3);
    // elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Dialer Mode");

    draw_dialer(canvas, model);

    FuriString* output = furi_string_alloc();

    if(model->freq1 && model->freq2) {
        furi_string_cat_printf(
            output,
            "Dual Tone\nF1: %u Hz\nF2: %u Hz\n",
            (unsigned int)model->freq1,
            (unsigned int)model->freq2);
    } else if(model->freq1) {
        furi_string_cat_printf(output, "Single Tone\nF: %u Hz\n", (unsigned int)model->freq1);
    }

    canvas_set_font(canvas, FontSecondary);
    canvas_set_color(canvas, ColorBlack);
    if(model->pulse_ms) {
        furi_string_cat_printf(output, "P: %u * %u ms\n", model->pulses, model->pulse_ms);
    }
    if(model->gap_ms) {
        furi_string_cat_printf(output, "Gaps: %u ms\n", model->gap_ms);
    }
    elements_multiline_text(
        canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, furi_string_get_cstr(output));

    furi_string_free(output);
}

static bool dtmf_dolphin_dialer_input_callback(InputEvent* event, void* context) {
    furi_assert(context);
    DTMFDolphinDialer* dtmf_dolphin_dialer = context;
    bool consumed = false;

    if(event->type == InputTypeShort) {
        if(event->key == InputKeyRight) {
            consumed = dtmf_dolphin_dialer_process_right(dtmf_dolphin_dialer);
        } else if(event->key == InputKeyLeft) {
            consumed = dtmf_dolphin_dialer_process_left(dtmf_dolphin_dialer);
        } else if(event->key == InputKeyUp) {
            consumed = dtmf_dolphin_dialer_process_up(dtmf_dolphin_dialer);
        } else if(event->key == InputKeyDown) {
            consumed = dtmf_dolphin_dialer_process_down(dtmf_dolphin_dialer);
        }

    } else if(event->key == InputKeyOk) {
        consumed = dtmf_dolphin_dialer_process_ok(dtmf_dolphin_dialer, event);
    }

    return consumed;
}

static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer) {
    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            uint8_t span = 0;
            uint8_t cursor = model->row;
            while(span == 0 && cursor > 0) {
                cursor--;
                span = dtmf_dolphin_get_tone_span(cursor, model->col);
            }
            if(span != 0) {
                model->row = cursor;
            }
        },
        true);
    return true;
}

static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer) {
    uint8_t max_rows = 0;
    uint8_t max_cols = 0;
    uint8_t max_span = 0;
    dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);

    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            uint8_t span = 0;
            uint8_t cursor = model->row;
            while(span == 0 && cursor < max_rows - 1) {
                cursor++;
                span = dtmf_dolphin_get_tone_span(cursor, model->col);
            }
            if(span != 0) {
                model->row = cursor;
            }
        },
        true);
    return true;
}

static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer) {
    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            uint8_t span = 0;
            uint8_t cursor = model->col;
            while(span == 0 && cursor > 0) {
                cursor--;
                span = dtmf_dolphin_get_tone_span(model->row, cursor);
            }
            if(span != 0) {
                model->col = cursor;
            }
        },
        true);
    return true;
}

static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer) {
    uint8_t max_rows = 0;
    uint8_t max_cols = 0;
    uint8_t max_span = 0;
    dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);

    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            uint8_t span = 0;
            uint8_t cursor = model->col;
            while(span == 0 && cursor < max_cols - 1) {
                cursor++;
                span = dtmf_dolphin_get_tone_span(model->row, cursor);
            }
            if(span != 0) {
                model->col = cursor;
            }
        },
        true);
    return true;
}

static bool
    dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event) {
    bool consumed = false;

    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            if(event->type == InputTypePress) {
                model->playing = dtmf_dolphin_audio_play_tones(
                    model->freq1, model->freq2, model->pulses, model->pulse_ms, model->gap_ms);
            } else if(event->type == InputTypeRelease) {
                model->playing = !dtmf_dolphin_audio_stop_tones();
            }
        },
        true);

    return consumed;
}

static void dtmf_dolphin_dialer_enter_callback(void* context) {
    furi_assert(context);
    DTMFDolphinDialer* dtmf_dolphin_dialer = context;

    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            model->col = 0;
            model->row = 0;
            model->section = 0;
            model->freq1 = 0.0;
            model->freq2 = 0.0;
            model->playing = false;
        },
        true);
}

DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() {
    DTMFDolphinDialer* dtmf_dolphin_dialer = malloc(sizeof(DTMFDolphinDialer));

    dtmf_dolphin_dialer->view = view_alloc();
    view_allocate_model(
        dtmf_dolphin_dialer->view, ViewModelTypeLocking, sizeof(DTMFDolphinDialerModel));

    with_view_model(
        dtmf_dolphin_dialer->view,
        DTMFDolphinDialerModel * model,
        {
            model->col = 0;
            model->row = 0;
            model->section = 0;
            model->freq1 = 0.0;
            model->freq2 = 0.0;
            model->playing = false;
        },
        true);

    view_set_context(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer);
    view_set_draw_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_draw_callback);
    view_set_input_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_input_callback);
    view_set_enter_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_enter_callback);
    return dtmf_dolphin_dialer;
}

void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer) {
    furi_assert(dtmf_dolphin_dialer);
    view_free(dtmf_dolphin_dialer->view);
    free(dtmf_dolphin_dialer);
}

View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer) {
    furi_assert(dtmf_dolphin_dialer);
    return dtmf_dolphin_dialer->view;
}