#include "../main.h"

/**
 * SceneManagerHandlers initialization using the macro.
 */
void (*const scene_on_enter_handlers[])(void* context) = {
#define SCENE_ACTION(scene) scene##_on_enter,
#include "list.h"
#undef SCENE_ACTION
};

bool (*const scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#define SCENE_ACTION(scene) scene##_on_event,
#include "list.h"
#undef SCENE_ACTION
};

void (*const scene_on_exit_handlers[])(void* context) = {
#define SCENE_ACTION(scene) scene##_on_exit,
#include "list.h"
#undef SCENE_ACTION
};

const SceneManagerHandlers scene_handlers = {
    .on_enter_handlers = scene_on_enter_handlers,
    .on_event_handlers = scene_on_event_handlers,
    .on_exit_handlers = scene_on_exit_handlers,
    .scene_num = AppSceneNum,
};

/**
 * Register all scenes.
 */

void register_scenes(App* app) {
    app->scene_manager = scene_manager_alloc(&scene_handlers, app);
    furi_assert(app->scene_manager != NULL, "Failed to allocate scene manager.");

    app->view_dispatcher = view_dispatcher_alloc();
    furi_assert(app->view_dispatcher != NULL, "Failed to allocate view dispatcher.");

    if(app->allocated_scenes == NULL) {
        app->allocated_scenes = (void**)malloc(sizeof(void*) * AppSceneNum);
    }

    view_dispatcher_enable_queue(app->view_dispatcher);

    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
    view_dispatcher_set_custom_event_callback(app->view_dispatcher, scene_handler_event_forwarder);
    view_dispatcher_set_navigation_event_callback(
        app->view_dispatcher, scene_handler_navigation_forwarder);

    View* view = NULL;
#define SCENE_ACTION(scene)                                                                   \
    app->allocated_scenes[scene] = (void*)scene##_alloc();                                    \
    furi_assert(                                                                              \
        app->allocated_scenes[scene] != NULL, "Failed to allocate scene: " STRINGIFY(scene)); \
    view = scene##_get_view(app->allocated_scenes[scene]);                                    \
    furi_assert(view != NULL, "Failed to get view for scene: " STRINGIFY(scene));             \
    view_dispatcher_add_view(app->view_dispatcher, scene, view);
#include "list.h"
#undef SCENE_ACTION
}

/**
 * Free all scenes.
 */
void free_scenes(App* app) {
    FURI_LOG_I("DemoApp", "Freeing scenes.");
    furi_assert(app != NULL, "App is NULL.");
    void* tmp;
#define SCENE_ACTION(scene)                                                                    \
    if(app->allocated_scenes != NULL) {                                                        \
        tmp = app->allocated_scenes[scene];                                                    \
        app->allocated_scenes[scene] = NULL;                                                   \
        FURI_LOG_I("DemoApp", "Freeing scene " STRINGIFY(scene) ".");                          \
        if(tmp != NULL) scene##_free(tmp);                                                     \
        FURI_LOG_I("DemoApp", "Free'd scene " STRINGIFY(scene) ".");                           \
    }                                                                                          \
    if(app->view_dispatcher != NULL) view_dispatcher_remove_view(app->view_dispatcher, scene); \
    FURI_LOG_I("DemoApp", "Removed from dispatcher " STRINGIFY(scene) ".");

#include "list.h"
#undef SCENE_ACTION

    FURI_LOG_I("DemoApp", "Freeing allocated scenes.");
    furi_assert(app->allocated_scenes != NULL, "Allocated scenes is NULL.");
    free(app->allocated_scenes);
    app->allocated_scenes = NULL;

    FURI_LOG_I("DemoApp", "Freeing View dispatcher.");
    furi_assert(app->view_dispatcher != NULL, "View dispatcher is NULL.");
    view_dispatcher_free(app->view_dispatcher);

    FURI_LOG_I("DemoApp", "Freeing SceneManager");
    furi_assert(app->scene_manager != NULL, "Scene manager is NULL.");
    scene_manager_free(app->scene_manager);

    FURI_LOG_I("DemoApp", "Freeing App");
    free(app);
}
