#include <furi.h>
#include <furi_hal.h>

#include <infrared_worker.h>

#include <gui/gui.h>
#include <input/input.h>
#include <dialogs/dialogs.h>
#include <ir_remote_icons.h>

#include <notification/notification.h>
#include <notification/notification_messages.h>

#include "infrared_signal.h"
#include "infrared_remote.h"
#include "infrared_remote_button.h"
#define TAG "ir_remote"

#include <flipper_format/flipper_format.h>

typedef struct {
    int status;
    ViewPort* view_port;
    FuriString* up_button;
    FuriString* down_button;
    FuriString* left_button;
    FuriString* right_button;
    FuriString* ok_button;
    FuriString* back_button;
    FuriString* up_hold_button;
    FuriString* down_hold_button;
    FuriString* left_hold_button;
    FuriString* right_hold_button;
    FuriString* ok_hold_button;
    InfraredWorker* infrared_worker;
} IRApp;

// Screen is 128x64 px
static void app_draw_callback(Canvas* canvas, void* ctx) {
    // Show config is incorrect when cannot read the remote file
    // Showing button string in the screen, upper part is short press, lower part is long press
    IRApp* app = ctx;
    if(app->status) {
        canvas_clear(canvas);
        view_port_set_orientation(app->view_port, ViewPortOrientationHorizontal);
        canvas_set_font(canvas, FontPrimary);
        canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Config is incorrect.");
        canvas_set_font(canvas, FontSecondary);
        canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "Please configure map.");
        canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit.");
    } else {
        canvas_clear(canvas);
        view_port_set_orientation(app->view_port, ViewPortOrientationVertical);
        canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4);
        canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4);
        canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7);
        canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7);
        canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9);
        canvas_draw_icon(canvas, 0, 53, &I_back_10px);

        //Labels
        canvas_set_font(canvas, FontSecondary);

        canvas_draw_str_aligned(
            canvas, 32, 8, AlignCenter, AlignCenter, furi_string_get_cstr(app->up_button));
        canvas_draw_str_aligned(
            canvas, 32, 18, AlignCenter, AlignCenter, furi_string_get_cstr(app->down_button));
        canvas_draw_str_aligned(
            canvas, 32, 28, AlignCenter, AlignCenter, furi_string_get_cstr(app->left_button));
        canvas_draw_str_aligned(
            canvas, 32, 38, AlignCenter, AlignCenter, furi_string_get_cstr(app->right_button));
        canvas_draw_str_aligned(
            canvas, 32, 48, AlignCenter, AlignCenter, furi_string_get_cstr(app->ok_button));
        canvas_draw_str_aligned(
            canvas, 32, 58, AlignCenter, AlignCenter, furi_string_get_cstr(app->back_button));

        canvas_draw_line(canvas, 0, 65, 64, 65);

        canvas_draw_icon(canvas, 1, 70, &I_ButtonUp_7x4);
        canvas_draw_icon(canvas, 1, 80, &I_ButtonDown_7x4);
        canvas_draw_icon(canvas, 2, 88, &I_ButtonLeft_4x7);
        canvas_draw_icon(canvas, 2, 98, &I_ButtonRight_4x7);
        canvas_draw_icon(canvas, 0, 107, &I_Ok_btn_9x9);
        canvas_draw_icon(canvas, 0, 118, &I_back_10px);

        canvas_draw_str_aligned(
            canvas, 32, 73, AlignCenter, AlignCenter, furi_string_get_cstr(app->up_hold_button));
        canvas_draw_str_aligned(
            canvas, 32, 83, AlignCenter, AlignCenter, furi_string_get_cstr(app->down_hold_button));
        canvas_draw_str_aligned(
            canvas, 32, 93, AlignCenter, AlignCenter, furi_string_get_cstr(app->left_hold_button));
        canvas_draw_str_aligned(
            canvas,
            32,
            103,
            AlignCenter,
            AlignCenter,
            furi_string_get_cstr(app->right_hold_button));
        canvas_draw_str_aligned(
            canvas, 32, 113, AlignCenter, AlignCenter, furi_string_get_cstr(app->ok_hold_button));
        canvas_draw_str_aligned(canvas, 32, 123, AlignCenter, AlignCenter, "Exit App");
    }
}

static void app_input_callback(InputEvent* input_event, void* ctx) {
    furi_assert(ctx);

    FuriMessageQueue* event_queue = ctx;
    furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}

int32_t infrared_remote_app(void* p) {
    UNUSED(p);
    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));

    // App button string
    IRApp* app = malloc(sizeof(IRApp));
    app->up_button = furi_string_alloc();
    app->down_button = furi_string_alloc();
    app->left_button = furi_string_alloc();
    app->right_button = furi_string_alloc();
    app->ok_button = furi_string_alloc();
    app->back_button = furi_string_alloc();
    app->up_hold_button = furi_string_alloc();
    app->down_hold_button = furi_string_alloc();
    app->left_hold_button = furi_string_alloc();
    app->right_hold_button = furi_string_alloc();
    app->ok_hold_button = furi_string_alloc();
    app->view_port = view_port_alloc();
    app->infrared_worker = infrared_worker_alloc();

    // Configure view port
    view_port_draw_callback_set(app->view_port, app_draw_callback, app);
    view_port_input_callback_set(app->view_port, app_input_callback, event_queue);

    // Register view port in GUI
    Gui* gui = furi_record_open(RECORD_GUI);
    gui_add_view_port(gui, app->view_port, GuiLayerFullscreen);

    InputEvent event;

    Storage* storage = furi_record_open(RECORD_STORAGE);
    FlipperFormat* ff = flipper_format_file_alloc(storage);

    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
    DialogsFileBrowserOptions browser_options;
    dialog_file_browser_set_basic_options(&browser_options, ".txt", &I_sub1_10px);
    FuriString* map_file = furi_string_alloc();
    furi_string_set(map_file, "/ext/ir_remote");

    bool res = dialog_file_browser_show(dialogs, map_file, map_file, &browser_options);

    furi_record_close(RECORD_DIALOGS);

    // if user didn't choose anything, free everything and exit
    if(!res) {
        FURI_LOG_I(TAG, "exit");
        flipper_format_free(ff);
        furi_record_close(RECORD_STORAGE);

        furi_string_free(app->up_button);
        furi_string_free(app->down_button);
        furi_string_free(app->left_button);
        furi_string_free(app->right_button);
        furi_string_free(app->ok_button);
        furi_string_free(app->back_button);
        furi_string_free(app->up_hold_button);
        furi_string_free(app->down_hold_button);
        furi_string_free(app->left_hold_button);
        furi_string_free(app->right_hold_button);
        furi_string_free(app->ok_hold_button);

        view_port_enabled_set(app->view_port, false);
        gui_remove_view_port(gui, app->view_port);
        view_port_free(app->view_port);
        free(app);
        furi_message_queue_free(event_queue);

        furi_record_close(RECORD_GUI);
        return 255;
    }

    InfraredRemote* remote = infrared_remote_alloc();
    FuriString* remote_path = furi_string_alloc();

    InfraredSignal* up_signal = infrared_signal_alloc();
    InfraredSignal* down_signal = infrared_signal_alloc();
    InfraredSignal* left_signal = infrared_signal_alloc();
    InfraredSignal* right_signal = infrared_signal_alloc();
    InfraredSignal* ok_signal = infrared_signal_alloc();
    InfraredSignal* back_signal = infrared_signal_alloc();
    InfraredSignal* up_hold_signal = infrared_signal_alloc();
    InfraredSignal* down_hold_signal = infrared_signal_alloc();
    InfraredSignal* left_hold_signal = infrared_signal_alloc();
    InfraredSignal* right_hold_signal = infrared_signal_alloc();
    InfraredSignal* ok_hold_signal = infrared_signal_alloc();

    InfraredSignal* active_signal = NULL;
    bool is_transmitting = false;

    bool up_enabled = false;
    bool down_enabled = false;
    bool left_enabled = false;
    bool right_enabled = false;
    bool ok_enabled = false;
    bool back_enabled = false;
    bool up_hold_enabled = false;
    bool down_hold_enabled = false;
    bool left_hold_enabled = false;
    bool right_hold_enabled = false;
    bool ok_hold_enabled = false;

    if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(map_file))) {
        FURI_LOG_E(TAG, "Could not open MAP file %s", furi_string_get_cstr(map_file));
        app->status = 1;
    } else {
        //Filename Assignment/Check Start

        if(!flipper_format_read_string(ff, "REMOTE", remote_path)) {
            FURI_LOG_E(TAG, "Could not read REMOTE string");
            app->status = 1;
        } else {
            if(!infrared_remote_load(remote, remote_path)) {
                FURI_LOG_E(TAG, "Could not load ir file: %s", furi_string_get_cstr(remote_path));
                app->status = 1;
            } else {
                FURI_LOG_I(TAG, "Loaded REMOTE file: %s", furi_string_get_cstr(remote_path));
            }
        }

        //assign variables to values within map file
        //set missing filenames to N/A
        //assign button signals
        size_t index = 0;
        if(!flipper_format_read_string(ff, "UP", app->up_button)) {
            FURI_LOG_W(TAG, "Could not read UP string");
            furi_string_set(app->up_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->up_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                up_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                up_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "DOWN", app->down_button)) {
            FURI_LOG_W(TAG, "Could not read DOWN string");
            furi_string_set(app->down_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->down_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                down_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                down_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "LEFT", app->left_button)) {
            FURI_LOG_W(TAG, "Could not read LEFT string");
            furi_string_set(app->left_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->left_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                left_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                left_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "RIGHT", app->right_button)) {
            FURI_LOG_W(TAG, "Could not read RIGHT string");
            furi_string_set(app->right_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->right_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                right_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                right_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "OK", app->ok_button)) {
            FURI_LOG_W(TAG, "Could not read OK string");
            furi_string_set(app->ok_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->ok_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                ok_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                ok_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "BACK", app->back_button)) {
            FURI_LOG_W(TAG, "Could not read BACK string");
            furi_string_set(app->back_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->back_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                back_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                back_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "UPHOLD", app->up_hold_button)) {
            FURI_LOG_W(TAG, "Could not read UPHOLD string");
            furi_string_set(app->up_hold_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->up_hold_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                up_hold_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                up_hold_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "DOWNHOLD", app->down_hold_button)) {
            FURI_LOG_W(TAG, "Could not read DOWNHOLD string");
            furi_string_set(app->down_hold_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->down_hold_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                down_hold_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                down_hold_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "LEFTHOLD", app->left_hold_button)) {
            FURI_LOG_W(TAG, "Could not read LEFTHOLD string");
            furi_string_set(app->left_hold_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->left_hold_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                left_hold_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                left_hold_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "RIGHTHOLD", app->right_hold_button)) {
            FURI_LOG_W(TAG, "Could not read RIGHTHOLD string");
            furi_string_set(app->right_hold_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->right_hold_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                right_hold_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                right_hold_enabled = true;
            }
        }

        if(!flipper_format_read_string(ff, "OKHOLD", app->ok_hold_button)) {
            FURI_LOG_W(TAG, "Could not read OKHOLD string");
            furi_string_set(app->ok_hold_button, "N/A");
        } else {
            if(!infrared_remote_find_button_by_name(
                   remote, furi_string_get_cstr(app->ok_hold_button), &index)) {
                FURI_LOG_W(TAG, "Error");
            } else {
                ok_hold_signal =
                    infrared_remote_button_get_signal(infrared_remote_get_button(remote, index));
                ok_hold_enabled = true;
            }
        }
    }

    furi_string_free(remote_path);

    flipper_format_free(ff);
    furi_record_close(RECORD_STORAGE);

    bool running = true;
    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);

    if(app->status) {
        view_port_update(app->view_port);
        while(running) {
            if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
                if(event.type == InputTypeShort) {
                    switch(event.key) {
                    case InputKeyBack:
                        running = false;
                        break;
                    default:
                        break;
                    }
                }
            }
        }
    } else {
        view_port_update(app->view_port);
        while(running) {
            if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
                // short press signal
                if(event.type == InputTypeShort) {
                    switch(event.key) {
                    case InputKeyUp:
                        if(up_enabled) {
                            active_signal = up_signal;
                            FURI_LOG_I(TAG, "up");
                        }
                        break;
                    case InputKeyDown:
                        if(down_enabled) {
                            active_signal = down_signal;
                            FURI_LOG_I(TAG, "down");
                        }
                        break;
                    case InputKeyRight:
                        if(right_enabled) {
                            active_signal = right_signal;
                            FURI_LOG_I(TAG, "right");
                        }
                        break;
                    case InputKeyLeft:
                        if(left_enabled) {
                            active_signal = left_signal;
                            FURI_LOG_I(TAG, "left");
                        }
                        break;
                    case InputKeyOk:
                        if(ok_enabled) {
                            active_signal = ok_signal;
                            FURI_LOG_I(TAG, "ok");
                        }
                        break;
                    case InputKeyBack:
                        if(back_enabled) {
                            active_signal = back_signal;
                            FURI_LOG_I(TAG, "back");
                        }
                        break;
                    default:
                        running = false;
                        break;
                    }
                    // long press signal
                } else if(event.type == InputTypeLong) {
                    switch(event.key) {
                    case InputKeyUp:
                        if(up_hold_enabled) {
                            active_signal = up_hold_signal;
                            FURI_LOG_I(TAG, "up!");
                        }
                        break;
                    case InputKeyDown:
                        if(down_hold_enabled) {
                            active_signal = down_hold_signal;
                            FURI_LOG_I(TAG, "down!");
                        }
                        break;
                    case InputKeyRight:
                        if(right_hold_enabled) {
                            active_signal = right_hold_signal;
                            FURI_LOG_I(TAG, "right!");
                        }
                        break;
                    case InputKeyLeft:
                        if(left_hold_enabled) {
                            active_signal = left_hold_signal;
                            FURI_LOG_I(TAG, "left!");
                        }
                        break;
                    case InputKeyOk:
                        if(ok_hold_enabled) {
                            active_signal = ok_hold_signal;
                            FURI_LOG_I(TAG, "ok!");
                        }
                        break;
                    default:
                        running = false;
                        break;
                    }
                } else if(event.type == InputTypeRelease && is_transmitting) {
                    notification_message(notification, &sequence_blink_stop);
                    infrared_worker_tx_stop(app->infrared_worker);
                    is_transmitting = false;
                    active_signal = NULL;
                }

                if(active_signal != NULL &&
                   (event.type == InputTypeShort || event.type == InputTypeLong)) {
                    if(is_transmitting) {
                        infrared_worker_tx_stop(app->infrared_worker);
                    }

                    if(infrared_signal_is_raw(active_signal)) {
                        InfraredRawSignal* raw_signal =
                            infrared_signal_get_raw_signal(active_signal);
                        infrared_worker_set_raw_signal(
                            app->infrared_worker,
                            raw_signal->timings,
                            raw_signal->timings_size,
                            raw_signal->frequency,
                            raw_signal->duty_cycle);
                    } else {
                        InfraredMessage* message = infrared_signal_get_message(active_signal);
                        infrared_worker_set_decoded_signal(app->infrared_worker, message);
                    }

                    infrared_worker_tx_set_get_signal_callback(
                        app->infrared_worker, infrared_worker_tx_get_signal_steady_callback, app);

                    infrared_worker_tx_start(app->infrared_worker);
                    notification_message(notification, &sequence_blink_start_magenta);
                    is_transmitting = true;
                }
            }
            view_port_update(app->view_port);
        }
    }

    // Free all things
    furi_string_free(app->up_button);
    furi_string_free(app->down_button);
    furi_string_free(app->left_button);
    furi_string_free(app->right_button);
    furi_string_free(app->ok_button);
    furi_string_free(app->back_button);
    furi_string_free(app->up_hold_button);
    furi_string_free(app->down_hold_button);
    furi_string_free(app->left_hold_button);
    furi_string_free(app->right_hold_button);
    furi_string_free(app->ok_hold_button);

    if(is_transmitting) {
        infrared_worker_tx_stop(app->infrared_worker);
        notification_message(notification, &sequence_blink_stop);
    }
    infrared_worker_free(app->infrared_worker);

    infrared_remote_free(remote);
    view_port_enabled_set(app->view_port, false);
    gui_remove_view_port(gui, app->view_port);
    view_port_free(app->view_port);
    free(app);
    furi_message_queue_free(event_queue);

    furi_record_close(RECORD_NOTIFICATION);
    furi_record_close(RECORD_GUI);

    return 0;
}
