////----------------------------------------------------------------------------------
// File:        CVideoFile.cpp
// SDK Version: 1.0.2
//
// SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.
//
//----------------------------------------------------------------------------------

///////////////////////////////////////////
// CVideoFile.cpp
//
// Read uncompressed formats from file into a buffer
//

#include <windows.h>

#include "CDX1x_Common.h"
#include "CVideoFile.h"

// Constructor
//------------------------------------------------------------------------------
CVideoFile::CVideoFile(bool bUseTexturePitch)
: m_nFourCC(0)
, m_nWidth(0)
, m_nHeight(0)
, m_nPitch(0)
, m_nTexturePitch(0)
, m_bUseTexturePitch(bUseTexturePitch)
, m_nBPP(0)
, m_nNumFrames(0)
, m_nCurrentFrameIndex(0)
, m_File(0)
{
    *m_strFourCC    = '\0';
    *m_strClipName  = '\0';
}

// Destructor
//------------------------------------------------------------------------------
CVideoFile::~CVideoFile()
{
    Close();
}
 
// Open the specified video clip and associate the file with the file stream.
// In addition, parse the filename to grab the video clip properties.
//------------------------------------------------------------------------------
HRESULT CVideoFile::Open(
    char * strFilePath)
{
    // Open the file
    fopen_s(&m_File, strFilePath, "rb");
    if (!m_File) 
    {
        return E_FAIL;
    }

    // Gather information about the video file (metadata).
    // This information is encoded in the filename.
    char *position = strrchr(strFilePath, '\\');
    position ? position++ : position = strFilePath;
    char strParams[256];
    strcpy_s(strParams, position);
    for (char *c = strParams; *c; c++)
        if (*c == '_') *c = ' ';
    strncpy_s(m_strFourCC, strParams, 4);
    m_strFourCC[4] = '\0';

    sscanf_s(&strParams[5], "%d %d %d %d %s", &m_nWidth, &m_nHeight, &m_nPitch, &m_nNumFrames, m_strClipName, (UINT)sizeof(m_strClipName));
    m_nTexturePitch = ROUNDUP(m_nPitch, 256);

    // If it's an unrecognized FourCC, we will need to add it.
    m_nFourCC = 0;
    if (!strcmp(m_strFourCC, "NV12")) { m_nFourCC = FOURCC_NV12;      m_nBPP = 1; }
    if (!strcmp(m_strFourCC, "YUY2")) { m_nFourCC = FOURCC_YUY2;      m_nBPP = 2; }
    if (!strcmp(m_strFourCC, "ARGB")) { m_nFourCC = FOURCC_ARGB;      m_nBPP = 4; }
    m_lCurrentOffet = 0;

    return S_OK;
}

// Close the file.
//------------------------------------------------------------------------------
void CVideoFile::Close()
{
    if (m_File)
    {
        fclose(m_File);
        m_File = NULL;
    }
}

// Read from the video file and fill the destination buffer with the video
// frame data. Note that the desired FourCC may be different than that of the
// stored data. In that case, we must transform the data to the desired format.
// Realistically, we probably won't be using video files > 2GB.
//------------------------------------------------------------------------------
HRESULT CVideoFile::FillBuffer(
    char* const pDstBufStart,
    int nDstFourCC,
    int nDstPitch, 
    bool bDoubleSize)
{
    if (!pDstBufStart)
    {
        return E_POINTER;
    }

    // Pointer to the destination buffer
    char* pDstBuf = NULL;
    char TmpBuf[8192];

    // Read the data and store it to the D3D surface.
    // The pattern of reading is different based on the video format.
    switch (nDstFourCC)
    {
        case FOURCC_NV12:
            {
                // Frame size.
                const long lFrameSize       = m_nPitch * m_nHeight;

                // Offsets into the file buffer.
                const long nLumaOffset      = m_nCurrentFrameIndex * lFrameSize * 3 / 2;
                const long nChromaOffset    = nLumaOffset + lFrameSize;

                // LUMA --------------------------------------------------------
                pDstBuf = pDstBufStart;
                if (m_nPitch == nDstPitch)
                {
                    Read(pDstBuf, nLumaOffset, m_nPitch * m_nHeight);
                }
                else
                {
                    for (int y = 0; y < m_nHeight; y++)
                    {
                        if (bDoubleSize)
                        {
                            Read(TmpBuf, nLumaOffset + m_nPitch * y, m_nWidth);
                            for (int c = 0; c < m_nWidth; c++)
                            {
                                pDstBuf[c * 2] = TmpBuf[c];
                                pDstBuf[c * 2 + 1] = TmpBuf[c];
                                pDstBuf[nDstPitch + c * 2] = TmpBuf[c];
                                pDstBuf[nDstPitch + c * 2 + 1] = TmpBuf[c];
                            }
                            pDstBuf += (2 * nDstPitch);
                        }
                        else
                        {
                            Read(pDstBuf, nLumaOffset + m_nPitch * y, m_nWidth);
                            pDstBuf += nDstPitch;
                        }
                    }
                }

                // CHROMA ------------------------------------------------------
                pDstBuf = pDstBufStart + nDstPitch * m_nHeight;
                if (bDoubleSize)
                {
                    pDstBuf = pDstBufStart + nDstPitch * m_nHeight * 2;
                }
                if (m_nPitch == nDstPitch)
                {
                    Read(pDstBuf, nChromaOffset, m_nPitch * (m_nHeight / 2));
                }
                else
                {
                    for (int y = 0; y < (m_nHeight / 2); y++)
                    {
                        if (bDoubleSize)
                        {
                            Read(TmpBuf, nChromaOffset + m_nPitch * y, m_nWidth);
                            for (int c = 0; c < m_nWidth; c += 2)
                            {
                                pDstBuf[c * 2] = TmpBuf[c];
                                pDstBuf[c * 2 + 1] = TmpBuf[c] + 1;
                                pDstBuf[c * 2 + 2] = TmpBuf[c];
                                pDstBuf[c * 2 + 3] = TmpBuf[c] + 1;
                                pDstBuf[nDstPitch + c * 2] = TmpBuf[c];
                                pDstBuf[nDstPitch + c * 2 + 1] = TmpBuf[c] + 1;
                                pDstBuf[nDstPitch + c * 2 + 2] = TmpBuf[c];
                                pDstBuf[nDstPitch + c * 2 + 3] = TmpBuf[c] + 1;
                            }
                            pDstBuf += (2 * nDstPitch);
                        }
                        else
                        {
                            Read(pDstBuf, nChromaOffset + m_nPitch * y, m_nWidth);
                            pDstBuf += nDstPitch;
                        }
                    }
                }
            }
            break;

        case FOURCC_YUY2:
            {
                // Frame size.
                const long lFrameSize       = m_nPitch * m_nHeight;

                // Offsets into the file buffer.
                const long nRowOffset       = m_nCurrentFrameIndex * lFrameSize;

                pDstBuf = pDstBufStart;
                if (m_nPitch == nDstPitch)
                {
                    Read(pDstBuf, nRowOffset, m_nPitch * m_nHeight);
                }
                else
                {
                    for (int y = 0; y < m_nHeight; y++)
                    {
                        Read(pDstBuf, nRowOffset + m_nPitch * y, m_nWidth * 2);
                        pDstBuf += nDstPitch;
                    }
                }
            }
            break;

        case FOURCC_ARGB:
        {
                // Frame size.
                const long lFrameSize       = m_nPitch * m_nHeight;

                // Offsets into the file buffer.
                const long nRowOffset       = m_nCurrentFrameIndex * lFrameSize;

                pDstBuf = pDstBufStart;
                if (m_nPitch == nDstPitch)
                {
                    Read(pDstBuf, nRowOffset, m_nPitch * m_nHeight);
                }
                else
                {
                    for (int y = 0; y < m_nHeight; y++)
                    {
                        Read(pDstBuf, nRowOffset + m_nPitch * y, m_nWidth * m_nBPP);
                        pDstBuf += nDstPitch;
                    }
                }
            }
            break;
            return E_NOTIMPL;

        default:
            // Unrecognized FourCC. Please add other image formats!
            return E_INVALIDARG;
    }

    return S_OK;
}

// Simple inline function to encapsulate the actual reading from file.
// We can then change this function to read from memory, or something else if needed.
// The offset is always from the beginning of the file.
//------------------------------------------------------------------------------
inline void CVideoFile::Read(
    char* const pDstBuf,
    long lOffset,
    int nAmount)
{
    if (m_lCurrentOffet != lOffset)
    {
        fseek(m_File, lOffset, SEEK_SET);
    }
    fread(pDstBuf, nAmount, 1, m_File);
    m_lCurrentOffet = lOffset + nAmount;
}

// Progresses the frame pointer so that we read the next frame in the file.
//------------------------------------------------------------------------------
HRESULT CVideoFile::IncrementFrame()
{
    // Track the current frame read from file.
    // We have to ensure that we are not reading more data than there is in the file.
    if (m_nCurrentFrameIndex < m_nNumFrames)
    {
        m_nCurrentFrameIndex++;
    }
    else
    {
        return E_FAIL;
    }

    return S_OK;
}

// Regresses the frame pointer so that we read the last frame in the file.
//------------------------------------------------------------------------------
HRESULT CVideoFile::DecrementFrame()
{
    if (m_nCurrentFrameIndex >= 0)
    {
        m_nCurrentFrameIndex--;
    }
    else
    {
        return E_FAIL;
    }

    return S_OK;
}

// Progresses the frame pointer so that we read the next frame in the file.
//------------------------------------------------------------------------------
int CVideoFile::IncrementFrameCyclic()
{
    // Track the current frame read from file.
    // We have to ensure that we are not reading more data than there is in the file.
    int curIndex = m_nCurrentFrameIndex;
    m_nCurrentFrameIndex++;
    if (m_nCurrentFrameIndex >= m_nNumFrames)
        m_nCurrentFrameIndex = 0;

    return curIndex;
}

// Regresses the frame pointer so that we read the last frame in the file.
//------------------------------------------------------------------------------
int CVideoFile::DecrementFrameCyclic()
{
    int curIndex = m_nCurrentFrameIndex;
    if (m_nCurrentFrameIndex >= 0)
    {
        m_nCurrentFrameIndex--;
    }
    else
    {
        m_nCurrentFrameIndex = m_nNumFrames - 1;
    }

    return curIndex;
}
