/*
 * Copyright (c) 2011 Intel Corporation. All Rights Reserved.
 * Copyright (c) Imagination Technologies Limited, UK
 *
 * 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, sub license, 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 (including the
 * next paragraph) 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS 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.
 *
 * Authors:
 *    Elaine Wang <elaine.wang@intel.com>
 *    Zeng Li <zeng.li@intel.com>
 *
 */

#include "psb_def.h"
#include "psb_drv_debug.h"
#include "psb_surface.h"
#include "psb_cmdbuf.h"
#include "pnw_MPEG4ES.h"
#include "pnw_hostcode.h"
#include "pnw_hostheader.h"

#include <stdlib.h>
#include <stdint.h>
#include <string.h>


#define TOPAZ_MPEG4_MAX_BITRATE 16000000

#define INIT_CONTEXT_MPEG4ES    context_ENC_p ctx = (context_ENC_p) obj_context->format_data
#define SURFACE(id)    ((object_surface_p) object_heap_lookup( &ctx->obj_context->driver_data->surface_heap, id ))
#define BUFFER(id)  ((object_buffer_p) object_heap_lookup( &ctx->obj_context->driver_data->buffer_heap, id ))



static void pnw_MPEG4ES_QueryConfigAttributes(
    VAProfile __maybe_unused profile,
    VAEntrypoint __maybe_unused entrypoint,
    VAConfigAttrib * attrib_list,
    int num_attribs)
{
    int i;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_QueryConfigAttributes\n");

    /* RateControl attributes */
    for (i = 0; i < num_attribs; i++) {
        switch (attrib_list[i].type) {
        case VAConfigAttribRTFormat:
            break;

        case VAConfigAttribRateControl:
            attrib_list[i].value = VA_RC_NONE | VA_RC_CBR | VA_RC_VBR;
            break;

        default:
            attrib_list[i].value = VA_ATTRIB_NOT_SUPPORTED;
            break;
        }
    }

    return;
}


static VAStatus pnw_MPEG4ES_ValidateConfig(
    object_config_p obj_config)
{
    int i;
    /* Check all attributes */
    for (i = 0; i < obj_config->attrib_count; i++) {
        switch (obj_config->attrib_list[i].type) {
        case VAConfigAttribRTFormat:
            /* Ignore */
            break;
        case VAConfigAttribRateControl:
            break;
        default:
            return VA_STATUS_ERROR_ATTR_NOT_SUPPORTED;
        }
    }

    return VA_STATUS_SUCCESS;
}


static VAStatus pnw_MPEG4ES_CreateContext(
    object_context_p obj_context,
    object_config_p obj_config)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    context_ENC_p ctx;
    int i;
    unsigned int eRCmode;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_CreateContext\n");

    vaStatus = pnw_CreateContext(obj_context, obj_config, 0);
    if (VA_STATUS_SUCCESS != vaStatus)
        return VA_STATUS_ERROR_ALLOCATION_FAILED;

    ctx = (context_ENC_p) obj_context->format_data;

    for (i = 0; i < obj_config->attrib_count; i++) {
        if (obj_config->attrib_list[i].type == VAConfigAttribRateControl)
            break;
    }

    if (i >= obj_config->attrib_count)
        eRCmode = VA_RC_NONE;
    else
        eRCmode = obj_config->attrib_list[i].value;


    if (eRCmode == VA_RC_VBR) {
        ctx->eCodec = IMG_CODEC_MPEG4_VBR;
        ctx->sRCParams.RCEnable = IMG_TRUE;
    } else if (eRCmode == VA_RC_CBR) {
        ctx->eCodec = IMG_CODEC_MPEG4_CBR;
        ctx->sRCParams.RCEnable = IMG_TRUE;
    } else if (eRCmode == VA_RC_NONE) {
        ctx->eCodec = IMG_CODEC_MPEG4_NO_RC;
        ctx->sRCParams.RCEnable = IMG_FALSE;
    } else
        return VA_STATUS_ERROR_UNSUPPORTED_RT_FORMAT;
    ctx->eFormat = IMG_CODEC_PL12;

    ctx->Slices = 1;
    ctx->ParallelCores = 1;

    ctx->IPEControl = pnw__get_ipe_control(ctx->eCodec);

    switch (obj_config->profile) {
    case VAProfileMPEG4Simple:
        ctx->profile_idc = 2;
        break;
    case VAProfileMPEG4AdvancedSimple:
        ctx->profile_idc = 3;
        break;
    default:
        ctx->profile_idc = 2;
        break;
    }

    return vaStatus;
}


static void pnw_MPEG4ES_DestroyContext(
    object_context_p obj_context)
{
    drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_DestroyPicture\n");

    pnw_DestroyContext(obj_context);
}

static VAStatus pnw_MPEG4ES_BeginPicture(
    object_context_p obj_context)
{
    INIT_CONTEXT_MPEG4ES;
    VAStatus vaStatus = VA_STATUS_SUCCESS;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_BeginPicture\n");

    vaStatus = pnw_BeginPicture(ctx);

    return vaStatus;
}

static VAStatus pnw__MPEG4ES_process_sequence_param(context_ENC_p ctx, object_buffer_p obj_buffer)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    VAEncSequenceParameterBufferMPEG4 *seq_params;
    pnw_cmdbuf_p cmdbuf = ctx->obj_context->pnw_cmdbuf;
    MPEG4_PROFILE_TYPE profile;
    int i, vop_time_increment_resolution;
    unsigned frame_size;

    ASSERT(obj_buffer->type == VAEncSequenceParameterBufferType);
    ASSERT(obj_buffer->num_elements == 1);
    ASSERT(obj_buffer->size == sizeof(VAEncSequenceParameterBufferMPEG4));

    //initialize the frame_rate and qp
    ctx->sRCParams.InitialQp = 15;
    ctx->sRCParams.MinQP = 1;
    ctx->sRCParams.FrameRate = 30;

    if ((obj_buffer->num_elements != 1) ||
        (obj_buffer->size != sizeof(VAEncSequenceParameterBufferMPEG4))) {
        return VA_STATUS_ERROR_UNKNOWN;
    }

    seq_params = (VAEncSequenceParameterBufferMPEG4 *) obj_buffer->buffer_data;
    obj_buffer->buffer_data = NULL;
    obj_buffer->size = 0;

    if (seq_params->bits_per_second > TOPAZ_MPEG4_MAX_BITRATE) {
        ctx->sRCParams.BitsPerSecond = TOPAZ_MPEG4_MAX_BITRATE;
        drv_debug_msg(VIDEO_DEBUG_GENERAL, " bits_per_second(%d) exceeds \
		the maximum bitrate, set it with %d\n",
                                 seq_params->bits_per_second,
                                 TOPAZ_MPEG4_MAX_BITRATE);
    } else
        ctx->sRCParams.BitsPerSecond = seq_params->bits_per_second;

    ctx->sRCParams.FrameRate = (seq_params->frame_rate < 1) ?
        1 : ((65535 < seq_params->frame_rate) ? 65535 : seq_params->frame_rate);
    ctx->sRCParams.InitialQp = seq_params->initial_qp;
    ctx->sRCParams.MinQP = seq_params->min_qp;
    ctx->sRCParams.BUSize = 0;  /* default 0, and will be set in pnw__setup_busize */

    ctx->sRCParams.Slices = 1;
    ctx->sRCParams.QCPOffset = 0;/* FIXME */

    if (ctx->sRCParams.IntraFreq != seq_params->intra_period
            && ctx->raw_frame_count != 0
            && ctx->sRCParams.IntraFreq != 0
            && ((ctx->obj_context->frame_count + 1) % ctx->sRCParams.IntraFreq) != 0
            && (!ctx->sRCParams.bDisableFrameSkipping)) {
        drv_debug_msg(VIDEO_DEBUG_ERROR,
                "Changing intra period value in the middle of a GOP is\n"
                "not allowed if frame skip isn't disabled.\n"
                "it can cause I frame been skipped\n");
        free(seq_params);
        return VA_STATUS_ERROR_INVALID_PARAMETER;
    }
    else
        ctx->sRCParams.IntraFreq = seq_params->intra_period;

    ctx->sRCParams.IntraFreq = seq_params->intra_period;

    frame_size = ctx->sRCParams.BitsPerSecond / ctx->sRCParams.FrameRate;

    ctx->sRCParams.BufferSize = ctx->sRCParams.BitsPerSecond;
    /* Header buffersize is specified in 16384 units, so ensure conformance
       of parameters. InitialLevel in units of 64, assured by this */

    ctx->sRCParams.BufferSize /= 16384;
    ctx->sRCParams.BufferSize *= 16384;

    ctx->sRCParams.InitialLevel = (3 * ctx->sRCParams.BufferSize) >> 4;
    /* Aligned with target frame size */
    ctx->sRCParams.InitialLevel += (frame_size / 2);
    ctx->sRCParams.InitialLevel /= frame_size;
    ctx->sRCParams.InitialLevel *= frame_size;
    ctx->sRCParams.InitialDelay = ctx->sRCParams.BufferSize - ctx->sRCParams.InitialLevel;
    ctx->buffer_size = ctx->sRCParams.BufferSize;

    if (ctx->raw_frame_count == 0) { /* Add Register IO behind begin Picture */
        for (i = (ctx->ParallelCores - 1); i >= 0; i--)
            pnw_set_bias(ctx, i);
    }

    cmdbuf = ctx->obj_context->pnw_cmdbuf;

    switch (ctx->profile_idc) {
    case 2:
        profile = SP;
        break;
    case 3:
        profile = ASP;
        break;
    default:
        profile = SP;
        break;
    }

    memset(cmdbuf->header_mem_p + ctx->seq_header_ofs,
           0,
           HEADER_SIZE);

    vop_time_increment_resolution = (seq_params->vop_time_increment_resolution < 1) ? 1 :
        ((65535 < seq_params->vop_time_increment_resolution) ? 65535 : seq_params->vop_time_increment_resolution);
    pnw__MPEG4_prepare_sequence_header(
        cmdbuf->header_mem_p + ctx->seq_header_ofs,
        0, /* BFrame? */
        profile, /* sProfile */
        seq_params->profile_and_level_indication, /* */
        seq_params->fixed_vop_time_increment, /*3,*/  /* sFixed_vop_time_increment */
        seq_params->video_object_layer_width,/* Picture_Width_Pixels */
        seq_params->video_object_layer_height, /* Picture_Height_Pixels */
        NULL,
        vop_time_increment_resolution); /* VopTimeResolution */

    ctx->MPEG4_vop_time_increment_resolution = vop_time_increment_resolution;

    pnw_cmdbuf_insert_command_package(ctx->obj_context,
                                      ctx->ParallelCores - 1, /* Send to the last core as this will complete first */
                                      MTX_CMDID_DO_HEADER,
                                      &cmdbuf->header_mem,
                                      ctx->seq_header_ofs);

    free(seq_params);
    return vaStatus;
}


static VAStatus pnw__MPEG4ES_process_picture_param(context_ENC_p ctx, object_buffer_p obj_buffer)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    VAEncPictureParameterBufferMPEG4 *pBuffer;
    pnw_cmdbuf_p cmdbuf = ctx->obj_context->pnw_cmdbuf;
    unsigned int *pPictureHeaderMem;
    MTX_HEADER_PARAMS *psPicHeader;
    int i;
    IMG_BOOL bIsVOPCoded = IMG_TRUE;

    ASSERT(obj_buffer->type == VAEncPictureParameterBufferType);

    if ((obj_buffer->num_elements != 1) ||
        (obj_buffer->size != sizeof(VAEncPictureParameterBufferMPEG4))) {
        return VA_STATUS_ERROR_UNKNOWN;
    }

    /* Transfer ownership of VAEncPictureParameterBufferMPEG4 data */
    pBuffer = (VAEncPictureParameterBufferMPEG4 *) obj_buffer->buffer_data;
    obj_buffer->buffer_data = NULL;
    obj_buffer->size = 0;

    ctx->ref_surface = SURFACE(pBuffer->reference_picture);
    ctx->dest_surface = SURFACE(pBuffer->reconstructed_picture);
    ctx->coded_buf = BUFFER(pBuffer->coded_buf);

    ASSERT(ctx->Width == pBuffer->picture_width);
    ASSERT(ctx->Height == pBuffer->picture_height);

    /*if (ctx->sRCParams.RCEnable && ctx->sRCParams.FrameSkip)
        bIsVOPCoded = IMG_FALSE;*/

    ctx->FCode = 4 - 1; /* 4 is default value of "ui8Search_range" */

    pPictureHeaderMem = (unsigned int *)(cmdbuf->header_mem_p + ctx->pic_header_ofs);
    psPicHeader = (MTX_HEADER_PARAMS *)pPictureHeaderMem;

    memset(pPictureHeaderMem, 0, HEADER_SIZE);

    pnw__MPEG4_prepare_vop_header((unsigned char *)pPictureHeaderMem,
                                  bIsVOPCoded,
                                  pBuffer->vop_time_increment, /* In testbench, this should be FrameNum */
                                  4,/* default value is 4,search range */
                                  pBuffer->picture_type,
                                  ctx->MPEG4_vop_time_increment_resolution/* defaule value */);

    /* Mark this header as a complex header */
    psPicHeader->Elements |= 0x100;
    pPictureHeaderMem += ((HEADER_SIZE)  >> 3);

    pnw__MPEG4_prepare_vop_header((unsigned char *)pPictureHeaderMem,
                                  IMG_FALSE,
                                  pBuffer->vop_time_increment, /* In testbench, this should be FrameNum */
                                  4,/* default value is 4,search range */
                                  pBuffer->picture_type,
                                  ctx->MPEG4_vop_time_increment_resolution/* defaule value */);

    pnw_cmdbuf_insert_command_package(ctx->obj_context,
                                      ctx->ParallelCores - 1, /* Send to the last core as this will complete first */
                                      MTX_CMDID_DO_HEADER,
                                      &cmdbuf->header_mem,
                                      ctx->pic_header_ofs);

    /* Prepare START_PICTURE params */
    for (i = (ctx->ParallelCores - 1); i >= 0; i--)
        vaStatus = pnw_RenderPictureParameter(ctx, i);

    free(pBuffer);
    return vaStatus;
}

static VAStatus pnw__MPEG4ES_process_slice_param(context_ENC_p ctx, object_buffer_p obj_buffer)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    VAEncSliceParameterBuffer *pBuffer;
    pnw_cmdbuf_p cmdbuf = ctx->obj_context->pnw_cmdbuf;
    PIC_PARAMS *psPicParams = (PIC_PARAMS *)(cmdbuf->pic_params_p);
    unsigned int i;
    int slice_param_idx;

    ASSERT(obj_buffer->type == VAEncSliceParameterBufferType);

    pBuffer = (VAEncSliceParameterBuffer *) obj_buffer->buffer_data;

    /*In case the slice number changes*/
    if ((ctx->slice_param_cache != NULL) && (obj_buffer->num_elements != ctx->slice_param_num)) {
        drv_debug_msg(VIDEO_DEBUG_GENERAL, "Slice number changes. Previous value is %d. Now it's %d\n",
                                 ctx->slice_param_num, obj_buffer->num_elements);
        free(ctx->slice_param_cache);
        ctx->slice_param_cache = NULL;
        ctx->slice_param_num = 0;
    }

    if (NULL == ctx->slice_param_cache) {
        drv_debug_msg(VIDEO_DEBUG_GENERAL, "Allocate %d VAEncSliceParameterBuffer cache buffers\n", 2 * ctx->slice_param_num);
        ctx->slice_param_num = obj_buffer->num_elements;
        ctx->slice_param_cache = calloc(2 * ctx->slice_param_num, sizeof(VAEncSliceParameterBuffer));
        if (NULL == ctx->slice_param_cache) {
            drv_debug_msg(VIDEO_DEBUG_ERROR, "Run out of memory!\n");
            free(obj_buffer->buffer_data);
            return VA_STATUS_ERROR_ALLOCATION_FAILED;
        }
    }


    for (i = 0; i < obj_buffer->num_elements; i++) {

        unsigned char deblock_idc;

        deblock_idc = pBuffer->slice_flags.bits.disable_deblocking_filter_idc;

        if ((pBuffer->start_row_number == 0) && pBuffer->slice_flags.bits.is_intra) {
            pnw_reset_encoder_params(ctx);
            ctx->BelowParamsBufIdx = (ctx->BelowParamsBufIdx + 1) & 0x1;
        }

        /*The corresponding slice buffer cache*/
        slice_param_idx = (pBuffer->slice_flags.bits.is_intra ? 0 : 1) * ctx->slice_param_num + i;

        if (VAEncSliceParameter_Equal(&ctx->slice_param_cache[slice_param_idx], pBuffer) == 0) {
            /* cache current param parameters */
            memcpy(&ctx->slice_param_cache[slice_param_idx],
                   pBuffer, sizeof(VAEncSliceParameterBuffer));

            /* Setup InParams value*/
            pnw_setup_slice_params(ctx,
                                   pBuffer->start_row_number * 16,
                                   pBuffer->slice_height * 16,
                                   pBuffer->slice_flags.bits.is_intra,
                                   ctx->obj_context->frame_count > 0,
                                   psPicParams->sInParams.SeInitQP);
        }

        pnw__send_encode_slice_params(ctx,
                                      pBuffer->slice_flags.bits.is_intra,
                                      pBuffer->start_row_number * 16,
                                      deblock_idc,
                                      ctx->obj_context->frame_count,
                                      pBuffer->slice_height * 16,
                                      ctx->obj_context->slice_count);

        drv_debug_msg(VIDEO_DEBUG_GENERAL, "Now frame_count/slice_count is %d/%d\n",
                                 ctx->obj_context->frame_count, ctx->obj_context->slice_count);

        ctx->obj_context->slice_count++;
        pBuffer++;

        ASSERT(ctx->obj_context->slice_count < MAX_SLICES_PER_PICTURE);
    }

    free(obj_buffer->buffer_data);
    obj_buffer->buffer_data = NULL;

    return vaStatus;
}


static VAStatus pnw_MPEG4ES_RenderPicture(
    object_context_p obj_context,
    object_buffer_p *buffers,
    int num_buffers)
{
    INIT_CONTEXT_MPEG4ES;
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    int i;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_RenderPicture\n");

    for (i = 0; i < num_buffers; i++) {
        object_buffer_p obj_buffer = buffers[i];

        switch (obj_buffer->type) {
        case VAEncSequenceParameterBufferType:
            drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_RenderPicture got VAEncSequenceParameterBufferType\n");
            vaStatus = pnw__MPEG4ES_process_sequence_param(ctx, obj_buffer);
            DEBUG_FAILURE;
            break;

        case VAEncPictureParameterBufferType:
            drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_RenderPicture got VAEncPictureParameterBufferType\n");
            vaStatus = pnw__MPEG4ES_process_picture_param(ctx, obj_buffer);
            DEBUG_FAILURE;
            break;

        case VAEncSliceParameterBufferType:
            drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_RenderPicture got VAEncSliceParameterBufferType\n");
            vaStatus = pnw__MPEG4ES_process_slice_param(ctx, obj_buffer);
            DEBUG_FAILURE;
            break;
        default:
            vaStatus = VA_STATUS_ERROR_UNKNOWN;
            DEBUG_FAILURE;
        }
    }

    return vaStatus;
}

static VAStatus pnw_MPEG4ES_EndPicture(
    object_context_p obj_context)
{
    INIT_CONTEXT_MPEG4ES;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "pnw_MPEG4ES_EndPicture\n");
    return pnw_EndPicture(ctx);
}


struct format_vtable_s pnw_MPEG4ES_vtable = {
queryConfigAttributes:
    pnw_MPEG4ES_QueryConfigAttributes,
validateConfig:
    pnw_MPEG4ES_ValidateConfig,
createContext:
    pnw_MPEG4ES_CreateContext,
destroyContext:
    pnw_MPEG4ES_DestroyContext,
beginPicture:
    pnw_MPEG4ES_BeginPicture,
renderPicture:
    pnw_MPEG4ES_RenderPicture,
endPicture:
    pnw_MPEG4ES_EndPicture
};