/**
 * @file hv_drv_OsdGraphic.c
 * @brief Osd Driver Graphic layer c file.
 * @details This file provides the following functions: \n
 *          (1) Graphic initialization functions \n
 *          (2) Graphic Draw functions \n
 *
 * @author HiView SoC Software Team
 * @version 1.0.0
 * @date 2022-08-31
 * @copyright Copyright(c),2022-8, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author         <th>Date        <th>Change Description
 * <tr><td>OSD Group      <td>2022-08-31   <td>create
 * </table>
*/

#include "hv_comm_DataType.h"
#include "hv_drv_Osd.h"
#include "hv_cal_Osd.h"
#include "hv_cal_Dma.h"
#include "hv_drv_Dma.h"

#ifdef CONFIG_USER_TJPG_ON
#include "tjpgd.h"
#endif

static OsdGraphicParam *s_pstGraphicParam = NULL;

#if (HV_PROJECT_CONFIG_OSD_GRAPHIC == HV_CONFIG_ON)

BOOL Hv_Drv_OsdGraphic_GetEnable(VOID)
{
    return Hv_Cal_OsdGraphic_GetEnable();
}

VOID Hv_Drv_OsdGraphic_SetEnable(BOOL bEnable)
{
    Hv_Cal_OsdGraphic_SetEnable(bEnable);
}

static Status Hv_Drv_OsdGraphic_CopyGraphicCb(VOID *arg)
{
    s_pstGraphicParam->bGraphicCopyDone = HV_TRUE;

    return HV_SUCCESS;
}

#ifdef CONFIG_USER_TJPG_ON
UINT32 Hv_Drv_OsdGraphic_DecompInput(register JDEC *pstJd, register UCHAR8 *pucSeg, register UINT32 uiSize)
{
    register const UCHAR8 *pucMatrix = pstJd->device;

    if (NULL != pucSeg)
    {
        //HV_LOGI("Input %d bytes from 0x%x to 0x%x", uiSize, pucMatrix, pucSeg);
        HV_MEMCPY((VOID*)pucSeg, (VOID*)pucMatrix, uiSize);
    }
    else
    {
        //HV_LOGI("Skip %d bytes from 0x%x", uiSize, pucMatrix);
    }

    pucMatrix += uiSize;
    pstJd->device = pucMatrix;

    return uiSize;
}

UINT32 Hv_Drv_OsdGraphic_DecompOutput(register JDEC *pstJd, register UCHAR8 *pucData, register JRECT *pstJRect)
{
    register UCHAR8 *pucDst = NULL;
    register UCHAR8 *pucSrc = NULL;
    register USHORT16 usY = 0;
    register UCHAR8 ucPixelBytes = 0;
    register USHORT16 usLineBytes = 0;
    register USHORT16 usRectLineBytes = 0;

    if (pstJd->format == JDF_BGR565)
    {
        ucPixelBytes = 2;
    }
    else
    {
        ucPixelBytes = 3;
    }

    //HV_LOGI("output RECT(%d, %d, %d, %d)", pstJRect->left, pstJRect->right, pstJRect->top, pstJRect->bottom);

    usRectLineBytes = (pstJRect->right - pstJRect->left + 1) * ucPixelBytes;
    usLineBytes = pstJd->width * ucPixelBytes;
    pucSrc = pucData;
    pucDst = (UCHAR8*)HV_MEMORY_CONFIG_OSD_GRAPHIC_DDR_START + ucPixelBytes * (pstJRect->top * pstJd->width + pstJRect->left);

    //pucDst = (UCHAR8*)HV_MEMORY_CONFIG_OSD_DDR_START + ucPixelBytes * (pstJRect->top * pstJd->width + pstJRect->left);
    for (usY = pstJRect->top; usY <= pstJRect->bottom; ++usY)
    {
        /* copy one line */
        HV_MEMCPY(pucDst, pucSrc, usRectLineBytes);

        pucSrc += usRectLineBytes;
        pucDst += usLineBytes;
    }

    return 1; /* continue decompress */
}

static Status Hv_Drv_OsdGraphic_Decompress(const OsdGraphic *pstGraphic, const OsdGraphicAddition *pstGraphicAddition)
{
    JDEC stJD;
    JDEC *pstJD = &stJD;
    UCHAR8 *pucPool = (UCHAR8*)OSD_TJPG_POOL_START;
    const UCHAR8 *pucMatrix = NULL;
    JFORMAT enFormat = 0;
    JRESULT enRes;

    if (pstGraphicAddition->bIsAddition)
    {
        pucMatrix = (UCHAR8 *)pstGraphicAddition->uiAdditionAddr;
        for (int i = 0; i < 16; i++)
        {
            OSD_DRV_LOG_DEBUG("*****pucMatrix: 0x%02x", *(pucMatrix+i));
        }
    }
    else
    {
        pucMatrix = g_aucOsdGraphicMatrix + g_auiOsdGraphicMatrixOffset[pstGraphic->ucGraphicID];
    }

    if (OSD_PIXEL_FORMAT_RGB565 == pstGraphic->ucFormat)
    {
        enFormat = JDF_BGR565;
    }
    else if (OSD_PIXEL_FORMAT_RGB888 == pstGraphic->ucFormat)
    {
        enFormat = JDF_BGR888;
    }
    else
    {
        OSD_DRV_LOG_ERROR("Decompress do not support format %d", pstGraphic->ucFormat);
        return HV_FAILURE;
    }

    //stJD.swap = 0;
    enRes = jd_prepare(pstJD,
            (size_t (*)(JDEC*,uint8_t*,size_t))Hv_Drv_OsdGraphic_DecompInput,
            pucPool, OSD_TJPG_POOL_SIZE, enFormat, pucMatrix);
    if (enRes != JDR_OK)
    {
        OSD_DRV_LOG_ERROR("Fail to decompress graphic. jd_prepare return %d", enRes);
        return HV_FAILURE;
    }

    OSD_DRV_LOG_DEBUG("decompress graphic: width %d height %d", pstJD->width, pstJD->height);

    enRes = jd_decomp(pstJD,
            (int (*)(JDEC*,void*,JRECT*))Hv_Drv_OsdGraphic_DecompOutput, 0);
    if (enRes != JDR_OK)
    {
        //OSD_DRV_LOG_ERROR("Fail to decompress graphic. jd_decomp return %d", enRes);
        HV_LOGI("Fail to decompress graphic. jd_decomp return %d", enRes);
        return HV_FAILURE;
    }

    Hv_Vos_InvalidAllDCache();

    OSD_DRV_LOG_DEBUG("decompress graphic ok.");
    return HV_SUCCESS;
}

#else
static Status Hv_Drv_OsdGraphic_Decompress(const OsdGraphic *pstGraphic, const OsdGraphicAddition *pstGraphicAddition)
{
    OSD_DRV_LOG_ERROR("TJpg decompress not supported, turn on CONFIG_USER_TJPG_SUPPORT@property.config, and try again");
    return HV_FAILURE;
}
#endif

VOID Hv_Drv_OsdGraphic_DrawGraphic(const OsdGraphic *pstGraphic, const OsdGraphicAddition *pstGraphicAddition)
{
    HV_ASSERT_VALID_PTR_VOID(pstGraphic);
    HV_ASSERT_VALID_PTR_VOID(pstGraphicAddition);
    UCHAR8 ucTimeout = 0;
    OsdArea stArea;
    Status enStatus = 0;
    //UINT64 ullBeforeTick = 0;
    //UINT64 ullAfterTick = 0;

    if (pstGraphic->ucCompressed)
    {
        //ullBeforeTick = Hv_Vos_GetTick();
        enStatus = Hv_Drv_OsdGraphic_Decompress(pstGraphic, pstGraphicAddition);
        //ullAfterTick = Hv_Vos_GetTick();
        //HV_LOGI("decompression cost %llu ticks", ullAfterTick - ullBeforeTick);
        if (HV_FAILURE == enStatus)
        {
            return;
        }
    }
    else
    {
        Hv_Drv_Dma_Init(OSD_SYSDMA_PORT_NUMBER,
                Hv_Drv_OsdGraphic_CopyGraphicCb);

        s_pstGraphicParam->bGraphicCopyDone = HV_FALSE;
        if (pstGraphicAddition->bIsAddition)
        {
            Hv_Drv_Dma_MemMove(OSD_SYSDMA_PORT_NUMBER,
                                  pstGraphicAddition->uiAdditionAddr,
                                  HV_MEMORY_CONFIG_OSD_GRAPHIC_DDR_START,
                                  pstGraphicAddition->uiAdditionSize);
        }
        else
        {
            Hv_Drv_Dma_MemMove(OSD_SYSDMA_PORT_NUMBER,
                    (UINT32)(g_aucOsdGraphicMatrix + g_auiOsdGraphicMatrixOffset[pstGraphic->ucGraphicID]),
                    HV_MEMORY_CONFIG_OSD_GRAPHIC_DDR_START,
                    g_auiOsdGraphicMatrixSize[pstGraphic->ucGraphicID]);
        }
    }

    Hv_Cal_OsdGraphic_SetFormat(pstGraphic->ucFormat, pstGraphic->ucAlpha, pstGraphic->uiColor);
    Hv_Cal_OsdFrame_SetUniversalAlphaEnable(OSD_DATAPATH_GRAPHIC, pstGraphic->ucSetAlpha);

    if(pstGraphic->ucColorKeyEn)
    {
        Hv_Cal_OsdGraphic_SetColorKey(pstGraphic->uiColorKey);
        Hv_Cal_OsdGraphic_SetColorKeyEnable(HV_TRUE);
    }
    else
    {
        Hv_Cal_OsdGraphic_SetColorKeyEnable(HV_FALSE);
    }

    stArea.usX = pstGraphic->usX;
    stArea.usY = pstGraphic->usY;
    stArea.usWidth = pstGraphic->usWidth;
    stArea.usHeight = pstGraphic->usHeight;
    Hv_Cal_OsdGraphic_SetRange(&stArea);
    Hv_Cal_OsdGraphic_SetRDMA(0, OSD_GRAPHIC_BURSTLEN);
    Hv_Cal_OsdGraphic_SetDDRAddr(HV_MEMORY_CONFIG_OSD_GRAPHIC_DDR_START);
    //Hv_Cal_OsdGraphic_SetDDRAddr(HV_MEMORY_CONFIG_OSD_DDR_START);

    Hv_Cal_OsdGraphic_SetEnable(1);
}

Status Hv_Drv_OsdGraphic_WaitTransferDone(VOID)
{
    UCHAR8 ucTimeout = 0;

    while ((!s_pstGraphicParam->bGraphicCopyDone) && (++ucTimeout <= OSD_DMA_WAITDONE_MS))
    {
        Hv_Vos_Delayus(1000);
    }

    OSD_DRV_LOG_DEBUG("Wait Graphic transfer done for %ums", ucTimeout);

    return s_pstGraphicParam->bGraphicCopyDone ? HV_SUCCESS : HV_FAILURE;
}

VOID Hv_Drv_OsdGraphic_ClearCPUCanvas()
{
    UINT32 uiSize = s_pstGraphicParam->uiColumnStep * s_pstGraphicParam->usHeight;

    HV_MEMSET((VOID*)s_pstGraphicParam->uiCanvasDrawBuffer, 0, uiSize);
    return;
}

Status Hv_Drv_OsdGraphic_InitCPUCanvas(OsdGraphic *pstGraphic)
{
    HV_ASSERT_VALID_PTR_RET(pstGraphic, HV_FAILURE);

    UINT32 uiSize = 0;
    UINT32 uiSizeLimit = 0;
    UCHAR8 ucBits = 0;
    UINT32 uiRowBits = 0;
    UINT32 uiRowBytes = 0;
    UCHAR8 ucAlignedBytes = 16;
    OsdArea stArea = {0};

    if (OSD_PIXEL_FORMAT_RGB888 == pstGraphic->ucFormat)
    {
        ucAlignedBytes = 48;
    }

    ucBits = Hv_Drv_OsdCommon_GetPixelBits(pstGraphic->ucFormat);

    uiRowBits = pstGraphic->usWidth * ucBits;
    uiRowBytes = roundup(uiRowBits / 8, ucAlignedBytes);

    uiSize = uiRowBytes * pstGraphic->usHeight;
    uiSizeLimit = Hv_Board_Memory_CalculateOsdGraphicSize();

    if (uiSize * 2 >= uiSizeLimit)
    {
        OSD_DRV_LOG_ERROR("Osd graphic CPU Canvas size %d exceeds limit %d", uiSize * 2, uiSizeLimit);
        return HV_FAILURE;
    }

    s_pstGraphicParam->usWidth = pstGraphic->usWidth;
    s_pstGraphicParam->usHeight = pstGraphic->usHeight;
    s_pstGraphicParam->enPixelFormat = pstGraphic->ucFormat;
    s_pstGraphicParam->uiColumnStep = uiRowBytes;
    s_pstGraphicParam->uiCanvasDrawBuffer = HV_MEMORY_CONFIG_OSD_GRAPHIC_DDR_START;
    s_pstGraphicParam->uiCanvasDisplayBuffer = s_pstGraphicParam->uiCanvasDrawBuffer + uiSize;

    Hv_Cal_OsdGraphic_SetFormat(pstGraphic->ucFormat, pstGraphic->ucAlpha, pstGraphic->uiColor);
    Hv_Cal_OsdFrame_SetUniversalAlphaEnable(OSD_DATAPATH_GRAPHIC, pstGraphic->ucSetAlpha);

    if(pstGraphic->ucColorKeyEn)
    {
        Hv_Cal_OsdGraphic_SetColorKey(pstGraphic->uiColorKey);
        Hv_Cal_OsdGraphic_SetColorKeyEnable(HV_TRUE);
    }
    else
    {
        Hv_Cal_OsdGraphic_SetColorKeyEnable(HV_FALSE);
    }

    stArea.usX = pstGraphic->usX;
    stArea.usY = pstGraphic->usY;
    stArea.usWidth = pstGraphic->usWidth;
    stArea.usHeight = pstGraphic->usHeight;
    Hv_Cal_OsdGraphic_SetRange(&stArea);
    Hv_Cal_OsdGraphic_SetRDMA(0, OSD_GRAPHIC_BURSTLEN);
    Hv_Cal_OsdGraphic_SetDDRAddr(s_pstGraphicParam->uiCanvasDrawBuffer);
    //Hv_Cal_OsdGraphic_SetDDRAddr(HV_MEMORY_CONFIG_OSD_DDR_START);

    Hv_Cal_OsdGraphic_SetEnable(1);

    return HV_SUCCESS;
}

/*
 * uiColor: R in MSB B in LSB
 * Graphic: R in LSB B in MSB
 */
Status Hv_Drv_OsdGraphic_DrawCPUCanvasPoint(USHORT16 usX, USHORT16 usY, UINT32 uiColor)
{
    UCHAR8 *pucBuffer = (UCHAR8*)s_pstGraphicParam->uiCanvasDrawBuffer;
    UINT32 uiOffset = 0;
    UCHAR8 ucBits = 0;
    UCHAR8 ucValue = 0;

    if ((usX >= s_pstGraphicParam->usWidth) || (usY >= s_pstGraphicParam->usHeight))
    {
        OSD_DRV_LOG_ERROR("Graphic CPU Canvas point(%d %d) exceeds canvas area(%d %d)", usX, usY, s_pstGraphicParam->usWidth, s_pstGraphicParam->usHeight);

        return HV_FAILURE;
    }

    ucBits = Hv_Drv_OsdCommon_GetPixelBits(s_pstGraphicParam->enPixelFormat);

    uiOffset = s_pstGraphicParam->uiColumnStep * usY * 8 + usX * ucBits;
    switch (s_pstGraphicParam->enPixelFormat)
    {
        case OSD_PIXEL_FORMAT_RGBA8888:
        {
            pucBuffer += (uiOffset >> 3);
            *(UINT32*)pucBuffer = uiColor;

            break;
        }
        case OSD_PIXEL_FORMAT_RGB888:
        {
            pucBuffer += (uiOffset >> 3);

            *pucBuffer++ = OSD_B(uiColor);
            *pucBuffer++ = OSD_G(uiColor);
            *pucBuffer++ = OSD_R(uiColor);

            break;
        }
        case OSD_PIXEL_FORMAT_RGB565:
        {
            pucBuffer += (uiOffset >> 3);

            *(USHORT16*)pucBuffer = ((OSD_R(uiColor) >> 3) << 11) | ((OSD_G(uiColor) >> 2) << 5) | ((OSD_B(uiColor) >> 3));
            break;
        }
        case OSD_PIXEL_FORMAT_Alpha8:
        {
            pucBuffer += (uiOffset >> 3);

            *pucBuffer = uiColor & 0xff;
            break;
        }
        case OSD_PIXEL_FORMAT_Alpha4:
        {
            pucBuffer += (uiOffset >> 3);
            ucValue = uiColor & 0x0f;

            if (uiOffset & 0x7)
            {
                /* high 4bits */
                *pucBuffer = (*pucBuffer & 0x0f) | (ucValue << 4);
            }
            else
            {
                /* low 4bits */
                *pucBuffer = (*pucBuffer & 0xf0) | ucValue;
            }
            break;
        }
        default:
        {
            break;
        }
    }

    return HV_SUCCESS;
}

VOID Hv_Drv_OsdGraphic_SwitchCPUCanvasBuffer(VOID)
{
    UINT32 uiTemp = 0;

    uiTemp = s_pstGraphicParam->uiCanvasDrawBuffer;
    s_pstGraphicParam->uiCanvasDrawBuffer = s_pstGraphicParam->uiCanvasDisplayBuffer;
    s_pstGraphicParam->uiCanvasDisplayBuffer = uiTemp;
    return;
}

VOID Hv_Drv_OsdGraphic_ShowCPUCanvas(VOID)
{
    Hv_Cal_OsdGraphic_SetDDRAddr(s_pstGraphicParam->uiCanvasDrawBuffer);

    Hv_Drv_OsdGraphic_SwitchCPUCanvasBuffer();
    return;
}
#endif

VOID Hv_Drv_OsdGraphic_Init(VOID)
{
    s_pstGraphicParam = (OsdGraphicParam*)OSD_DRIVER_ALLOC_STATIC(OsdGraphicParam, 1);
    HV_MEMSET((VOID*)s_pstGraphicParam, 0, sizeof(*s_pstGraphicParam));

    s_pstGraphicParam->bGraphicCopyDone = HV_TRUE;

    Hv_Cal_OsdGraphic_SetEnable(0);
}

#if (HV_PROJECT_CONFIG_OSD_GRAPHIC == HV_CONFIG_ON)
typedef struct _OsdPoint
{
    USHORT16 usY;
    struct LinkedList stEntry;
} OsdPoint;
VOID Hv_Drv_OsdGraphic_CPUCanvasStep()
{
    UCHAR8 ucIndex = 0;
    USHORT16 usTotalPoints = 400;
    struct LinkedList *pstNode = NULL;
    OsdPoint *pstPoint = NULL;
    SHORT16 sY = 0;
    USHORT16 usX = 0;
    SHORT16 sDelta = 0;
    static BOOL bIncrease = 1;
    static struct LinkedList stAllPoints = {&stAllPoints, &stAllPoints};
    static USHORT16 usPoints = 0;
    static SHORT16 sLastY = 36; /* the middle */
    static BOOL bInited = HV_FALSE;
    OsdGraphic stGraphic = {0};

    stGraphic.usX = 300;
    stGraphic.usY = 300;
    stGraphic.uiColor = 0xc8d408;
    stGraphic.ucAlpha = 255;
    stGraphic.usWidth = 400;
    stGraphic.usHeight = 72;
    stGraphic.ucFormat = OSD_PIXEL_FORMAT_Alpha4;

    if (!bInited)
    {
        Hv_Drv_OsdGraphic_InitCPUCanvas(&stGraphic);
        bInited = HV_TRUE;
    }

    /* remove one */
    if (usPoints >= usTotalPoints)
    {
        for (ucIndex = 0; ucIndex < 1; ++ucIndex)
        {
            pstNode = Hv_Comm_LinkedList_PopTail(&stAllPoints);
            pstPoint = Hv_Comm_LinkedList_Entry(pstNode, OsdPoint, stEntry);
            HV_FREE(pstPoint);
            pstPoint = NULL;

            --usPoints;
        }
    }

#define POS_RANDOM
#ifdef POS_RANDOM
    bIncrease = random() % 2;
    sDelta = random() % 5;
#else
    sDelta = 1;
#endif
    if (bIncrease)
    {
        sY = sLastY + sDelta;
        if (sY >= 72)
        {
            sY = sLastY - sDelta;
#ifndef POS_RANDOM
            bIncrease = 0;
#endif
        }
    }
    else
    {
        sY = sLastY - sDelta;
        if (sY < 0)
        {
            sY = sLastY + sDelta;
#ifndef POS_RANDOM
            bIncrease = 1;
#endif
        }
    }

    /* generate a new point */
    for (ucIndex = 0; ucIndex < 1; ++ucIndex)
    {
        pstPoint = HV_MALLOC(sizeof(*pstPoint));
        HV_MEMSET(pstPoint, 0, sizeof(*pstPoint));
        Hv_Comm_LinkedList_Init(&pstPoint->stEntry);
        pstPoint->usY = sY;

        Hv_Comm_LinkedList_AddHead(&stAllPoints, &pstPoint->stEntry);
        ++usPoints;
    }

    /* clear area */
    Hv_Drv_OsdGraphic_ClearCPUCanvas();

    Hv_Comm_LinkedList_ForEach(&stAllPoints, pstNode)
    {
        pstPoint = Hv_Comm_LinkedList_Entry(pstNode, OsdPoint, stEntry);

        /* left point */
        if (usX != 0)
        {
            Hv_Drv_OsdGraphic_DrawCPUCanvasPoint(usX - 1, pstPoint->usY, 0xc8d4f8);
        }
        /* top point */
        if (pstPoint->usY != 0)
        {
            Hv_Drv_OsdGraphic_DrawCPUCanvasPoint(usX, pstPoint->usY - 1, 0xc8d4f8);
        }
        /* current point */
        Hv_Drv_OsdGraphic_DrawCPUCanvasPoint(usX, pstPoint->usY, 0xc8d4f8);
        /* bottom point */
        if (pstPoint->usY != 71)
        {
            Hv_Drv_OsdGraphic_DrawCPUCanvasPoint(usX, pstPoint->usY + 1, 0xc8d4f8);
        }
        /* right point */
        if (usX != usTotalPoints - 1)
        {
            Hv_Drv_OsdGraphic_DrawCPUCanvasPoint(usX + 1, pstPoint->usY, 0xc8d4f8);
        }

        usX += 1;
    }

    Hv_Drv_OsdGraphic_ShowCPUCanvas();

    sLastY = sY;

    Hv_Drv_OsdFrame_DrawPost();
    Hv_Drv_OsdFrame_SetSync();
}
#endif
