/*
// Copyright (c) 2014 Intel Corporation 
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/

#include <HwcTrace.h>
#include <common/RotationBufferProvider.h>
#include <system/graphics-base.h>

namespace android {
namespace intel {

#define CHECK_VA_STATUS_RETURN(FUNC) \
if (vaStatus != VA_STATUS_SUCCESS) {\
    ETRACE(FUNC" failed. vaStatus = %#x", vaStatus);\
    return false;\
}

#define CHECK_VA_STATUS_BREAK(FUNC) \
if (vaStatus != VA_STATUS_SUCCESS) {\
    ETRACE(FUNC" failed. vaStatus = %#x", vaStatus);\
    break;\
}

// With this display value, VA will hook VED driver insead of VSP driver for buffer rotation
#define DISPLAYVALUE  0x56454450

RotationBufferProvider::RotationBufferProvider(Wsbm* wsbm)
    : mWsbm(wsbm),
      mVaInitialized(false),
      mVaDpy(0),
      mVaCfg(0),
      mVaCtx(0),
      mVaBufFilter(0),
      mSourceSurface(0),
      mDisplay(DISPLAYVALUE),
      mWidth(0),
      mHeight(0),
      mTransform(0),
      mRotatedWidth(0),
      mRotatedHeight(0),
      mRotatedStride(0),
      mTargetIndex(0),
      mTTMWrappers(),
      mBobDeinterlace(0)
{
    for (int i = 0; i < MAX_SURFACE_NUM; i++) {
        mKhandles[i] = 0;
        mRotatedSurfaces[i] = 0;
        mDrmBuf[i] = NULL;
    }
}

RotationBufferProvider::~RotationBufferProvider()
{
}

uint32_t RotationBufferProvider::getMilliseconds()
{
    struct timeval ptimeval;
    gettimeofday(&ptimeval, NULL);
    return (uint32_t)((ptimeval.tv_sec * 1000) + (ptimeval.tv_usec / 1000));
}

bool RotationBufferProvider::initialize()
{
    if (NULL == mWsbm)
        return false;
    mTTMWrappers.setCapacity(TTM_WRAPPER_COUNT);
    return true;
}

void RotationBufferProvider::deinitialize()
{
    stopVA();
    reset();
}

void RotationBufferProvider::reset()
{
    if (mTTMWrappers.size()) {
        invalidateCaches();
    }
}

void RotationBufferProvider::invalidateCaches()
{
    void *buf;

    for (size_t i = 0; i < mTTMWrappers.size(); i++) {
        buf = mTTMWrappers.valueAt(i);
        if (!mWsbm->destroyTTMBuffer(buf))
            WTRACE("failed to free TTMBuffer");
    }
    mTTMWrappers.clear();
}

int RotationBufferProvider::transFromHalToVa(int transform)
{
    if (transform == HAL_TRANSFORM_ROT_90)
        return VA_ROTATION_90;
    if (transform == HAL_TRANSFORM_ROT_180)
        return VA_ROTATION_180;
    if (transform == HAL_TRANSFORM_ROT_270)
        return VA_ROTATION_270;
    return 0;
}

int RotationBufferProvider::getStride(bool isTarget, int width)
{
    int stride = 0;
    if (width <= 512)
        stride = 512;
    else if (width <= 1024)
        stride = 1024;
    else if (width <= 1280) {
        stride = 1280;
        if (isTarget)
            stride = 2048;
    } else if (width <= 2048)
        stride = 2048;
    else if (width <= 4096)
        stride = 4096;
    else
        stride = (width + 0x3f) & ~0x3f;
    return stride;
}

buffer_handle_t RotationBufferProvider::createWsbmBuffer(int width, int height, void **buf)
{
    int size = width * height * 3 / 2; // YUV420 NV12 format
    int allignment = 16 * 2048; // tiling row stride aligned
    bool ret = mWsbm->allocateTTMBuffer(size, allignment, buf);

    if (ret == false) {
        ETRACE("failed to allocate TTM buffer");
        return 0;
    }

    return (buffer_handle_t) mWsbm->getKBufHandle(*buf);
}

bool RotationBufferProvider::createVaSurface(VideoPayloadBuffer *payload, int transform, bool isTarget)
{
    VAStatus vaStatus;
    VASurfaceAttributeTPI attribTpi;
    VASurfaceAttributeTPI *vaSurfaceAttrib = &attribTpi;
    int stride;
    unsigned long buffers;
    VASurfaceID *surface;
    int width = 0, height = 0, bufferHeight = 0;

    if (isTarget) {
        if (transFromHalToVa(transform) == VA_ROTATION_180) {
            width = payload->width;
            height = payload->height;
        } else {
            width = payload->height;
            height = payload->width;
        }
        mRotatedWidth = width;
        mRotatedHeight = height;
        bufferHeight = (height + 0x1f) & ~0x1f;
        stride = getStride(isTarget, width);
    } else {
        width = payload->width;
        height = payload->height;
        bufferHeight = (payload->height + 0x1f) & ~0x1f;
        stride = payload->luma_stride; /* NV12 srouce buffer */
    }

    if (!stride) {
        ETRACE("invalid stride value");
        return false;
    }

    mBobDeinterlace = payload->bob_deinterlace;
    // adjust source target for Bob deinterlace
    if (!isTarget && mBobDeinterlace) {
        height >>= 1;
        bufferHeight >>= 1;
        stride <<= 1;
    }

    vaSurfaceAttrib->count = 1;
    vaSurfaceAttrib->width = width;
    vaSurfaceAttrib->height = height;
    vaSurfaceAttrib->pixel_format = payload->format;
    vaSurfaceAttrib->type = VAExternalMemoryKernelDRMBufffer;
    vaSurfaceAttrib->tiling = payload->tiling;
    vaSurfaceAttrib->size = (stride * bufferHeight * 3) / 2;
    vaSurfaceAttrib->luma_offset = 0;
    vaSurfaceAttrib->chroma_v_offset = stride * bufferHeight;
    vaSurfaceAttrib->luma_stride = vaSurfaceAttrib->chroma_u_stride
                                 = vaSurfaceAttrib->chroma_v_stride
                                 = stride;
    vaSurfaceAttrib->chroma_u_offset = vaSurfaceAttrib->chroma_v_offset;
    vaSurfaceAttrib->buffers = &buffers;

    if (isTarget) {
        buffer_handle_t khandle = createWsbmBuffer(stride, bufferHeight, &mDrmBuf[mTargetIndex]);
        if (khandle == 0) {
            ETRACE("failed to create buffer by wsbm");
            return false;
        }

        mKhandles[mTargetIndex] = khandle;
        vaSurfaceAttrib->buffers[0] = (uintptr_t) khandle;
        mRotatedStride = stride;
        surface = &mRotatedSurfaces[mTargetIndex];
    } else {
        vaSurfaceAttrib->buffers[0] = (uintptr_t) payload->khandle;
        surface = &mSourceSurface;
        /* set src surface width/height to video crop size */
        if (payload->crop_width && payload->crop_height) {
            width = payload->crop_width;
            height = (payload->crop_height >> mBobDeinterlace);
        } else {
            VTRACE("Invalid cropping width or height");
            payload->crop_width = width;
            payload->crop_height = height;
        }
    }

    vaStatus = vaCreateSurfacesWithAttribute(mVaDpy,
                                             width,
                                             height,
                                             VA_RT_FORMAT_YUV420,
                                             1,
                                             surface,
                                             vaSurfaceAttrib);
    if (vaStatus != VA_STATUS_SUCCESS) {
        ETRACE("vaCreateSurfacesWithAttribute returns %d", vaStatus);
        ETRACE("Attributes: target: %d, width: %d, height %d, bufferHeight %d, tiling %d",
                isTarget, width, height, bufferHeight, payload->tiling);
        *surface = 0;
        return false;
    }

    return true;
}

bool RotationBufferProvider::startVA(VideoPayloadBuffer *payload, int transform)
{
    bool ret = true;
    VAStatus vaStatus;
    VAEntrypoint *entryPoint;
    VAConfigAttrib attribDummy;
    int numEntryPoints;
    bool supportVideoProcessing = false;
    int majorVer = 0, minorVer = 0;

    // VA will hold a copy of the param pointer, so local varialbe doesn't work
    mVaDpy = vaGetDisplay(&mDisplay);
    if (NULL == mVaDpy) {
        ETRACE("failed to get VADisplay");
        return false;
    }

    vaStatus = vaInitialize(mVaDpy, &majorVer, &minorVer);
    CHECK_VA_STATUS_RETURN("vaInitialize");

    numEntryPoints = vaMaxNumEntrypoints(mVaDpy);

    if (numEntryPoints <= 0) {
        ETRACE("numEntryPoints value is invalid");
        return false;
    }

    entryPoint = (VAEntrypoint*)malloc(sizeof(VAEntrypoint) * numEntryPoints);
    if (NULL == entryPoint) {
        ETRACE("failed to malloc memory for entryPoint");
        return false;
    }

    vaStatus = vaQueryConfigEntrypoints(mVaDpy,
                                        VAProfileNone,
                                        entryPoint,
                                        &numEntryPoints);
    CHECK_VA_STATUS_RETURN("vaQueryConfigEntrypoints");

    for (int i = 0; i < numEntryPoints; i++)
        if (entryPoint[i] == VAEntrypointVideoProc)
            supportVideoProcessing = true;

    free(entryPoint);
    entryPoint = NULL;

    if (!supportVideoProcessing) {
        ETRACE("VAEntrypointVideoProc is not supported");
        return false;
    }

    vaStatus = vaCreateConfig(mVaDpy,
                              VAProfileNone,
                              VAEntrypointVideoProc,
                              &attribDummy,
                              0,
                              &mVaCfg);
    CHECK_VA_STATUS_RETURN("vaCreateConfig");

    // create first target surface
    ret = createVaSurface(payload, transform, true);
    if (ret == false) {
        ETRACE("failed to create target surface with attribute");
        return false;
    }

    vaStatus = vaCreateContext(mVaDpy,
                               mVaCfg,
                               payload->width,
                               payload->height,
                               0,
                               &mRotatedSurfaces[0],
                               1,
                               &mVaCtx);
    CHECK_VA_STATUS_RETURN("vaCreateContext");

    VAProcFilterType filters[VAProcFilterCount];
    unsigned int numFilters = VAProcFilterCount;
    vaStatus = vaQueryVideoProcFilters(mVaDpy, mVaCtx, filters, &numFilters);
    CHECK_VA_STATUS_RETURN("vaQueryVideoProcFilters");

    bool supportVideoProcFilter = false;
    for (unsigned int j = 0; j < numFilters; j++)
        if (filters[j] == VAProcFilterNone)
            supportVideoProcFilter = true;

    if (!supportVideoProcFilter) {
        ETRACE("VAProcFilterNone is not supported");
        return false;
    }

    VAProcFilterParameterBuffer filter;
    filter.type = VAProcFilterNone;
    filter.value = 0;

    vaStatus = vaCreateBuffer(mVaDpy,
                              mVaCtx,
                              VAProcFilterParameterBufferType,
                              sizeof(filter),
                              1,
                              &filter,
                              &mVaBufFilter);
    CHECK_VA_STATUS_RETURN("vaCreateBuffer");

    VAProcPipelineCaps pipelineCaps;
    unsigned int numCaps = 1;
    vaStatus = vaQueryVideoProcPipelineCaps(mVaDpy,
                                            mVaCtx,
                                            &mVaBufFilter,
                                            numCaps,
                                            &pipelineCaps);
    CHECK_VA_STATUS_RETURN("vaQueryVideoProcPipelineCaps");

    if (!(pipelineCaps.rotation_flags & (1 << transFromHalToVa(transform)))) {
        ETRACE("VA_ROTATION_xxx: 0x%08x is not supported by the filter",
             transFromHalToVa(transform));
        return false;
    }

    mVaInitialized = true;

    return true;
}

bool RotationBufferProvider::setupRotationBuffer(VideoPayloadBuffer *payload, int transform)
{
#ifdef DEBUG_ROTATION_PERFROMANCE
    uint32_t setup_Begin = getMilliseconds();
#endif
    VAStatus vaStatus;
    int stride;
    bool ret = false;

    if (payload->format != VA_FOURCC_NV12 || payload->width == 0 || payload->height == 0) {
        WTRACE("payload data is not correct: format %#x, width %d, height %d",
            payload->format, payload->width, payload->height);
        return ret;
    }

    if (payload->width > 1280 && payload->width <= 2048) {
        payload->tiling = 1;
    }

    do {
        if (isContextChanged(payload->width, payload->height, transform)) {
            DTRACE("VA is restarted as rotation context changes");

            if (mVaInitialized) {
                stopVA(); // need to re-initialize VA for new rotation config
            }
            mTransform = transform;
            mWidth = payload->width;
            mHeight = payload->height;
        }

        if (!mVaInitialized) {
            ret = startVA(payload, transform);
            if (ret == false) {
                vaStatus = VA_STATUS_ERROR_OPERATION_FAILED;
                break;
            }
        }

        // start to create next target surface
        if (!mRotatedSurfaces[mTargetIndex]) {
            ret = createVaSurface(payload, transform, true);
            if (ret == false) {
                ETRACE("failed to create target surface with attribute");
                vaStatus = VA_STATUS_ERROR_OPERATION_FAILED;
                break;
            }
        }

        // create source surface
        ret = createVaSurface(payload, transform, false);
        if (ret == false) {
            ETRACE("failed to create source surface with attribute");
            vaStatus = VA_STATUS_ERROR_OPERATION_FAILED;
            break;
        }

#ifdef DEBUG_ROTATION_PERFROMANCE
        uint32_t beginPicture = getMilliseconds();
#endif
        vaStatus = vaBeginPicture(mVaDpy, mVaCtx, mRotatedSurfaces[mTargetIndex]);
        CHECK_VA_STATUS_BREAK("vaBeginPicture");

        VABufferID pipelineBuf;
        void *p;
        VAProcPipelineParameterBuffer *pipelineParam;
        vaStatus = vaCreateBuffer(mVaDpy,
                                  mVaCtx,
                                  VAProcPipelineParameterBufferType,
                                  sizeof(*pipelineParam),
                                  1,
                                  NULL,
                                  &pipelineBuf);
        CHECK_VA_STATUS_BREAK("vaCreateBuffer");

        vaStatus = vaMapBuffer(mVaDpy, pipelineBuf, &p);
        CHECK_VA_STATUS_BREAK("vaMapBuffer");

        pipelineParam = (VAProcPipelineParameterBuffer*)p;
        pipelineParam->surface = mSourceSurface;
        pipelineParam->rotation_state = transFromHalToVa(transform);
        pipelineParam->filters = &mVaBufFilter;
        pipelineParam->num_filters = 1;
        pipelineParam->surface_region = NULL;
        pipelineParam->output_region = NULL;
        pipelineParam->num_forward_references = 0;
        pipelineParam->num_backward_references = 0;
        vaStatus = vaUnmapBuffer(mVaDpy, pipelineBuf);
        CHECK_VA_STATUS_BREAK("vaUnmapBuffer");

        vaStatus = vaRenderPicture(mVaDpy, mVaCtx, &pipelineBuf, 1);
        CHECK_VA_STATUS_BREAK("vaRenderPicture");

        vaStatus = vaEndPicture(mVaDpy, mVaCtx);
        CHECK_VA_STATUS_BREAK("vaEndPicture");

        vaStatus = vaSyncSurface(mVaDpy, mRotatedSurfaces[mTargetIndex]);
        CHECK_VA_STATUS_BREAK("vaSyncSurface");

#ifdef DEBUG_ROTATION_PERFROMANCE
        ITRACE("time spent %dms from vaBeginPicture to vaSyncSurface",
             getMilliseconds() - beginPicture);
#endif

        // Populate payload fields so that overlayPlane can flip the buffer
        payload->rotated_width = mRotatedStride;
        payload->rotated_height = mRotatedHeight;
        payload->rotated_buffer_handle = mKhandles[mTargetIndex];
        // setting client transform to 0 to force re-generating rotated buffer whenever needed.
        payload->client_transform = 0;
        mTargetIndex++;
        if (mTargetIndex >= MAX_SURFACE_NUM)
            mTargetIndex = 0;

    } while (0);

#ifdef DEBUG_ROTATION_PERFROMANCE
    ITRACE("time spent %dms for setupRotationBuffer",
         getMilliseconds() - setup_Begin);
#endif

    if (mSourceSurface > 0) {
        vaStatus = vaDestroySurfaces(mVaDpy, &mSourceSurface, 1);
        if (vaStatus != VA_STATUS_SUCCESS)
            WTRACE("vaDestroySurfaces failed, vaStatus = %d", vaStatus);
        mSourceSurface = 0;
    }

    if (vaStatus != VA_STATUS_SUCCESS) {
        stopVA();
        return false; // To not block HWC, just abort instead of retry
    }

    if (!payload->khandle) {
        WTRACE("khandle is reset by decoder, surface is invalid!");
        return false;
    }

    return true;
}

bool RotationBufferProvider::prepareBufferInfo(int w, int h, int stride, VideoPayloadBuffer *payload, void *user_pt)
{
    int chroma_offset, size;
    void *buf = NULL;

    payload->width = payload->crop_width = w;
    payload->height = payload->crop_height = h;
    payload->coded_width = ((w + 0xf) & ~0xf);
    payload->coded_height = ((h + 0xf) & ~0xf);
    payload->format = VA_FOURCC_NV12;
    payload->tiling = 1;
    payload->luma_stride = stride;
    payload->chroma_u_stride = stride;
    payload->chroma_v_stride = stride;
    payload->client_transform = 0;
    payload->bob_deinterlace = 0;

    chroma_offset = stride * h;
    size = stride * h + stride * h / 2;

    ssize_t index;
    index = mTTMWrappers.indexOfKey((uint64_t)user_pt);
    if (index < 0) {
        VTRACE("wrapped userPt as wsbm buffer");
        bool ret = mWsbm->allocateTTMBufferUB(size, 0, &buf, user_pt);
        if (ret == false) {
            ETRACE("failed to allocate TTM buffer");
            return ret;
        }

        if (mTTMWrappers.size() >= TTM_WRAPPER_COUNT) {
            WTRACE("mTTMWrappers is unexpectedly full. Invalidate caches");
            invalidateCaches();
        }

        index = mTTMWrappers.add((uint64_t)user_pt, buf);
    } else {
        VTRACE("got wsbmBuffer in saved caches");
        buf = mTTMWrappers.valueAt(index);
    }

    payload->khandle = (buffer_handle_t) mWsbm->getKBufHandle(buf);
    return true;
}

void RotationBufferProvider::freeVaSurfaces()
{
    bool ret;
    VAStatus vaStatus;

    for (int i = 0; i < MAX_SURFACE_NUM; i++) {
        if (NULL != mDrmBuf[i]) {
            ret = mWsbm->destroyTTMBuffer(mDrmBuf[i]);
            if (!ret)
                WTRACE("failed to free TTMBuffer");
            mDrmBuf[i] = NULL;
        }
    }

    // remove wsbm buffer ref from VA
    for (int j = 0; j < MAX_SURFACE_NUM; j++) {
        if (0 != mRotatedSurfaces[j]) {
            vaStatus = vaDestroySurfaces(mVaDpy, &mRotatedSurfaces[j], 1);
            if (vaStatus != VA_STATUS_SUCCESS)
                WTRACE("vaDestroySurfaces failed, vaStatus = %d", vaStatus);
        }
        mRotatedSurfaces[j] = 0;
    }
}

void RotationBufferProvider::stopVA()
{
    freeVaSurfaces();

    if (0 != mVaBufFilter)
        vaDestroyBuffer(mVaDpy, mVaBufFilter);
    if (0 != mVaCfg)
        vaDestroyConfig(mVaDpy,mVaCfg);
    if (0 != mVaCtx)
        vaDestroyContext(mVaDpy, mVaCtx);
    if (0 != mVaDpy)
        vaTerminate(mVaDpy);

    mVaInitialized = false;

    for (int i = 0; i < MAX_SURFACE_NUM; i++) {
        mKhandles[i] = 0;
        mRotatedSurfaces[i] = 0;
        mDrmBuf[i] = NULL;
    }
    // reset VA variable
    mVaDpy = 0;
    mVaCfg = 0;
    mVaCtx = 0;
    mVaBufFilter = 0;
    mSourceSurface = 0;

    mWidth = 0;
    mHeight = 0;
    mRotatedWidth = 0;
    mRotatedHeight = 0;
    mRotatedStride = 0;
    mTargetIndex = 0;
    mBobDeinterlace = 0;
}

bool RotationBufferProvider::isContextChanged(int width, int height, int transform)
{
    // check rotation config
    if (height == mHeight &&
        width == mWidth &&
        transform == mTransform) {
        return false;
    }

    return true;
}

} // name space intel
} // name space android