#include "nfc_protocol_support.h"
 
#include "nfc/nfc_app_i.h"
#include "nfc/helpers/nfc_supported_cards.h"
 
#include "nfc_protocol_support_defs.h"
#include "nfc_protocol_support_gui_common.h"
 
typedef void (*NfcProtocolSupportCommonOnEnter)(NfcApp* instance);
typedef bool (*NfcProtocolSupportCommonOnEvent)(NfcApp* instance, SceneManagerEvent event);
typedef void (*NfcProtocolSupportCommonOnExit)(NfcApp* instance);
 
typedef struct {
    NfcProtocolSupportCommonOnEnter on_enter;
    NfcProtocolSupportCommonOnEvent on_event;
    NfcProtocolSupportCommonOnExit on_exit;
} NfcProtocolSupportCommonSceneBase;
 
static const NfcProtocolSupportCommonSceneBase nfc_protocol_support_scenes[];
 
// Interface functions
void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context) {
    furi_assert(scene < NfcProtocolSupportSceneCount);
    furi_assert(context);
 
    NfcApp* instance = context;
    nfc_protocol_support_scenes[scene].on_enter(instance);
}
 
bool nfc_protocol_support_on_event(
    NfcProtocolSupportScene scene,
    void* context,
    SceneManagerEvent event) {
    furi_assert(scene < NfcProtocolSupportSceneCount);
    furi_assert(context);
 
    NfcApp* instance = context;
    return nfc_protocol_support_scenes[scene].on_event(instance, event);
}
 
void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) {
    furi_assert(scene < NfcProtocolSupportSceneCount);
    furi_assert(context);
 
    NfcApp* instance = context;
    nfc_protocol_support_scenes[scene].on_exit(instance);
}
 
static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
    return nfc_protocol_support[protocol]->features & feature;
}
 
// Common scene handlers
// SceneInfo
static void nfc_protocol_support_scene_info_on_enter(NfcApp* instance) {
    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
    nfc_protocol_support[protocol]->scene_info.on_enter(instance);
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
 
static bool nfc_protocol_support_scene_info_on_event(NfcApp* instance, SceneManagerEvent event) {
    bool consumed = false;
 
    if(event.type == SceneManagerEventTypeCustom) {
        const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
        consumed = nfc_protocol_support[protocol]->scene_info.on_event(instance, event.event);
    } else if(event.type == SceneManagerEventTypeBack) {
        // If the card could not be parsed, return to the respective menu
        if(!scene_manager_get_scene_state(instance->scene_manager, NfcSceneSupportedCard)) {
            const uint32_t scenes[] = {NfcSceneSavedMenu, NfcSceneReadMenu};
            scene_manager_search_and_switch_to_previous_scene_one_of(
                instance->scene_manager, scenes, COUNT_OF(scenes));
            consumed = true;
        }
    }
 
    return consumed;
}
 
static void nfc_protocol_support_scene_info_on_exit(NfcApp* instance) {
    widget_reset(instance->widget);
}
 
static void nfc_protocol_support_scene_card_dump_on_enter(NfcApp* instance) {
    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
    nfc_protocol_support[protocol]->scene_card_dump.on_enter(instance);
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
}
 
static bool
    nfc_protocol_support_scene_card_dump_on_event(NfcApp* instance, SceneManagerEvent event) {
    UNUSED(instance);
    UNUSED(event);
    return false;
}
 
static void nfc_protocol_support_scene_card_dump_on_exit(NfcApp* instance) {
    text_box_reset(instance->text_box);
    furi_string_reset(instance->text_box_store);
}
 
// SceneRead
static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) {
    popup_set_header(
        instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop);
    popup_set_icon(instance->popup, 12, 23, &A_Loading_24);
 
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
 
    const NfcProtocol protocol = instance->protocols_detected[instance->protocols_detected_idx];
    instance->poller = nfc_poller_alloc(instance->nfc, protocol);
 
    // Start poller with the appropriate callback
    nfc_protocol_support[protocol]->scene_read.on_enter(instance);
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
 
    nfc_blink_detect_start(instance);
}
 
static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) {
    bool consumed = false;
 
    if(event.type == SceneManagerEventTypeCustom) {
        if(event.event == NfcCustomEventPollerSuccess) {
            notification_message(instance->notifications, &sequence_success);
            scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
            dolphin_deed(DolphinDeedNfcReadSuccess);
            consumed = true;
        } else if(event.event == NfcCustomEventPollerIncomplete) {
            nfc_supported_cards_read(instance->nfc_device, instance->nfc);
 
            view_dispatcher_send_custom_event(
                instance->view_dispatcher, NfcCustomEventPollerSuccess);
            consumed = true;
        } else if(event.event == NfcCustomEventPollerFailure) {
            if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDetect)) {
                scene_manager_search_and_switch_to_previous_scene(
                    instance->scene_manager, NfcSceneDetect);
            }
            consumed = true;
        }
    } else if(event.type == SceneManagerEventTypeBack) {
        static const uint32_t possible_scenes[] = {NfcSceneSelectProtocol, NfcSceneStart};
        scene_manager_search_and_switch_to_previous_scene_one_of(
            instance->scene_manager, possible_scenes, COUNT_OF(possible_scenes));
        consumed = true;
    }
 
    return consumed;
}
 
static void nfc_protocol_support_scene_read_on_exit(NfcApp* instance) {
    nfc_poller_stop(instance->poller);
    nfc_poller_free(instance->poller);
    popup_reset(instance->popup);
 
    nfc_blink_stop(instance);
}
 
// SceneReadMenu
static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) {
    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 
    Submenu* submenu = instance->submenu;
 
    submenu_add_item(
        submenu,
        "Save",
        SubmenuIndexCommonSave,
        nfc_protocol_support_common_submenu_callback,
        instance);
 
    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
        submenu_add_item(
            submenu,
            "Emulate UID",
            SubmenuIndexCommonEmulate,
            nfc_protocol_support_common_submenu_callback,
            instance);
 
    } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) {
        submenu_add_item(
            submenu,
            "Emulate",
            SubmenuIndexCommonEmulate,
            nfc_protocol_support_common_submenu_callback,
            instance);
    }
 
    nfc_protocol_support[protocol]->scene_read_menu.on_enter(instance);
 
    submenu_add_item(
        submenu,
        "Info",
        SubmenuIndexCommonInfo,
        nfc_protocol_support_common_submenu_callback,
        instance);
 
    submenu_set_selected_item(
        instance->submenu,
        scene_manager_get_scene_state(instance->scene_manager, NfcSceneReadMenu));
 
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
}
 
static bool
    nfc_protocol_support_scene_read_menu_on_event(NfcApp* instance, SceneManagerEvent event) {
    bool consumed = false;
 
    if(event.type == SceneManagerEventTypeCustom) {
        scene_manager_set_scene_state(instance->scene_manager, NfcSceneReadMenu, event.event);
 
        if(event.event == SubmenuIndexCommonSave) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName);
            consumed = true;
        } else if(event.event == SubmenuIndexCommonInfo) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneInfo);
            consumed = true;
        } else if(event.event == SubmenuIndexCommonEmulate) {
            dolphin_deed(DolphinDeedNfcEmulate);
            scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
            consumed = true;
        } else {
            const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
            consumed =
                nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event);
        }
 
    } else if(event.type == SceneManagerEventTypeBack) {
        scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0);
    }
 
    return consumed;
}
 
static void nfc_protocol_support_scene_read_menu_on_exit(NfcApp* instance) {
    submenu_reset(instance->submenu);
}
 
// SceneReadSuccess
static void nfc_protocol_support_scene_read_success_on_enter(NfcApp* instance) {
    Widget* widget = instance->widget;
 
    FuriString* temp_str = furi_string_alloc();
    if(nfc_supported_cards_parse(instance->nfc_device, temp_str)) {
        widget_add_text_scroll_element(
            instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
    } else {
        const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
        nfc_protocol_support[protocol]->scene_read_success.on_enter(instance);
    }
 
    furi_string_free(temp_str);
 
    widget_add_button_element(
        widget, GuiButtonTypeLeft, "Retry", nfc_protocol_support_common_widget_callback, instance);
    widget_add_button_element(
        widget, GuiButtonTypeRight, "More", nfc_protocol_support_common_widget_callback, instance);
 
    notification_message_block(instance->notifications, &sequence_set_green_255);
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
 
static bool
    nfc_protocol_support_scene_read_success_on_event(NfcApp* instance, SceneManagerEvent event) {
    bool consumed = false;
 
    if(event.type == SceneManagerEventTypeCustom) {
        if(event.event == GuiButtonTypeLeft) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm);
            consumed = true;
 
        } else if(event.event == GuiButtonTypeRight) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu);
            consumed = true;
        }
 
    } else if(event.type == SceneManagerEventTypeBack) {
        scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm);
        consumed = true;
    }
 
    return consumed;
}
 
static void nfc_protocol_support_scene_read_success_on_exit(NfcApp* instance) {
    notification_message_block(instance->notifications, &sequence_reset_green);
    widget_reset(instance->widget);
}
 
// SceneSavedMenu
static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) {
    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 
    Submenu* submenu = instance->submenu;
 
    // Header submenu items
    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
        submenu_add_item(
            submenu,
            "Emulate UID",
            SubmenuIndexCommonEmulate,
            nfc_protocol_support_common_submenu_callback,
            instance);
 
    } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) {
        submenu_add_item(
            submenu,
            "Emulate",
            SubmenuIndexCommonEmulate,
            nfc_protocol_support_common_submenu_callback,
            instance);
    }
 
    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEditUid)) {
        submenu_add_item(
            submenu,
            "Edit UID",
            SubmenuIndexCommonEdit,
            nfc_protocol_support_common_submenu_callback,
            instance);
    }
 
    // Protocol-dependent menu items
    nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance);
 
    // Trailer submenu items
    submenu_add_item(
        submenu,
        "Info",
        SubmenuIndexCommonInfo,
        nfc_protocol_support_common_submenu_callback,
        instance);
    submenu_add_item(
        submenu,
        "Rename",
        SubmenuIndexCommonRename,
        nfc_protocol_support_common_submenu_callback,
        instance);
    submenu_add_item(
        submenu,
        "Delete",
        SubmenuIndexCommonDelete,
        nfc_protocol_support_common_submenu_callback,
        instance);
 
    if(nfc_has_shadow_file(instance)) {
        submenu_add_item(
            submenu,
            "Restore Data Changes",
            SubmenuIndexCommonRestore,
            nfc_protocol_support_common_submenu_callback,
            instance);
    }
 
    submenu_set_selected_item(
        instance->submenu,
        scene_manager_get_scene_state(instance->scene_manager, NfcSceneSavedMenu));
 
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
}
 
static bool
    nfc_protocol_support_scene_saved_menu_on_event(NfcApp* instance, SceneManagerEvent event) {
    bool consumed = false;
 
    if(event.type == SceneManagerEventTypeCustom) {
        scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, event.event);
 
        if(event.event == SubmenuIndexCommonRestore) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneRestoreOriginalConfirm);
            consumed = true;
        } else if(event.event == SubmenuIndexCommonInfo) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneSupportedCard);
            consumed = true;
        } else if(event.event == SubmenuIndexCommonRename) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName);
            consumed = true;
        } else if(event.event == SubmenuIndexCommonDelete) {
            scene_manager_next_scene(instance->scene_manager, NfcSceneDelete);
            consumed = true;
        } else if(event.event == SubmenuIndexCommonEmulate) {
            const bool is_added =
                scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType);
            dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate);
            scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
            consumed = true;
        } else {
            const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
            consumed =
                nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event);
        }
 
    } else if(event.type == SceneManagerEventTypeBack) {
        scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0);
    }
 
    return consumed;
}
 
static void nfc_protocol_support_scene_saved_menu_on_exit(NfcApp* instance) {
    submenu_reset(instance->submenu);
}
 
// SceneEmulate
enum {
    NfcSceneEmulateStateWidget,
    NfcSceneEmulateStateTextBox,
};
 
static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
    Widget* widget = instance->widget;
    TextBox* text_box = instance->text_box;
 
    FuriString* temp_str = furi_string_alloc();
    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 
    widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
 
    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
        widget_add_string_element(
            widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID");
 
        size_t uid_len;
        const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
 
        for(size_t i = 0; i < uid_len; ++i) {
            furi_string_cat_printf(temp_str, "%02X ", uid[i]);
        }
 
        furi_string_trim(temp_str);
 
    } else {
        widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
        furi_string_set(
            temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
    }
 
    widget_add_text_box_element(
        widget, 56, 28, 68, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
 
    furi_string_free(temp_str);
 
    text_box_set_font(text_box, TextBoxFontHex);
    text_box_set_focus(text_box, TextBoxFocusEnd);
    furi_string_reset(instance->text_box_store);
 
    // instance->listener is allocated in the respective on_enter() handler
    nfc_protocol_support[protocol]->scene_emulate.on_enter(instance);
 
    scene_manager_set_scene_state(
        instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
 
    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
    nfc_blink_emulate_start(instance);
}
 
static bool
    nfc_protocol_support_scene_emulate_on_event(NfcApp* instance, SceneManagerEvent event) {
    bool consumed = false;
 
    const uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneEmulate);
 
    if(event.type == SceneManagerEventTypeCustom) {
        if(event.event == NfcCustomEventListenerUpdate) {
            // Add data button to widget if data is received for the first time
            if(furi_string_size(instance->text_box_store)) {
                widget_add_button_element(
                    instance->widget,
                    GuiButtonTypeCenter,
                    "Log",
                    nfc_protocol_support_common_widget_callback,
                    instance);
            }
            // Update TextBox data
            text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
            consumed = true;
        } else if(event.event == GuiButtonTypeCenter) {
            if(state == NfcSceneEmulateStateWidget) {
                view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
                scene_manager_set_scene_state(
                    instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox);
                consumed = true;
            }
        }
    } else if(event.type == SceneManagerEventTypeBack) {
        if(state == NfcSceneEmulateStateTextBox) {
            view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
            scene_manager_set_scene_state(
                instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
            consumed = true;
        }
    }
 
    return consumed;
}
 
static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) {
    nfc_listener_stop(instance->listener);
 
    NfcDevice* nfc_stub = nfc_device_alloc();
    NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
    const NfcDeviceData* data = nfc_listener_get_data(instance->listener, protocol);
    nfc_device_set_data(nfc_stub, protocol, data);
 
    //TODO: think about nfc_device_is_equal(NfcDevice*,NfcDeviceData*);
    if(!nfc_device_is_equal(nfc_stub, instance->nfc_device)) {
        nfc_device_set_data(instance->nfc_device, protocol, data);
        nfc_save_shadow_file(instance);
    }
    nfc_device_free(nfc_stub);
 
    nfc_listener_free(instance->listener);
 
    // Clear view
    widget_reset(instance->widget);
    text_box_reset(instance->text_box);
    furi_string_reset(instance->text_box_store);
 
    nfc_blink_stop(instance);
}
 
static const NfcProtocolSupportCommonSceneBase
    nfc_protocol_support_scenes[NfcProtocolSupportSceneCount] = {
        [NfcProtocolSupportSceneInfo] =
            {
                .on_enter = nfc_protocol_support_scene_info_on_enter,
                .on_event = nfc_protocol_support_scene_info_on_event,
                .on_exit = nfc_protocol_support_scene_info_on_exit,
            },
        [NfcProtocolSupportSceneCardDump] =
            {
                .on_enter = nfc_protocol_support_scene_card_dump_on_enter,
                .on_event = nfc_protocol_support_scene_card_dump_on_event,
                .on_exit = nfc_protocol_support_scene_card_dump_on_exit,
            },
        [NfcProtocolSupportSceneRead] =
            {
                .on_enter = nfc_protocol_support_scene_read_on_enter,
                .on_event = nfc_protocol_support_scene_read_on_event,
                .on_exit = nfc_protocol_support_scene_read_on_exit,
            },
        [NfcProtocolSupportSceneReadMenu] =
            {
                .on_enter = nfc_protocol_support_scene_read_menu_on_enter,
                .on_event = nfc_protocol_support_scene_read_menu_on_event,
                .on_exit = nfc_protocol_support_scene_read_menu_on_exit,
            },
        [NfcProtocolSupportSceneReadSuccess] =
            {
                .on_enter = nfc_protocol_support_scene_read_success_on_enter,
                .on_event = nfc_protocol_support_scene_read_success_on_event,
                .on_exit = nfc_protocol_support_scene_read_success_on_exit,
            },
        [NfcProtocolSupportSceneSavedMenu] =
            {
                .on_enter = nfc_protocol_support_scene_saved_menu_on_enter,
                .on_event = nfc_protocol_support_scene_saved_menu_on_event,
                .on_exit = nfc_protocol_support_scene_saved_menu_on_exit,
            },
        [NfcProtocolSupportSceneEmulate] =
            {
                .on_enter = nfc_protocol_support_scene_emulate_on_enter,
                .on_event = nfc_protocol_support_scene_emulate_on_event,
                .on_exit = nfc_protocol_support_scene_emulate_on_exit,
            },
};

V524 It is odd that the body of 'nfc_protocol_support_scene_saved_menu_on_exit' function is fully equivalent to the body of 'nfc_protocol_support_scene_read_menu_on_exit' function.