/*
 * Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "vaapidecoder_base.h"
#include "common/log.h"
#include "vaapi/vaapisurfaceallocator.h"
#include "vaapi/vaapicontext.h"
#include "vaapi/vaapidisplay.h"
#include "vaapi/VaapiUtils.h"
#include "vaapidecsurfacepool.h"
#include <string.h>
#include <stdlib.h> // for setenv
#include <va/va_backend.h>
#include <unistd.h>

namespace YamiMediaCodec{
typedef VaapiDecoderBase::PicturePtr PicturePtr;

inline void unrefAllocator(SurfaceAllocator* allocator)
{
    allocator->unref(allocator);
}

VaapiDecoderBase::VaapiDecoderBase()
    : m_VAStarted(false)
    , m_currentPTS(INVALID_PTS)
{
    INFO("base: construct()");
    m_externalDisplay.handle = 0,
    m_externalDisplay.type = NATIVE_DISPLAY_AUTO,
    memset(&m_videoFormatInfo, 0, sizeof(VideoFormatInfo));
    memset(&m_configBuffer, 0, sizeof(m_configBuffer));
    m_configBuffer.fourcc = YAMI_FOURCC_NV12;
}

VaapiDecoderBase::~VaapiDecoderBase()
{
    INFO("base: deconstruct()");
    stop();
}

YamiStatus VaapiDecoderBase::createPicture(PicturePtr& picture, int64_t timeStamp /* , VaapiPictureStructure structure = VAAPI_PICTURE_STRUCTURE_FRAME */)
{
    /*accquire one surface from m_surfacePool in base decoder  */
    SurfacePtr surface = createSurface();
    if (!surface) {
        DEBUG("create surface failed");
        return YAMI_DECODE_NO_SURFACE;
    }

    picture.reset(new VaapiDecPicture(m_context, surface, timeStamp));
    return YAMI_SUCCESS;
}

YamiStatus VaapiDecoderBase::start(VideoConfigBuffer* buffer)
{
    YamiStatus status;

    INFO("base: start()");

    if (buffer == NULL) {
        return YAMI_DECODE_INVALID_DATA;
    }

    m_configBuffer = *buffer;
    m_configBuffer.data = NULL;
    m_configBuffer.size = 0;

    m_videoFormatInfo.width = buffer->width;
    m_videoFormatInfo.height = buffer->height;
    m_videoFormatInfo.surfaceWidth = buffer->surfaceWidth;
    m_videoFormatInfo.surfaceHeight = buffer->surfaceHeight;
    m_videoFormatInfo.surfaceNumber = buffer->surfaceNumber;
    if (!m_configBuffer.fourcc) {
        /* This just a workaround, user usually memset the VideoConfigBuffer to zero, and we will copy it to m_configBuffer.
           We need remove fields only for internal user from VideoConfigBuffer
           i.e., following thing should removed:
            int32_t surfaceWidth;
            int32_t surfaceHeight;
            int32_t frameRate;
            int32_t surfaceNumber;
            VAProfile profile;
            uint32_t flag;
            uint32_t fourcc;
        */
        m_videoFormatInfo.fourcc = m_configBuffer.fourcc = YAMI_FOURCC_NV12;
    }
    else {
        m_videoFormatInfo.fourcc = m_configBuffer.fourcc;
    }

    status = setupVA(buffer->surfaceNumber, buffer->profile);
    if (status != YAMI_SUCCESS)
        return status;

    DEBUG
        ("m_videoFormatInfo video size: %d x %d, m_videoFormatInfo surface size: %d x %d",
         m_videoFormatInfo.width, m_videoFormatInfo.height,
         m_videoFormatInfo.surfaceWidth, m_videoFormatInfo.surfaceHeight);

#ifdef __ENABLE_DEBUG__
    renderPictureCount = 0;
    if (access("/tmp/yami", F_OK) == 0) {
        m_dumpSurface = true;
    }
    else {
        m_dumpSurface = false;
    }
    DEBUG("m_dumpSurface: %d", m_dumpSurface);
#endif
    return YAMI_SUCCESS;
}

YamiStatus VaapiDecoderBase::reset(VideoConfigBuffer* buffer)
{
    YamiStatus status;

    INFO("base: reset()");
    if (buffer == NULL) {
        return YAMI_DECODE_INVALID_DATA;
    }

    flush();

    status = terminateVA();
    if (status != YAMI_SUCCESS)
        return status;

    status = start(buffer);
    if (status != YAMI_SUCCESS)
        return status;

    return YAMI_SUCCESS;
}

void VaapiDecoderBase::stop(void)
{
    INFO("base: stop()");
    terminateVA();

    m_currentPTS = INVALID_PTS;

    m_videoFormatInfo.valid = false;
}

void VaapiDecoderBase::flush(void)
{

    INFO("base: flush()");
    m_output.clear();

    m_currentPTS = INVALID_PTS;
}

SharedPtr<VideoFrame> VaapiDecoderBase::getOutput()
{
    SharedPtr<VideoFrame> frame;
    if (m_output.empty())
        return frame;
    frame = m_output.front();
    m_output.pop_front();
    return frame;
}

const VideoFormatInfo *VaapiDecoderBase::getFormatInfo(void)
{
    INFO("base: getFormatInfo()");

    if (!m_VAStarted)
        return NULL;

    return &m_videoFormatInfo;
}

YamiStatus
VaapiDecoderBase::setupVA(uint32_t numSurface, VAProfile profile)
{
    INFO("base: setup VA");

    if (m_VAStarted) {
        return YAMI_SUCCESS;
    }

    if (m_display) {
        WARNING("VA is partially started.");
        return YAMI_FAIL;
    }

    m_display = VaapiDisplay::create(m_externalDisplay);

    if (!m_display) {
        ERROR("failed to create display");
        return YAMI_FAIL;
    }

    VAConfigAttrib attrib;
    attrib.type = VAConfigAttribRTFormat;
    attrib.value = VA_RT_FORMAT_YUV420;
    ConfigPtr config;

    YamiStatus status = VaapiConfig::create(m_display, profile, VAEntrypointVLD, &attrib, 1, config);
    if (YAMI_SUCCESS != status) {
        ERROR("failed to create config");
        return status;
    }

    if (!m_externalAllocator) {
        //use internal allocator
        m_allocator.reset(new VaapiSurfaceAllocator(m_display->getID()), unrefAllocator);
    } else {
        m_allocator = m_externalAllocator;
    }

    m_configBuffer.surfaceNumber = numSurface;
    m_surfacePool = VaapiDecSurfacePool::create(&m_configBuffer, m_allocator);
    DEBUG("surface pool is created");
    if (!m_surfacePool)
        return YAMI_FAIL;
    std::vector<VASurfaceID> surfaces;
    m_surfacePool->getSurfaceIDs(surfaces);
    if (surfaces.empty())
        return YAMI_FAIL;
    int size = surfaces.size();
    m_context = VaapiContext::create(config,
                                       m_videoFormatInfo.width,
                                       m_videoFormatInfo.height,
                                       0, &surfaces[0], size);

    if (!m_context) {
        ERROR("create context failed");
        return YAMI_FAIL;
    }

    m_videoFormatInfo.surfaceWidth = m_videoFormatInfo.width;
    m_videoFormatInfo.surfaceHeight = m_videoFormatInfo.height;

    m_VAStarted = true;
    return YAMI_SUCCESS;
}

bool VaapiDecoderBase::createAllocator()
{
    if (m_allocator)
        return true;
    m_display = VaapiDisplay::create(m_externalDisplay);
    if (!m_display) {
        ERROR("failed to create display");
        return false;
    }

    if (!m_externalAllocator) {
        //use internal allocator
        m_allocator.reset(new VaapiSurfaceAllocator(m_display->getID()), unrefAllocator);
    }
    else {
        m_allocator = m_externalAllocator;
    }
    if (!m_allocator) {
        m_display.reset();
        ERROR("failed to create allocator");
        return false;
    }
    return true;
}

bool VaapiDecoderBase::isSurfaceGeometryChanged() const
{
    return m_config.width < m_videoFormatInfo.surfaceWidth
        || m_config.height < m_videoFormatInfo.surfaceHeight
        || m_config.surfaceNumber != m_videoFormatInfo.surfaceNumber
        || m_config.fourcc != m_videoFormatInfo.fourcc;
}

YamiStatus VaapiDecoderBase::ensureSurfacePool()
{

    if (!isSurfaceGeometryChanged())
        return YAMI_SUCCESS;
    VideoFormatInfo& info = m_videoFormatInfo;
    m_config.width = info.surfaceWidth;
    m_config.height = info.surfaceHeight;
    m_config.surfaceNumber = info.surfaceNumber;
    m_config.fourcc = info.fourcc;

    if (!createAllocator())
        return YAMI_FAIL;

    m_surfacePool = VaapiDecSurfacePool::create(&m_config, m_allocator);
    if (!m_surfacePool)
        return YAMI_FAIL;
    DEBUG("surface pool is created");

    return YAMI_SUCCESS;
}

YamiStatus VaapiDecoderBase::ensureProfile(VAProfile profile)
{
    YamiStatus status;
    status = ensureSurfacePool();
    if (status != YAMI_SUCCESS)
        return status;

    if (!m_display || !m_surfacePool) {
        ERROR("bug: no display or surface pool");
        return YAMI_FAIL;
    }
    if (m_config.profile == profile)
        return YAMI_SUCCESS;

    m_config.profile = profile;
    VAConfigAttrib attrib;
    attrib.type = VAConfigAttribRTFormat;
    attrib.value = VA_RT_FORMAT_YUV420;
    ConfigPtr config;

    status = VaapiConfig::create(m_display, profile, VAEntrypointVLD, &attrib, 1, config);
    if (YAMI_SUCCESS != status) {
        ERROR("failed to create config");
        return status;
    }

    std::vector<VASurfaceID> surfaces;
    m_surfacePool->getSurfaceIDs(surfaces);
    if (surfaces.empty())
        return YAMI_FAIL;
    int size = surfaces.size();
    m_context = VaapiContext::create(config,
        m_videoFormatInfo.width,
        m_videoFormatInfo.height,
        0, &surfaces[0], size);

    if (!m_context) {
        ERROR("create context failed");
        return YAMI_FAIL;
    }
    return YAMI_SUCCESS;
}

YamiStatus VaapiDecoderBase::terminateVA(void)
{
    INFO("base: terminate VA");
    m_output.clear();
    m_config.resetConfig();
    m_surfacePool.reset();
    m_allocator.reset();
    DEBUG("surface pool is reset");
    m_context.reset();
    m_display.reset();

    m_VAStarted = false;
    return YAMI_SUCCESS;
}

void VaapiDecoderBase::setNativeDisplay(NativeDisplay * nativeDisplay)
{
    if (!nativeDisplay || nativeDisplay->type == NATIVE_DISPLAY_AUTO)
        return;

    m_externalDisplay = *nativeDisplay;
}

void VaapiDecoderBase::releaseLock(bool lockable)
{
    if (!m_surfacePool)
        return;

}

void VaapiDecoderBase::setAllocator(SurfaceAllocator* allocator)
{
    m_externalAllocator.reset(allocator,unrefAllocator);
}

SurfacePtr VaapiDecoderBase::createSurface()
{
    SurfacePtr surface;
    if (m_surfacePool) {
        surface = m_surfacePool->acquire();
    }
    return surface;
}

struct VaapiDecoderBase::VideoFrameRecycler {
    VideoFrameRecycler(const SurfacePtr& surface)
        : m_surface(surface)
    {
    }
    void operator()(VideoFrame* frame) {}
private:
    SurfacePtr m_surface;
};

YamiStatus VaapiDecoderBase::outputPicture(const PicturePtr& picture)
{
    SurfacePtr surface = picture->getSurface();
    SharedPtr<VideoFrame> frame(surface->m_frame.get(), VideoFrameRecycler(surface));
    frame->timeStamp = picture->m_timeStamp;
    m_output.push_back(frame);
    return YAMI_SUCCESS;
}

VADisplay VaapiDecoderBase::getDisplayID()
{
    if (!m_display)
        return NULL;
    return m_display->getID();
}

#define CHECK(f)                        \
    do {                                \
        if (m_videoFormatInfo.f != f) { \
            m_videoFormatInfo.f = f;    \
            changed = true;             \
        }                               \
    } while (0)

bool VaapiDecoderBase::setFormat(uint32_t width, uint32_t height, uint32_t surfaceWidth, uint32_t surfaceHeight,
    uint32_t surfaceNumber, uint32_t fourcc)
{
    bool changed = false;
    CHECK(width);
    CHECK(height);
    CHECK(surfaceWidth);
    CHECK(surfaceHeight);
    CHECK(surfaceNumber);
    CHECK(fourcc);
    //this not true, but it's only way to let user get format info
    m_VAStarted = true;
    return changed;
}

} //namespace YamiMediaCodec
