#pragma once

#include <stdbool.h>
#include <stdint.h>

#include <bnb/common_types.h>
#include <bnb/error.h>
#include <bnb/export.h>

#if __has_include(<bnb/recognizer.h>)
    #include <bnb/recognizer.h>
#else
    #include "recognizer.h"
#endif

#ifdef __cplusplus
extern "C"
{
#endif

    /**
     * EffectPlayer API
     */
    /** opaque object representing effect */
    typedef struct effect_holder effect_holder_t;
    /** opaque object representing full_image_t */
    typedef struct full_image_holder full_image_holder_t;
    /** opaque object representing effect_player */
    typedef struct effect_player_holder effect_player_holder_t;
    /** opaque object representing effect_manager */
    typedef struct effect_manager_holder effect_manager_holder_t;

    typedef struct frame_processor frame_processor_t;

    typedef struct processor_configuration processor_configuration_t;

    /** Legacy. Unsued */
    typedef enum
    {
        bnb_nn_mode_automatically,
        bnb_nn_mode_enable,
        bnb_nn_mode_disable
    } bnb_nn_mode_t;

    /**
     * These modes describe the relationship between processing loop and render loop frames
     *
     * <h3> Synchronous - Asynchronous:</h3>
     * <br/> - Synchronous renders a frame only once after a new frame was fully processed.
     * <br/> - Asynchronous may render multiple frames not waiting for frx. It's divided into Consistent and
     *    Inconsistent sub-modes. (note: Synchronous mode by definition is always Consistent.)
     *
     * <h3>Consistent - Inconsistent:</h3>
     * <br/> - Consistent renders the processed frames as-is - if frame is ready, it is rendered as-is, if not then nothing is rendered, but current timestamp for animation or video is incremented.
     * <br/> - Inconsistent changes the camera image for that frame to the latest available (not yet processed). It results in smoother rendering in the case when frx is slow or unsteady.
     */
    typedef enum
    {
        bnb_consistency_mode_synchronous,
        bnb_consistency_mode_synchronous_when_effect_loaded, ///< switches to async-inconsistent mode while effect is loading
        bnb_consistency_mode_asynchronous_inconsistent,
        bnb_consistency_mode_asynchronous_consistent,
        bnb_consistency_mode_asynchronous_consistent_when_effect_loaded ///< switches to async-inconsistent mode while effect is loading
    } bnb_consistency_mode_t;

    /**
     * Configuration for EffectPlayer
     */
    typedef struct
    {
        int32_t fx_width;  ///< Width of effect rendering area
        int32_t fx_height; ///< Height of effect rendering area

        bnb_nn_mode_t nn_enable; ///< Legacy. Has no effect

        bnb_face_search_mode_t face_search; ///< Legacy. Has no effect

        bool js_debugger_enable; ///< Legacy. Has no effect

        bool manual_audio; ///< Init audio device. Works only on iOS.
    } bnb_effect_player_configuration_t;

    /** type used for listeners ids */
    typedef uintptr_t listener_id_t;
    /** listeners */
    /** Effect activation callback */
    typedef void (*bnb_effect_activation_completion_listener_t)(const char* url, void* user_data);
    /** Error messages listener */
    typedef void (*bnb_error_listener_t)(const char* domain, const char* message, void* user_data);
    /** Full image planes releaser */
    typedef void (*bnb_full_image_data_releaser_t)(void*);
    /** Effect eval_js callback */
    typedef void (*bnb_effect_eval_js_callback_t)(const char* result);

    /**
     * Render backend type
     */
    typedef enum
    {
        bnb_render_backend_opengl,
        bnb_render_backend_metal,
    } bnb_render_backend_t;

    typedef enum
    {
        /** Synchronous mode. Frame drop not allowed, `pop` will block on processing. */
        bnb_realtime_processor_mode_sync,

        /**
         * Synchronous mode when effect loaded. Frame drop not allowed.
         * During effect loading `push` will forward frames to `pop`, `pop` will return SKIP.
         * When effect loaded, `pop` will block on processing.
         */
        bnb_realtime_processor_mode_sync_when_effect_loaded,

        /**
         * Asynchronous mode. Frame drop is allowed. `push` and `pop` do not block on processing.
         * `push` can drop frames, if processor busy,
         * `pop` can return EMPTY, if no processed frame data for now.
         */
        bnb_realtime_processor_mode_async,

        /**
         * Asynchronous mode. Frame drop is allowed. `push` and `pop` do not block on processing.
         * During effect loading `push` will forward frames to `pop`, `pop` will return SKIP.
         * When effect loaded, `push` can drop frames, if processor busy,
         * `pop` can return EMPTY, if no processed frame data for now.
         */
        bnb_realtime_processor_mode_async_when_effect_loaded
    } bnb_realtime_processor_mode_t;

    typedef enum
    {
        /** Live feed processor */
        bnb_processor_type_realtime,

        /** Photo processor */
        bnb_processor_type_photo,

        /** Video stream processor */
        bnb_processor_type_video,
    } bnb_processor_type_t;

    typedef enum
    {
        /** Processed frame data */
        bnb_processor_status_ok,
        /** Processor output buffer is empty */
        bnb_processor_status_empty,
        /** Processing is skipped */
        bnb_processor_status_skip,
        /** Error happen, check log */
        bnb_processor_status_error
    } bnb_processor_status_t;

    /** You must `destroy` `frame_data` in this object */
    typedef struct
    {
        bnb_processor_status_t status;
        frame_data_holder_t* frame_data;
    } bnb_processor_result_t;

    /**
     * Surface data type
     */
    typedef struct
    {
        int64_t gpu_device_ptr;
        int64_t command_queue_ptr;
        int64_t surface_ptr;
    } bnb_surface_data_t;

    /**
     * YUV color range
     */
    typedef enum
    {
        bnb_yuv_video_range, ///< Video (limited/partial/studio) range
        bnb_yuv_full_range   ///< Full range
    } bnb_yuv_color_range_t;

    /**
     * YUV color space
     */
    typedef enum
    {
        bnb_bt601, ///< BT.601
        bnb_bt709  ///< BT.709
    } bnb_yuv_color_space_t;

    /**
     * full_image_t
     */

    /**
     *  Example 1: Create images without copying
     *
     * The following functions support creating images without copying content:
     *      bnb_full_image_from_yuv_nv12_img_no_copy_ex(...)
     *      bnb_full_image_from_yuv_i420_img_no_copy_ex(...)
     *      bnb_full_image_from_bpc8_img_no_copy(...)
     * They additionally take a releaser function and user data as arguments. See example below.
     *
     * Example:
     * ... Somewhere in the code
     *      // Creating an image and allocating memory for it
     *      int width = 1280;
     *      int height = 720;
     *      int my_image_bytes_per_row = width * 3;
     *      void* my_image_rawptr = malloc(my_image_bytes_per_row * height); // allocate memory for RGB image
     * ...
     *      // Declaring and implementing a callback function
     *      void my_releaser_func(void* my_releaser_user_data) {
     *          void* my_image_rawptr = (void*)(releaser_user_data);
     *          free(my_image_rawptr); // deallocate memory for RGB image
     *      };
     * ...
     *      // Transferring an image to processing in the Banuba SDK with a callback function.
     *      void* my_releaser_user_data = (void*)(my_image_rawptr);
     *      full_image_holder_t* my_bnb_image = bnb_full_image_from_bpc8_img_no_copy(
     *          my_bnb_image_format, my_bnb_pixel_format,
     *          my_image_rawptr, my_image_bytes_per_row,
     *          my_releaser_func, my_releaser_user_data,
     *          nullptr);
     *
     *      // Then send the next frame my_bnb_image for processing and release it
     *      bnb_effect_player_push_frame(my_effect_player, my_bnb_image, nullptr);
     *      bnb_full_image_release(my_bnb_image, nullptr);
     * ...
     */

    /**
     * Creates full_image from bi-planar YUV in NV12 format.
     *
     * Expects BT601 Full range input.
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_yuv_nv12_img(
        bnb_image_format_t format,
        uint8_t* y_plane,
        int32_t y_stride,
        uint8_t* uv_plane,
        int32_t uv_stride,
        bnb_error**
    );
    /**
     * Creates full_image from bi-planar YUV in NV12 format.
     *
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_yuv_nv12_img_ex(
        const bnb_image_format_t* format,
        bnb_yuv_color_range_t range,
        bnb_yuv_color_space_t space,
        uint8_t* y_plane,
        int32_t y_stride,
        uint8_t* uv_plane,
        int32_t uv_stride,
        bnb_error**
    );
    /**
     * Creates full_image from bi-planar YUV in NV12 format without copying.
     *
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     *
     * see "Example 1: Create images without copying"
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_yuv_nv12_img_no_copy_ex(
        const bnb_image_format_t* format,
        bnb_yuv_color_range_t range,
        bnb_yuv_color_space_t space,
        uint8_t* y_plane,
        int32_t y_stride,
        uint8_t* uv_plane,
        int32_t uv_stride,
        bnb_full_image_data_releaser_t releaser,
        void* releaser_user_data,
        bnb_error**
    );
    /**
     * Creates full_image from planar YUV in I420 format.
     *
     * Expects BT601 Full range input.
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_yuv_i420_img(
        bnb_image_format_t format,
        uint8_t* y_plane,
        int32_t y_row_stride,
        int32_t y_pixel_stride,
        uint8_t* u_plane,
        int32_t u_row_stride,
        int32_t u_pixel_stride,
        uint8_t* v_plane,
        int32_t v_row_stride,
        int32_t v_pixel_stride,
        bnb_error**
    );
    /**
     * Creates full_image from planar YUV in I420 format.
     *
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_yuv_i420_img_ex(
        const bnb_image_format_t* format,
        bnb_yuv_color_range_t range,
        bnb_yuv_color_space_t space,
        uint8_t* y_plane,
        int32_t y_row_stride,
        int32_t y_pixel_stride,
        uint8_t* u_plane,
        int32_t u_row_stride,
        int32_t u_pixel_stride,
        uint8_t* v_plane,
        int32_t v_row_stride,
        int32_t v_pixel_stride,
        bnb_error**
    );
    /**
     * Creates full_image from planar YUV in I420 format without copying.
     *
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     *
     * see "Example 1: Create images without copying"
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_yuv_i420_img_no_copy_ex(
        const bnb_image_format_t* format,
        bnb_yuv_color_range_t range,
        bnb_yuv_color_space_t space,
        uint8_t* y_plane,
        int32_t y_row_stride,
        int32_t y_pixel_stride,
        uint8_t* u_plane,
        int32_t u_row_stride,
        int32_t u_pixel_stride,
        uint8_t* v_plane,
        int32_t v_row_stride,
        int32_t v_pixel_stride,
        bnb_full_image_data_releaser_t releaser,
        void* releaser_user_data,
        bnb_error**
    );
    /**
     * Creates full_image from RGB(A) buffer.
     *
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_bpc8_img(
        bnb_image_format_t format,
        bnb_pixel_format_t pixel_format,
        uint8_t* data,
        int32_t stride,
        bnb_error**
    );
    /**
     * Creates full_image from RGB(A) buffer without copying.
     *
     * It is the user's responsibility to keep pointers to plains valid until the frame is processed.
     *
     * see "Example 1: Create images without copying"
     */
    BNB_EXPORT full_image_holder_t* bnb_full_image_from_bpc8_img_no_copy(
        bnb_image_format_t format,
        bnb_pixel_format_t pixel_format,
        uint8_t* data,
        int32_t stride,
        bnb_full_image_data_releaser_t releaser,
        void* releaser_user_data,
        bnb_error**
    );

    /**
     * Get image metadata (width, height, rotation, etc.)
     */
    BNB_EXPORT void bnb_full_image_get_format(full_image_holder_t* image, bnb_image_format_t* format, bnb_error**);

    /**
     * Destroys full_image object.
     *
     * Should be called after bnb_effect_player_push_frame().
     */
    BNB_EXPORT void bnb_full_image_release(full_image_holder_t* img, bnb_error**);

    /*
     * effect_player
     */

    /**
     * Helper function to load GL functions.
     *
     * Should be called on desktop platforms. Has no effect on mobile platforms.
     */
    BNB_EXPORT void bnb_effect_player_load_gl_functions(bnb_error**);

    /**
     * Set render backend type
     *
     * Not all values are supported on all platforms.
     * By default, each platform is set to the best value for that platform.
     *
     * @see bnb_render_backend_t
     * @note MUST be called before bnb_effect_player_create() function
     */
    BNB_EXPORT void bnb_effect_player_set_render_backend(bnb_render_backend_t render_backend, bnb_error**);

    /**
     * Creates effect_player object.
     */
    BNB_EXPORT effect_player_holder_t* bnb_effect_player_create(bnb_effect_player_configuration_t* cfg, bnb_error**);
    /**
     * Destroys effect_player object.
     */
    BNB_EXPORT void bnb_effect_player_destroy(effect_player_holder_t* ep, bnb_error**);

    /**
     * Changes render consistency mode.
     *
     * @see bnb_consistency_mode_t
     */
    BNB_EXPORT void bnb_effect_player_set_render_consistency_mode(effect_player_holder_t* ep, bnb_consistency_mode_t mode, bnb_error**);

    /**
     * Use to notify the EffectPlayer that the surface exists and the effect can be played.
     *
     * @note MUST be called from the render thread
     */
    BNB_EXPORT void bnb_effect_player_surface_created(effect_player_holder_t* ep, int32_t width, int32_t height, bnb_error**);
    /**
     * Notify about rendering surface being resized.
     *
     * @note MUST be called from the render thread
     */
    BNB_EXPORT void bnb_effect_player_surface_changed(effect_player_holder_t* ep, int32_t width, int32_t height, bnb_error**);
    /**
     * Notify about rendering surface being destroyed.
     *
     * This method should be called right before an active context will become invalid.
     * Switches playback state to inactive state. If it's not done an application will be
     * crashed on next draw iteration. After losing the surface effect playback can't be
     * resumed from the last position.
     *
     * @note MUST be called from the render thread
     */
    BNB_EXPORT void bnb_effect_player_surface_destroyed(effect_player_holder_t* ep, bnb_error**);

    /**
     * Provides image to process and to play effect.
     */
    BNB_EXPORT void bnb_effect_player_push_frame(effect_player_holder_t* ep, full_image_holder_t* img, bnb_error**);

    /**
     * Draw the current effect into the current frame buffer.
     *
     * Uses internal `frame_data` object obtained from latest `push_frame` recognition result.
     *
     * @return current frame number if the drawing was performed and the caller should swap buffers
     * otherwise returns `DRAW_SKIPPED`(-1)
     * @note MUST be called from the render thread
     */
    BNB_EXPORT int64_t bnb_effect_player_draw(effect_player_holder_t* ep, bnb_error**);

    /**
     * Draw the current effect into the current framebuffer. Uses externally provided `frame_data`
     * object instead of internal one obtained from latest `push_frame` recognition result.
     *
     * @return frame number from provided `frame_data` if drawing was performed and caller should swap buffers
     * otherwise `DRAW_SKIPPED`(-1)
     * @note MUST be called from the render thread
     */
    BNB_EXPORT int64_t bnb_effect_player_draw_with_external_frame_data(effect_player_holder_t* ep, frame_data_holder_t* fd, bnb_error**);

    /**
     * Start the playback.
     */
    BNB_EXPORT void bnb_effect_player_playback_play(effect_player_holder_t* ep, bnb_error**);
    /**
     * Pause the playback.
     */
    BNB_EXPORT void bnb_effect_player_playback_pause(effect_player_holder_t* ep, bnb_error**);
    /**
     * Stop the playback.
     */
    BNB_EXPORT void bnb_effect_player_playback_stop(effect_player_holder_t* ep, bnb_error**);

    /**
     * DEPRECATED. Does nothing, left for backwards compatibility.
     */
    BNB_EXPORT void bnb_effect_player_enable_audio(effect_player_holder_t* ep, bool enable, bnb_error**);
    /**
     * Set effect audio volume.
     *
     * @param volume A value in range `[0, 1]`, where `1` means maximum volume.
     */
    BNB_EXPORT void bnb_effect_player_set_effect_volume(effect_player_holder_t* ep, float volume, bnb_error**);

    /**
     * Add a callback to receive the current effect activation notification from Effect Player.
     */
    BNB_EXPORT listener_id_t bnb_effect_player_add_effect_activation_completion_listener(effect_player_holder_t* ep, bnb_effect_activation_completion_listener_t l, void*, bnb_error**);
    /**
     * Remove a callback to receive the current effect activation notification from Effect Player.
     */
    BNB_EXPORT void bnb_effect_player_remove_effect_activation_completion_listener(effect_player_holder_t* ep, listener_id_t l, bnb_error**);

    /**
     * Get effect manager object.
     *
     * The returned object should NOT be freed somehow. It is will be freed with effect_player.
     */
    BNB_EXPORT effect_manager_holder_t* bnb_effect_player_get_effect_manager(effect_player_holder_t* ep, bnb_error**);

    /**
     * Set frame processor as current
     * Thread-safe. May be called from any thread
     */
    BNB_EXPORT void bnb_effect_player_set_frame_processor(effect_player_holder_t* ep, frame_processor_t* processor, bnb_error**);

    /**
     * Get current frame processor
     * Thread-safe. May be called from any thread
     * You are respolsible to `destroy` returned `frame_processor_t`.
     */
    BNB_EXPORT frame_processor_t* bnb_effect_player_frame_processor(effect_player_holder_t* ep, bnb_error**);

    /**
     * effect_manager
     */
    /**
     * Load and activate the effect.
     *
     * @note MUST be called from the render thread
     */
    BNB_EXPORT effect_holder_t* bnb_effect_manager_load_effect(effect_manager_holder_t* em, const char* effect_path, bnb_error**);
    /**
     * Load effect async, activate in the draw() call when it is ready.
     *
     * @note May be called from any thread
     */
    BNB_EXPORT effect_holder_t* bnb_effect_manager_load_effect_async(effect_manager_holder_t* em, const char* effect_path, bnb_error**);

    /**
     * Get active effect.
     *
     * The returned object should NOT be freed somehow. It is will be freed with effect_player.
     */
    BNB_EXPORT effect_holder_t* bnb_effect_manager_get_current_effect(effect_manager_holder_t* em, bnb_error**);

    /**
     * Changes effect player render size.
     *
     * @note MUST be called from the render thread
     */
    BNB_EXPORT void bnb_effect_manager_set_effect_size(effect_manager_holder_t* em, int width, int height, bnb_error**);

    /**
     * Set render surface.
     */
    BNB_EXPORT void bnb_effect_manager_set_render_surface(effect_manager_holder_t* em, bnb_surface_data_t* surface, bnb_error**);

    /**
     * Add a callback to receive error messages from Effect Player.
     */
    BNB_EXPORT listener_id_t bnb_effect_manager_add_error_listener(effect_manager_holder_t* em, bnb_error_listener_t l, void* user_data, bnb_error**);
    /**
     * Remove a callback to receive error messages from Effect Player.
     */
    BNB_EXPORT void bnb_effect_manager_remove_error_listener(effect_manager_holder_t* em, listener_id_t l, bnb_error**);

    /*
     * effect
     */
    /**
     * Get effect URL.
     *
     * The returned string must be freed using bnb_memory_free() .
     */
    BNB_EXPORT char* bnb_effect_get_url(effect_holder_t* effect, bnb_error**);
    /**
     * Adds js method call to call queue. The queue is performed during the *draw* operation.
     *
     * If there is an effect in the loading state, all calls will be performed
     * when the effect loading is finished.
     *
     * @param method JS global function name. Member functions are not supported.
     * @param params Function arguments as JSON string.
     */
    BNB_EXPORT void bnb_effect_call_js_method(effect_holder_t* effect, const char* method, const char* param, bnb_error**);

    /**
     * Evaluate the `script` in effect.
     *
     * @param script JS string to execute
     * @param result_callback Callback for result
     *
     */
    BNB_EXPORT void bnb_effect_eval_js(effect_holder_t* effect, const char* script, const bnb_effect_eval_js_callback_t result_callback, bnb_error**);
    /**
     * Reset effect state.
     *
     * @note MUST be called from the render thread
     */
    BNB_EXPORT void bnb_effect_reset(effect_holder_t* effect, bnb_error**);

    /*
     * frame_processor
     */
    /**
     * Realtime feed processor. See `bnb_realtime_processor_mode` for more info.
     */
    BNB_EXPORT frame_processor_t* bnb_frame_processor_create_realtime_processor(
        bnb_realtime_processor_mode_t mode, processor_configuration_t* configuration, bnb_error**
    );

    /**
     * Photo processor. `push` to set photo, processing happens on `pop` synchronously.
     * Several `pop` calls return same result without unnecessary processing,
     * except case when recognizer pipeline was changed by EffectPlayer, processing will be restarted.
     */
    BNB_EXPORT frame_processor_t* bnb_frame_processor_create_photo_processor(processor_configuration_t* config, bnb_error**);

    /** Video processor.Consistent `push` - `pop` will process frames synchronously. */
    BNB_EXPORT frame_processor_t* bnb_frame_processor_create_video_processor(processor_configuration_t* config, bnb_error**);

    BNB_EXPORT void bnb_frame_processor_destroy(frame_processor_t*, bnb_error**);

    BNB_EXPORT bnb_processor_type_t bnb_frame_processor_get_type(frame_processor_t*, bnb_error**);

    BNB_EXPORT void bnb_frame_processor_push(frame_processor_t*, frame_data_holder_t* fd, bnb_error**);

    /** You are responsible to `destroy` `frame_data` in returned  `bnb_processor_result_t` */
    BNB_EXPORT bnb_processor_result_t bnb_frame_processor_pop(frame_processor_t*, bnb_error**);

    /*
     * processor_configuration
     */
    /** Create with default params */
    BNB_EXPORT processor_configuration_t* bnb_processor_configuration_create(bnb_error**);

    BNB_EXPORT void bnb_processor_configuration_destroy(processor_configuration_t*, bnb_error**);

    /**
     * Use future frame to filter prediction, improves anti-jitter, adds processed frame inconsistency
     * Example: push frame 1 - pop frame 1, push frame 2 - pop frame 1, push frame 3 - pop frame 2, ...
     * Cannot be used together with other configurations
     * Default: true
     */
    BNB_EXPORT void bnb_processor_configuration_set_use_future_filter(processor_configuration_t*, bool value, bnb_error**);

    /**
     * Use future frame to interpolate prediction, improves performance, adds processed frame inconsistency
     * Example: push frame 1 - pop frame 1, push frame 2 - pop frame 1, push frame 3 - pop frame 2, ...
     * Cannot be used together with other configurations
     * Default: false
     */
    BNB_EXPORT void bnb_processor_configuration_set_use_future_interpolate(processor_configuration_t*, bool value, bnb_error**);

    /**
     * Use offline NN's for processing, improces accuracy in exchange to performance
     * Cannot be used together with other configurations
     * Default: false
     */
    BNB_EXPORT void bnb_processor_configuration_set_use_offline_mode(processor_configuration_t*, bool value, bnb_error**);

    /*
     * full_image
     * One method here because bnb_full_iamge is decalred in this file
     */

    BNB_EXPORT void bnb_frame_data_add_full_img(frame_data_holder_t* frame_data, full_image_holder_t* image, bnb_error**);

    /**
     * utility functions
     */
    /**
     * Function to release anything allocated by C API
     *
     * This function must be used to free string (and other objects without a specific free() function) returned
     * by any API functions.
     *
     * This is crucial on Windows.
     *
     * @param ptr pointer to free
     */
    BNB_EXPORT void bnb_memory_free(void* ptr);

#ifdef __cplusplus
} // extern "C"
#endif
