// Copyright (c) 2003-2018 Intel Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include "umc_defs.h"
#include "umc_frame_data.h"

#include <algorithm>

namespace UMC
{

// Number of planes in format description table.
// Number of planes in image can be greater. In this case additional
// planes should be supported by the user with functions SetPlanePointer,
// SetPlanePitch.
enum
{
    MAX_PLANE_NUMBER            = 4
};

struct PlaneFormatInfo
{
    int32_t m_iWidthScale;  // Horizontal downsampling factor
    int32_t m_iHeightScale; // Vertical downsampling factor
    int32_t m_iChannels;  // Number of merged channels in the plane
};

// Color format description structure
struct ColorFormatInfo
{
    ColorFormat m_cFormat;
    uint32_t m_iPlanes;        // Number of planes
    int32_t m_iMinBitDepth;   // Minimum bitdepth

    PlaneFormatInfo m_PlaneFormatInfo[MAX_PLANE_NUMBER];
};

// Color format description table
static
const
ColorFormatInfo FormatInfo[] =
{
    {YV12,    3,  8, {{0, 0, 1}, {1, 1, 1}, {1, 1, 1}}},
    {NV12,    2,  8, {{0, 0, 1}, {1, 1, 2}, }},
    {NV16,    2,  8, {{0, 0, 1}, {1, 0, 2}, }},
    {IMC3,    3,  8, {{0, 0, 1}, {1, 1, 1}, {1, 1, 1}}},
    {YUY2,    1,  8, {{2, 1, 4}, }},
    {UYVY,    1,  8, {{2, 1, 4}, }},
    {YUV411,  3,  8, {{1, 1, 1}, {4, 1, 1}, {4, 1, 1}}},
    {YUV420,  3,  8, {{0, 0, 1}, {1, 1, 1}, {1, 1, 1}}},
    {YUV422,  3,  8, {{0, 0, 1}, {1, 0, 1}, {1, 0, 1}}},
    {YUV444,  3,  8, {{0, 0, 1}, {0, 0, 1}, {0, 0, 1}}},
    {YUV_VC1, 3,  8, {{1, 1, 1}, {2, 2, 1}, {2, 2, 1}}},
    {Y411,    1,  8, {{4, 1, 6}}},
    {Y41P,    1,  8, {{8, 1, 12}}},
    {RGB32,   1,  8, {{1, 1, 4}}},
    {AYUV,    1,  8, {{0, 0, 4}}},
    {RGB24,   1,  8, {{1, 1, 3}}},
    {RGB565,  1, 16, {{1, 1, 1}}},
    {RGB555,  1, 16, {{1, 1, 1}}},
    {RGB444,  1, 16, {{1, 1, 1}}},
    {GRAY,    1,  8, {{0, 0, 1}}},
    {GRAYA,   2,  8, {{0, 0, 1}, {0, 0, 1}}},
    {YUV420A, 4,  8, {{0, 0, 1}, {1, 1, 1}, {1, 1, 1}, {0, 0, 1}}},
    {YUV422A, 4,  8, {{0, 0, 1}, {1, 0, 1}, {1, 0, 1}, {0, 0, 1}}},
    {YUV444A, 4,  8, {{0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1}}},
    {YVU9,    3,  8, {{1, 1, 1}, {4, 4, 1}, {4, 4, 1}}},
    {P010,    3, 10, {{0, 0, 1}, {1, 1, 2}, }},
    {P016,    3, 16, {{0, 0, 1}, {1, 1, 2}, }},
    {P210,    3, 10, {{0, 0, 1}, {1, 0, 2}, }},
    {P216,    3, 16, {{0, 0, 1}, {1, 0, 2}, }},
    {Y210,    1, 10, {{2, 1, 4}, }},
    {Y216,    1, 16, {{2, 1, 4}, }},
    {Y410,    1, 10, {{0, 0, 1}, }},
    {Y416,    1, 16, {{0, 0, 1}, }}
};

// Number of entries in the FormatInfo table
static
const
int32_t iNumberOfFormats = sizeof(FormatInfo) / sizeof(FormatInfo[0]);

// returns pointer to color format description table for cFormat
// or NULL if cFormat is not described (unknown color format).
static
const
ColorFormatInfo *GetColorFormatInfo(ColorFormat cFormat)
{
    const ColorFormatInfo *pReturn = NULL;
    int32_t i;

    // find required format
    for (i = 0; i < iNumberOfFormats; i += 1)
    {
        if (FormatInfo[i].m_cFormat == cFormat)
        {
            pReturn = FormatInfo + i;
            break;
        }
    }

    return pReturn;
} // ColorFormatInfo *GetColorFormatInfo(ColorFormat cFormat)

// Initialization to undefined image type
VideoDataInfo::VideoDataInfo(void)
{
    Close();
    memset(m_pPlaneData, 0, sizeof(m_pPlaneData));
} // VideoDataInfo::VideoDataInfo(void)

VideoDataInfo::~VideoDataInfo()
{

} // VideoDataInfo::~VideoDataInfo(void)

void VideoDataInfo::Close()
{
    m_iPlanes = 0;
    m_ippSize.width = 0;
    m_ippSize.height = 0;
    m_ColorFormat = NONE;
    m_picStructure = PS_FRAME;

    // set square pixel
    m_iHorzAspect =
    m_iVertAspect = 1;
}

// Initializes image dimensions and bitdepth.
// Has to be followed by SetColorFormat call.
// Planes' information is initialized to invalid values
Status VideoDataInfo::Init(int32_t iWidth,
                       int32_t iHeight,
                       int32_t iPlanes,
                       int32_t iBitDepth)
{
    int32_t i;

    if ((0 >= iWidth) ||
        (0 >= iHeight) ||
        (0 >= iPlanes) ||
        (8 > iBitDepth))
        return UMC_ERR_INVALID_PARAMS;

    for (i = 0; i < iPlanes; i++)
    {
        m_pPlaneData[i].m_iSamples = 1;
        m_pPlaneData[i].m_iSampleSize = (iBitDepth+7)>>3;
        m_pPlaneData[i].m_iBitDepth = iBitDepth;
        // can't assign dimension to unknown planes
        m_pPlaneData[i].m_ippSize.width = 0;
        m_pPlaneData[i].m_ippSize.height = 0;
    }

    m_iPlanes = iPlanes;
    m_ippSize.width = iWidth;
    m_ippSize.height = iHeight;

    return UMC_OK;

} // Status VideoDataInfo::Init(int32_t iWidth,

// Completely sets image information, without allocation or linking to
// image memory.
Status VideoDataInfo::Init(int32_t iWidth,
                       int32_t iHeight,
                       ColorFormat cFormat,
                       int32_t iBitDepth)
{
    Status umcRes;
    const ColorFormatInfo* pFormat;

    pFormat = GetColorFormatInfo(cFormat);
    if(NULL == pFormat)
        return UMC_ERR_INVALID_PARAMS;

    // allocate planes
    if(iBitDepth == 0)
      iBitDepth = pFormat->m_iMinBitDepth;
    umcRes = Init(iWidth, iHeight, pFormat->m_iPlanes, iBitDepth);
    if (UMC_OK != umcRes)
        return umcRes;

    // set color format and
    // correct width & height for planes
    umcRes = SetColorFormat(cFormat);
    if (UMC_OK != umcRes)
        return umcRes;

    return UMC_OK;

} // Status VideoDataInfo::Init(int32_t iWidth,

// Sets or change Color format information for image, only when it has
// specified size, number of planes and bitdepth. Number of planes in cFormat must
// be not greater than specified in image.
Status VideoDataInfo::SetColorFormat(ColorFormat cFormat)
{
    const ColorFormatInfo *pFormat;

    // check error(s)
    pFormat = GetColorFormatInfo(cFormat);
    if (NULL == pFormat)
        return UMC_ERR_INVALID_STREAM;

    if (m_iPlanes < pFormat->m_iPlanes)
        return UMC_ERR_INVALID_STREAM;

    m_ColorFormat = cFormat;

    // set correct width & height to planes
    for (uint32_t i = 0; i < m_iPlanes; i += 1)
    {
        if (i < pFormat->m_iPlanes)
        {
            m_pPlaneData[i].m_iWidthScale = pFormat->m_PlaneFormatInfo[i].m_iWidthScale;
            m_pPlaneData[i].m_iHeightScale = pFormat->m_PlaneFormatInfo[i].m_iHeightScale;
            m_pPlaneData[i].m_iSamples = pFormat->m_PlaneFormatInfo[i].m_iChannels;
        } else {
            m_pPlaneData[i].m_iWidthScale = 0;
            m_pPlaneData[i].m_iHeightScale = 0;
            m_pPlaneData[i].m_iSamples = 1;
        }

        m_pPlaneData[i].m_ippSize.width = m_ippSize.width >> m_pPlaneData[i].m_iWidthScale;
        m_pPlaneData[i].m_ippSize.height = m_ippSize.height >> m_pPlaneData[i].m_iHeightScale;
    }

    return UMC_OK;
} // Status VideoDataInfo::SetColorFormat(ColorFormat cFormat)


// Set sample size for specified plane, usually additional or when bitdepth differs
// for main planes
Status VideoDataInfo::SetPlaneSampleSize(int32_t iSampleSize, uint32_t iPlaneNumber)
{
    // check error(s)
    if (m_iPlanes <= iPlaneNumber)
        return UMC_ERR_FAILED;

    m_pPlaneData[iPlaneNumber].m_iSampleSize = iSampleSize;
    if(iSampleSize*8 < m_pPlaneData[iPlaneNumber].m_iBitDepth)
        m_pPlaneData[iPlaneNumber].m_iBitDepth = iSampleSize*8;

    return UMC_OK;
}

const VideoDataInfo::PlaneInfo* VideoDataInfo::GetPlaneInfo(uint32_t plane) const
{
    // check error(s)
    if (m_iPlanes <= plane)
        return 0;

    return &m_pPlaneData[plane];
}

size_t VideoDataInfo::GetSize() const
{
    size_t sz = 0;

    for (uint32_t i = 0; i < m_iPlanes; i++)
    {
        sz += m_pPlaneData[i].m_ippSize.width * m_pPlaneData[i].m_ippSize.height * m_pPlaneData[i].m_iSampleSize * m_pPlaneData[i].m_iSamples;
    }

    return sz;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Time
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FrameTime::FrameTime()
{
    Reset();
}

void FrameTime::Reset()
{
    m_pts_start = -1;
    m_pts_end = -1;
}

Status FrameTime::GetTime(double& start, double& end) const
{
    start = m_pts_start;
    end = m_pts_end;
    return UMC_OK;
}

Status FrameTime::SetTime(double start, double end)
{
    m_pts_start = start;
    m_pts_end = end;
    return UMC_OK;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FrameData::FrameData()
    : m_locked(false)
    , m_FrameMID(FRAME_MID_INVALID)
    , m_FrameAlloc(0)
    , m_PlaneInfo{}
{
}

FrameData::FrameData(const FrameData & fd)
    : m_locked(false)
    , m_Info(fd.m_Info)
    , m_FrameMID(fd.m_FrameMID)
    , m_FrameAlloc(fd.m_FrameAlloc)
    , m_AuxInfo(fd.m_AuxInfo)
{
    MFX_INTERNAL_CPY(m_PlaneInfo, fd.m_PlaneInfo, sizeof(m_PlaneInfo));
    if (m_FrameAlloc)
    {
        m_FrameAlloc->IncreaseReference(m_FrameMID);
    }
}

FrameData::~FrameData()
{
    Close();
}

FrameData& FrameData::operator=(const FrameData& fd)
{
    // check for assignment for self
    if (this == &fd)
    {
        return *this;
    }

    Close();

    m_FrameAlloc = fd.m_FrameAlloc;
    m_FrameMID = fd.m_FrameMID;
    m_Info = fd.m_Info;
    MFX_INTERNAL_CPY(m_PlaneInfo, fd.m_PlaneInfo, sizeof(m_PlaneInfo));
    m_locked = false;// fd.m_locked;

    if (m_FrameAlloc)
    {
        m_FrameAlloc->IncreaseReference(m_FrameMID);
    }

    m_AuxInfo = fd.m_AuxInfo;

    return *this;
}

const VideoDataInfo * FrameData::GetInfo() const
{
    return &m_Info;
}

FrameMemID FrameData::GetFrameMID() const
{
    return m_FrameMID;
}

FrameMemID FrameData::Release()
{
    FrameTime::Reset();
    FrameMemID mid = m_FrameMID;
    m_locked = false;
    m_FrameMID = FRAME_MID_INVALID;
    m_FrameAlloc = 0;
    m_Info.Close();
    return mid;
}

void FrameData::Init(const VideoDataInfo * info, FrameMemID memID, FrameAllocator * frameAlloc)
{
    Close();

    m_Info = *info;
    m_FrameMID = memID;
    m_FrameAlloc = frameAlloc;
    if (m_FrameAlloc && m_FrameMID != FRAME_MID_INVALID)
        m_FrameAlloc->IncreaseReference(m_FrameMID);
}

void FrameData::Close()
{
    FrameTime::Reset();

    if (m_FrameAlloc && m_FrameMID != FRAME_MID_INVALID)
    {
        if (m_locked)
        {
            m_FrameAlloc->Unlock(m_FrameMID);
        }

        m_locked = false;

        m_FrameAlloc->DecreaseReference(m_FrameMID);
        m_FrameMID = FRAME_MID_INVALID;
        m_FrameAlloc = 0;
    }

    memset(m_PlaneInfo, 0, sizeof(m_PlaneInfo));
    m_Info.Close();

    m_AuxInfo.clear();
}

const FrameData::PlaneMemoryInfo * FrameData::GetPlaneMemoryInfo(uint32_t plane) const
{
    if (plane >= m_Info.GetNumPlanes())
        return 0;

    return &(m_PlaneInfo[plane]);
}

void FrameData::SetPlanePointer(uint8_t* planePtr, uint32_t plane, size_t pitch)
{
    if (plane >= m_Info.GetNumPlanes())
        return;

    m_PlaneInfo[plane].m_planePtr = planePtr;
    m_PlaneInfo[plane].m_pitch = pitch;
}

void FrameData::SetAuxInfo(void* ptr, size_t size, int type)
{
     FrameAuxInfo* aux = GetAuxInfo(type);
     if (!aux)
     {
         m_AuxInfo.push_back(FrameAuxInfo());
         aux = &m_AuxInfo.back();
     }

     aux->ptr = ptr;
     aux->size = size;
     aux->type = type;
}

void FrameData::ClearAuxInfo(int type)
{
    FrameAuxInfo aux = { 0, 0, type };
    m_AuxInfo.remove(aux);
}

FrameData::FrameAuxInfo const* FrameData::GetAuxInfo(int type) const
{
    FrameAuxInfo aux = { 0, 0, type };
    std::list<FrameAuxInfo>::const_iterator
        i = std::find(m_AuxInfo.begin(), m_AuxInfo.end(), aux);

    return
        i != m_AuxInfo.end() ? &(*i) : 0;
}

} // end namespace UMC
