// SPDX-FileCopyrightText: © 2025 Allegro DVT <github-ip@allegrodvt.com>
// SPDX-License-Identifier: MIT

#include "SinkYuvCrc.hpp"
#include "lib_app/utils.hpp"

extern "C"
{
#include "lib_common/PixMapBuffer.h"
#include "lib_common/BufCommon.h"
#include "lib_common/DisplayInfoMeta.h"
}

#include <iomanip>

using namespace std;

#define POLYNOM_CRC 0x04c11db7
static uint32_t crc32_table[1024];
static bool bInitCRC;

/******************************************************************************/
static void init_crc32(int32_t bitdepth)
{
  for(int32_t i = 0; i < (1 << bitdepth); i++)
  {
    uint32_t crc_precalc = i << (32 - bitdepth);

    for(int32_t j = 0; j < bitdepth; j++)
      crc_precalc = (crc_precalc & 0x80000000) ? (crc_precalc << 1) ^ POLYNOM_CRC : (crc_precalc << 1);

    crc32_table[i] = crc_precalc;
  }
}

/******************************************************************************/
template<typename T>
void CRC32(int32_t iBdIn, int32_t iBdOut, uint32_t& crc, T* pBuffer)
{
  if(!bInitCRC)
  {
    init_crc32(iBdIn);
    bInitCRC = true;
  }

  int32_t iPix;

  if(iBdIn < iBdOut)
    iPix = *pBuffer << (iBdOut - iBdIn);
  else
    iPix = *pBuffer >> (iBdIn - iBdOut);

  int32_t mask = (1 << iBdOut) - 1;
  crc = (crc << iBdOut) ^ crc32_table[((crc >> (32 - iBdOut)) ^ iPix) & mask];
}

template<typename T>
uint32_t ComputePlaneCRC(AL_TBuffer* pYUV, AL_EPlaneId ePlane, AL_TDimension tDim, int32_t iBdIn, int32_t iBdOut)
{
  uint32_t crc = UINT32_MAX;

  T* pPlane = (T*)AL_PixMapBuffer_GetPlaneAddress(pYUV, ePlane);
  int32_t iPitch = AL_PixMapBuffer_GetPlanePitch(pYUV, ePlane) / sizeof(T);

  for(int32_t i = 0; i < tDim.iHeight; i++)
  {
    for(int32_t j = 0; j < tDim.iWidth; j++)
      CRC32(iBdOut, iBdIn, crc, pPlane++);

    pPlane += iPitch - tDim.iWidth;
  }

  return crc;
}

class CSCrcCalculator : public IFrameSink
{
public:
  CSCrcCalculator(const std::string& path)
  {
    if(!path.empty())
    {
      OpenOutput(ofCrcFile, path, false);
      ofCrcFile << std::hex << std::uppercase;
    }
  }

  void ProcessFrame(AL_TBuffer* pYUV) override
  {
    uint32_t crc_luma = UINT32_MAX;
    uint32_t crc_cb = UINT32_MAX;
    uint32_t crc_cr = UINT32_MAX;
    AL_TDisplayInfoMetaData* pMeta = reinterpret_cast<AL_TDisplayInfoMetaData*>(AL_Buffer_GetMetaData(pYUV, AL_META_TYPE_DISPLAY_INFO));
    int32_t iBdInY = pMeta->uStreamBitDepthY;
    int32_t iBdInC = pMeta->uStreamBitDepthC;

    AL_TDimension tDim = AL_PixMapBuffer_GetDimension(pYUV);

    TFourCC tFourCC = AL_PixMapBuffer_GetFourCC(pYUV);
    AL_TPicFormat tPicFormat;
    AL_GetPicFormat(tFourCC, &tPicFormat);

    if(tPicFormat.uBitDepth == 8)
      crc_luma = ComputePlaneCRC<uint8_t>(pYUV, AL_PLANE_Y, tDim, iBdInY, tPicFormat.uBitDepth);
    else
      crc_luma = ComputePlaneCRC<uint16_t>(pYUV, AL_PLANE_Y, tDim, iBdInY, tPicFormat.uBitDepth);

    if(tPicFormat.eChromaMode != AL_CHROMA_MONO)
    {
      tDim.iWidth = AL_GetChromaWidth(tFourCC, tDim.iWidth);
      tDim.iHeight = AL_GetChromaHeight(tFourCC, tDim.iHeight);

      if(tPicFormat.uBitDepth == 8)
      {
        crc_cb = ComputePlaneCRC<uint8_t>(pYUV, AL_PLANE_U, tDim, iBdInC, tPicFormat.uBitDepth);
        crc_cr = ComputePlaneCRC<uint8_t>(pYUV, AL_PLANE_V, tDim, iBdInC, tPicFormat.uBitDepth);
      }
      else
      {
        crc_cb = ComputePlaneCRC<uint16_t>(pYUV, AL_PLANE_U, tDim, iBdInC, tPicFormat.uBitDepth);
        crc_cr = ComputePlaneCRC<uint16_t>(pYUV, AL_PLANE_V, tDim, iBdInC, tPicFormat.uBitDepth);
      }
    }

    ofCrcFile << setfill('0') << setw(8) << crc_luma << " : ";
    ofCrcFile << setfill('0') << setw(8) << crc_cb << " : ";
    ofCrcFile << setfill('0') << setw(8) << crc_cr << endl;
  }

private:
  std::ofstream ofCrcFile;
};

IFrameSink* createCSCrcCalculator(const std::string path)
{
  return new CSCrcCalculator(path);
}
