/*
* Copyright (c) 2017, 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 "cm_surface_manager.h"
#include "cm_debug.h"
#include "cm_def.h"
#include "cm_device.h"
#include "cm_mem.h"

int32_t CmSurfaceManager::Surface2DSanityCheck(uint32_t width,
                                           uint32_t height,
                                           CM_SURFACE_FORMAT format)
{
    if ((width < CM_MIN_SURF_WIDTH) || (width > CM_MAX_2D_SURF_WIDTH))
    {
        CmAssert(0);
        return CM_INVALID_WIDTH;
    }
    if ((height < CM_MIN_SURF_HEIGHT) || (height > CM_MAX_2D_SURF_HEIGHT))
    {
        CmAssert(0);
        return CM_INVALID_HEIGHT;
    }

    switch (format)
    {
    case CM_SURFACE_FORMAT_X8R8G8B8:
    case CM_SURFACE_FORMAT_A8R8G8B8:
    case CM_SURFACE_FORMAT_A8B8G8R8:
    case CM_SURFACE_FORMAT_R32F:
    case CM_SURFACE_FORMAT_A16B16G16R16:
    case CM_SURFACE_FORMAT_R10G10B10A2:
    case CM_SURFACE_FORMAT_A16B16G16R16F:
    case CM_SURFACE_FORMAT_L16:
    case CM_SURFACE_FORMAT_D16:
    case CM_SURFACE_FORMAT_A8:
    case CM_SURFACE_FORMAT_P8:
    case CM_SURFACE_FORMAT_V8U8:
    case CM_SURFACE_FORMAT_R16_UINT:
    case CM_SURFACE_FORMAT_R8_UINT:
    case CM_SURFACE_FORMAT_Y216:
    case CM_SURFACE_FORMAT_Y416:
    case CM_SURFACE_FORMAT_AYUV:
    case CM_SURFACE_FORMAT_Y210:
    case CM_SURFACE_FORMAT_Y410:
    case CM_SURFACE_FORMAT_R32G32B32A32F:
        break;

    case CM_SURFACE_FORMAT_UYVY:
    case CM_SURFACE_FORMAT_YUY2:
        if (width & 0x1)
        {
            CmAssert(0);
            return CM_INVALID_WIDTH;
        }
        break;

    case CM_SURFACE_FORMAT_P016:
    case CM_SURFACE_FORMAT_P010:
    case CM_SURFACE_FORMAT_YV12:
    case CM_SURFACE_FORMAT_422H:
    case CM_SURFACE_FORMAT_444P:
    case CM_SURFACE_FORMAT_422V:
    case CM_SURFACE_FORMAT_411P:
    case CM_SURFACE_FORMAT_411R:
    case CM_SURFACE_FORMAT_RGBP:
    case CM_SURFACE_FORMAT_BGRP:
    case CM_SURFACE_FORMAT_IMC3:
    case CM_SURFACE_FORMAT_P208:
        if (width & 0x1)
        {
            CmAssert(0);
            return CM_INVALID_WIDTH;
        }
        if (height & 0x1)
        {
            CmAssert(0);
            return CM_INVALID_HEIGHT;
        }
        break;

    case CM_SURFACE_FORMAT_NV12:
        if (width & 0x1)
        {
            CmAssert(0);
            return CM_INVALID_WIDTH;
        }
        if (height & 0x1)
        {
            CmAssert(0);
            return CM_INVALID_HEIGHT;
        }
        break;

    default:
        CmAssert(0);
        return CM_SURFACE_FORMAT_NOT_SUPPORTED;
    }

    return CM_SUCCESS;
}


int32_t CmSurfaceManager::CreateSurface2D(uint32_t width,
                                      uint32_t height,
                                      CM_SURFACE_FORMAT format,
                                      CmSurface2D *&surface)
{
    int32_t result = CM_SUCCESS;

    //Sanity check
    CHK_RET(Surface2DSanityCheck(width, height, format));

    // Passes the reference to CMRT@UMD to create a CmSurface.
    CHK_RET(AllocateSurface2DInUmd(width, height, format, true, false, 0, surface));
    CHK_NULL(surface);

finish:
    if (FAILED(result))
    {
        surface = nullptr;
    }

    return result;
}

int32_t CmSurfaceManager::CreateSurface2D(VASurfaceID vaSurface,
                                      CmSurface2D *&surface)
{  // Not created by Cm , but it is created through Libva
    return CreateSurface2D(vaSurface, false, true, surface);
}

int32_t CmSurfaceManager::CreateSurface2D(VASurfaceID *vaSurfaceArray,
                                      const uint32_t surfaceCount,
                                      CmSurface2D **surfaceArray)
{
    int32_t result = CM_FAILURE;
    uint32_t surfIndex = 0;
    for (surfIndex = 0; surfIndex < surfaceCount; surfIndex++)
    {
        CHK_RET(CreateSurface2D(vaSurfaceArray[surfIndex], surfaceArray[surfIndex]));
        CHK_NULL(surfaceArray[surfIndex]);
    }

finish:
    if (result != CM_SUCCESS)
    {
        for (uint32_t j = 0; j < surfIndex; j++)
        {
            DestroySurface(surfaceArray[j]);
        }
    }
    return result;
}

int32_t CmSurfaceManager::CreateVaSurface2D(uint32_t width,
                                        uint32_t height,
                                        CM_SURFACE_FORMAT format,
                                        VASurfaceID &vaSurface,
                                        CmSurface2D *&surface)
{
    int32_t hr = CM_SUCCESS;
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    VADisplay *dpy = nullptr;
    uint32_t vaFormat = 0;

    vaFormat = ConvertToLibvaFormat(format);

    //Create Va Surface
    m_device->GetVaDpy(dpy);
    if(dpy == nullptr)
    {
        CmAssert(0);
        return CM_INVALID_LIBVA_INITIALIZE;
    }

    // Set attribute for vaCreateSurfaces
    VASurfaceAttrib surfaceAttrib;
    surfaceAttrib.type = VASurfaceAttribPixelFormat;
    surfaceAttrib.value.type = VAGenericValueTypeInteger;
    surfaceAttrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
    surfaceAttrib.value.value.i = vaFormat;

    // since no 10-bit format is supported in MDF,
    // the VA_RT_FORMAT_YUV420 will be overwritten
    // by the format in attribute
    vaStatus = vaCreateSurfaces(*dpy, VA_RT_FORMAT_YUV420, width, height, &vaSurface, 1,
                                 &surfaceAttrib, 1);
    if (vaStatus != VA_STATUS_SUCCESS)
    {
        CmAssert(0);
        return CM_VA_SURFACE_NOT_SUPPORTED;
    }

    //Create Cm Surface
    hr = CreateSurface2D(vaSurface, true, true, surface);
    if (hr != CM_SUCCESS)
    {
        CmAssert(0);
        vaStatus = vaDestroySurfaces(*dpy, &vaSurface, 1);
        return hr;
    }

    return CM_SUCCESS;
}

int32_t CmSurfaceManager::CreateSurface2D(VASurfaceID &vaSurface,
                                      bool cmCreated,
                                      bool createdbyLibva,
                                      CmSurface2D *&surface)
{
    int32_t result = CM_SUCCESS;
    //Pass it to CMRT@UMD to create CmSurface
    CHK_RET(AllocateSurface2DInUmd(0, 0, (CM_SURFACE_FORMAT)0, cmCreated, createdbyLibva,
                                 vaSurface, surface));
    CHK_NULL(surface);

finish:
    return result;
}

int32_t CmSurfaceManager::ConvertToLibvaFormat(int32_t format)
{
    int32_t vaFmt = format;
    switch (format)
    {
    case VA_CM_FMT_A8R8G8B8:
        vaFmt = VA_FOURCC_ARGB;
        break;

    default:
        vaFmt = format;
        break;
    }
    return vaFmt;
}

int32_t CmSurfaceManager::AllocateSurface2DInUmd(uint32_t width,
                                           uint32_t height,
                                           CM_SURFACE_FORMAT format,
                                           bool cmCreated,
                                           bool createdbyLibva,
                                           VASurfaceID vaSurface,
                                           CmSurface2D *&surface)
{
    VADisplay *display = nullptr;
    m_device->GetVaDpy(display);

    CM_CREATESURFACE2D_PARAM inParam;
    CmSafeMemSet(&inParam, 0, sizeof(CM_CREATESURFACE2D_PARAM));
    inParam.width = width;
    inParam.height = height;
    inParam.format = format;
    inParam.isCmCreated = cmCreated;
    inParam.isLibvaCreated = createdbyLibva;
    inParam.vaSurfaceID = vaSurface;
    inParam.vaDpy = display;

    int32_t hr = m_device->OSALExtensionExecute(CM_FN_CMDEVICE_CREATESURFACE2D,
                                                &inParam, sizeof(inParam),
                                                nullptr, 0);
    CHK_FAILURE_RETURN(hr);
    CHK_FAILURE_RETURN(inParam.returnValue);
    surface = (CmSurface2D *)inParam.cmSurface2DHandle;

    return hr;
}
