/*
 * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#pragma once

#include <stdint.h>
#include <sys/queue.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#include "driver/ppa.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_private/dma2d.h"
#include "hal/dma2d_types.h"
#include "hal/ppa_types.h"
#include "hal/ppa_hal.h"
#include "esp_pm.h"

#ifdef __cplusplus
extern "C" {
#endif

#define PPA_MEM_ALLOC_CAPS       (MALLOC_CAP_DEFAULT)

#define PPA_PM_LOCK_NAME_LEN_MAX 16

#define PPA_CHECK_CM_SUPPORT_BYTE_SWAP(str, color_type_id) \
            ESP_RETURN_ON_FALSE(color_type_id == COLOR_TYPE_ID(COLOR_SPACE_ARGB, COLOR_PIXEL_ARGB8888) || color_type_id == COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB565), \
                                ESP_ERR_INVALID_ARG, TAG, str "_cm does not support byte_swap");

#define PPA_CHECK_CM_SUPPORT_RGB_SWAP(str, color_type_id) \
            ESP_RETURN_ON_FALSE(COLOR_SPACE_TYPE(color_type_id) == COLOR_SPACE_ARGB || COLOR_SPACE_TYPE(color_type_id) == COLOR_SPACE_RGB, \
                                ESP_ERR_INVALID_ARG, TAG, str "_cm does not support rgb_swap");

#define PPA_IS_CM_YUV422(color_type_id) \
            (color_type_id == COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_UYVY422) || color_type_id == COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_VYUY422) || \
             color_type_id == COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUYV422) || color_type_id == COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YVYU422))

#define PPA_ALIGN_UP(num, align)    (((num) + ((align) - 1)) & ~((align) - 1))
#define PPA_ALIGN_DOWN(num, align)  ((num) & ~((align) - 1))

typedef struct ppa_platform_t ppa_platform_t;

/******************************** ENGINE *************************************/
// PPA module contains SRM engine and Blending engine

typedef struct ppa_engine_t ppa_engine_t;

struct ppa_engine_t {
    ppa_platform_t *platform;                     // PPA driver platform
    ppa_engine_type_t type;                       // Type of the PPA engine
    portMUX_TYPE spinlock;                        // Engine level spinlock
    SemaphoreHandle_t sem;                        // Semaphore for whether the engine is processing a transaction
    STAILQ_HEAD(trans, ppa_trans_s) trans_stailq; // link head of pending transactions for the PPA engine
#if CONFIG_PM_ENABLE
    esp_pm_lock_handle_t pm_lock;                 // Power management lock
#endif
};

typedef struct ppa_srm_engine_t {
    ppa_engine_t base;                            // PPA engine base structure
    dma2d_descriptor_t *dma_tx_desc;              // Into PPA SRM engine direction 2D-DMA descriptor
    dma2d_descriptor_t *dma_rx_desc;              // Out from PPA SRM engine direction 2D-DMA descriptor
} ppa_srm_engine_t;

typedef struct ppa_blend_engine_t {
    ppa_engine_t base;                            // PPA engine base structure
    dma2d_descriptor_t *dma_tx_bg_desc;           // Into PPA Blending engine direction background channel 2D-DMA descriptor
    dma2d_descriptor_t *dma_tx_fg_desc;           // Into PPA Blending engine direction foreground channel 2D-DMA descriptor
    dma2d_descriptor_t *dma_rx_desc;              // Out from PPA blending engine direction 2D-DMA descriptor
} ppa_blend_engine_t;

typedef struct {
    ppa_engine_type_t engine;                     // Engine type
} ppa_engine_config_t;

/******************************** CLIENT *************************************/

typedef struct ppa_client_t ppa_client_t;

struct ppa_client_t {
    ppa_operation_t oper_type;                    // The PPA operation type that the client wants to do in speciality
    ppa_engine_t *engine;                         // Pointer to the PPA engine that in charge of performing the PPA operation
    uint32_t trans_cnt;                           // Number of pending PPA transactions
    portMUX_TYPE spinlock;                        // Client level spinlock
    ppa_event_callback_t done_cb;                 // Transaction done callback
    QueueHandle_t trans_elm_ptr_queue;            // Queue that contains the pointers to the allocated memory to save the transaction contexts
    ppa_data_burst_length_t data_burst_length;    // The desired data burst length for all the transactions of the client
};

/****************************** OPERATION ************************************/

// The elements in this structure listed first are identical to the elements in structure `ppa_srm_oper_config_t`
// With adding a few extra elements at the end
// This allows memcpy
typedef struct {
    ppa_in_pic_blk_config_t in;
    ppa_out_pic_blk_config_t out;

    // scale-rotate-mirror manipulation
    ppa_srm_rotation_angle_t rotation_angle;
    float scale_x;
    float scale_y;
    bool mirror_x;
    bool mirror_y;

    // input data manipulation
    bool rgb_swap;
    bool byte_swap;
    ppa_alpha_update_mode_t alpha_update_mode;
    union {
        uint32_t alpha_fix_val;
        float alpha_scale_ratio;
    };

    ppa_trans_mode_t mode;
    void *user_data;

    uint32_t scale_x_int;                         // Calculation result for the integral part of the scale_x to be directly written to register
    uint32_t scale_x_frag;                        // Calculation result for the fractional part of the scale_x to be directly written to register
    uint32_t scale_y_int;                         // Calculation result for the integral part of the scale_y to be directly written to register
    uint32_t scale_y_frag;                        // Calculation result for the fractional part of the scale_y to be directly written to register
    uint32_t alpha_value;                         // Calculation result for the fix alpha value to be directly written to register
    ppa_data_burst_length_t data_burst_length;    // Data burst length for the transaction, information passed from the client
} ppa_srm_oper_t;

// The elements in this structure listed first are identical to the elements in structure `ppa_blend_oper_config_t`
// With adding a few extra elements at the end
// This allows memcpy
typedef struct {
    ppa_in_pic_blk_config_t in_bg;
    ppa_in_pic_blk_config_t in_fg;
    ppa_out_pic_blk_config_t out;

    // input data manipulation
    bool bg_rgb_swap;
    bool bg_byte_swap;
    ppa_alpha_update_mode_t bg_alpha_update_mode;
    union {
        uint32_t bg_alpha_fix_val;
        float bg_alpha_scale_ratio;
    };
    bool fg_rgb_swap;
    bool fg_byte_swap;
    ppa_alpha_update_mode_t fg_alpha_update_mode;
    union {
        uint32_t fg_alpha_fix_val;
        float fg_alpha_scale_ratio;
    };
    color_pixel_rgb888_data_t fg_fix_rgb_val;

    // color-keying
    bool bg_ck_en;
    color_pixel_rgb888_data_t bg_ck_rgb_low_thres;
    color_pixel_rgb888_data_t bg_ck_rgb_high_thres;
    bool fg_ck_en;
    color_pixel_rgb888_data_t fg_ck_rgb_low_thres;
    color_pixel_rgb888_data_t fg_ck_rgb_high_thres;
    color_pixel_rgb888_data_t ck_rgb_default_val;
    bool ck_reverse_bg2fg;

    ppa_trans_mode_t mode;
    void *user_data;

    uint32_t bg_alpha_value;                      // Calculation result for the fix alpha value for BG to be directly written to register
    uint32_t fg_alpha_value;                      // Calculation result for the fix alpha value for FG to be directly written to register
    ppa_data_burst_length_t data_burst_length;    // Data burst length for the transaction, information passed from the client
} ppa_blend_oper_t;

// The elements in this structure listed first are identical to the elements in structure `ppa_fill_oper_config_t`
// With adding a few extra elements at the end
// This allows memcpy
typedef struct {
    ppa_out_pic_blk_config_t out;

    uint32_t fill_block_w;
    uint32_t fill_block_h;
    union {
        color_pixel_argb8888_data_t fill_argb_color;
        color_pixel_gray8_data_t fill_gray8_color;
        color_macroblock_yuv_data_t fill_yuv_color;
        uint32_t fill_color_val;
    };

    ppa_trans_mode_t mode;
    void *user_data;

    ppa_data_burst_length_t data_burst_length;    // Data burst length for the transaction, information passed from the client
} ppa_fill_oper_t;

/***************************** TRANSACTION ***********************************/

// PPA transaction element
typedef struct ppa_trans_s {
    STAILQ_ENTRY(ppa_trans_s) entry;              // Link entry
    dma2d_trans_config_t *trans_desc;             // Pointer to the structure containing the configurations for a 2D-DMA transaction
    dma2d_trans_t *dma_trans_placeholder;         // Pointer to the memory to store the 2D-DMA transaction context
    SemaphoreHandle_t sem;                        // Semaphore to block when the transaction has not finished
    ppa_client_t *client;                         // Pointer to the client who requested the transaction
    void *user_data;                              // User registered event data (per transaction)
} ppa_trans_t;

typedef struct {
    union {
        ppa_srm_oper_t *srm_desc;                 // Pointer to the structure containing the configurations for a PPA SRM operation transaction
        ppa_blend_oper_t *blend_desc;             // Pointer to the structure containing the configurations for a PPA blend operation transaction
        ppa_fill_oper_t *fill_desc;               // Pointer to the structure containing the configurations for a PPA fill operation transaction
        void *op_desc;                            // General pointer to the structure containing the configurations for a PPA transaction
    };
    ppa_engine_t *ppa_engine;                     // Pointer to the PPA engine
    ppa_trans_t *trans_elm;                       // Pointer to the PPA transaction element
    dma2d_trigger_peripheral_t trigger_periph;    // The 2D-DMA trigger peripheral
} ppa_dma2d_trans_on_picked_config_t;

bool ppa_srm_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_config);
bool ppa_blend_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_config);
bool ppa_fill_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_config);

esp_err_t ppa_do_operation(ppa_client_handle_t ppa_client, ppa_engine_t *ppa_engine_base, ppa_trans_t *trans_elm, ppa_trans_mode_t mode);

bool ppa_transaction_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data);

bool ppa_recycle_transaction(ppa_client_handle_t ppa_client, ppa_trans_t *trans_elm);

/****************************** PPA DRIVER ***********************************/

struct ppa_platform_t {
    _lock_t mutex;                              // Platform level mutex lock to protect the ppa_engine_acquire/ppa_engine_release process
    portMUX_TYPE spinlock;                      // Platform level spinlock
    ppa_hal_context_t hal;                      // PPA HAL context
    dma2d_pool_handle_t dma2d_pool_handle;      // Pointer to the acquired 2D-DMA pool
    ppa_srm_engine_t *srm;                      // Pointer to the PPA SRM engine
    ppa_blend_engine_t *blending;               // Pointer to the PPA blending engine
    uint32_t srm_engine_ref_count;              // Reference count used to protect PPA SRM engine acquire and release
    uint32_t blend_engine_ref_count;            // Reference count used to protect PPA blending engine acquire and release
    size_t buf_alignment_size;                  // Alignment requirement for the outgoing buffer addr and size to satisfy cache line size
    uint32_t dma_desc_mem_size;                 // Alignment requirement for the 2D-DMA descriptor to satisfy cache line size
};

#ifdef __cplusplus
}
#endif
