/*
 * Copyright © 2010 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, 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:
 *    Zhou Chang <chang.zhou@intel.com>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "intel_batchbuffer.h"
#include "intel_driver.h"

#include "i965_defines.h"
#include "i965_drv_video.h"
#include "i965_encoder.h"
#include "gen6_vme.h"
#include "gen6_mfc.h"

extern Bool gen6_mfc_context_init(VADriverContextP ctx, struct intel_encoder_context *encoder_context);
extern Bool gen6_vme_context_init(VADriverContextP ctx, struct intel_encoder_context *encoder_context);
extern Bool gen7_mfc_context_init(VADriverContextP ctx, struct intel_encoder_context *encoder_context);

static VAStatus
intel_encoder_check_yuv_surface(VADriverContextP ctx,
                                VAProfile profile,
                                struct encode_state *encode_state,
                                struct intel_encoder_context *encoder_context)
{
    struct i965_driver_data *i965 = i965_driver_data(ctx);
    struct i965_surface src_surface, dst_surface;
    struct object_surface *obj_surface;
    VAStatus status;
    VARectangle rect;

    /* releae the temporary surface */
    if (encoder_context->is_tmp_id) {
        i965_DestroySurfaces(ctx, &encoder_context->input_yuv_surface, 1);
        encode_state->input_yuv_object = NULL;
    }

    encoder_context->is_tmp_id = 0;
    obj_surface = SURFACE(encode_state->current_render_target);
    assert(obj_surface && obj_surface->bo);

    if (!obj_surface || !obj_surface->bo)
        return VA_STATUS_ERROR_INVALID_PARAMETER;

    if (obj_surface->fourcc == VA_FOURCC_NV12) {
        unsigned int tiling = 0, swizzle = 0;

        dri_bo_get_tiling(obj_surface->bo, &tiling, &swizzle);

        if (tiling == I915_TILING_Y) {
            encoder_context->input_yuv_surface = encode_state->current_render_target;
            encode_state->input_yuv_object = obj_surface;
            return VA_STATUS_SUCCESS;
        }
    }

    rect.x = 0;
    rect.y = 0;
    rect.width = obj_surface->orig_width;
    rect.height = obj_surface->orig_height;
    
    src_surface.base = (struct object_base *)obj_surface;
    src_surface.type = I965_SURFACE_TYPE_SURFACE;
    src_surface.flags = I965_SURFACE_FLAG_FRAME;
    
    status = i965_CreateSurfaces(ctx,
                                 obj_surface->orig_width,
                                 obj_surface->orig_height,
                                 VA_RT_FORMAT_YUV420,
                                 1,
                                 &encoder_context->input_yuv_surface);
    assert(status == VA_STATUS_SUCCESS);

    if (status != VA_STATUS_SUCCESS)
        return status;

    obj_surface = SURFACE(encoder_context->input_yuv_surface);
    encode_state->input_yuv_object = obj_surface;
    assert(obj_surface);
    i965_check_alloc_surface_bo(ctx, obj_surface, 1, VA_FOURCC_NV12, SUBSAMPLE_YUV420);
    
    dst_surface.base = (struct object_base *)obj_surface;
    dst_surface.type = I965_SURFACE_TYPE_SURFACE;
    dst_surface.flags = I965_SURFACE_FLAG_FRAME;

    status = i965_image_processing(ctx,
                                   &src_surface,
                                   &rect,
                                   &dst_surface,
                                   &rect);
    assert(status == VA_STATUS_SUCCESS);

    encoder_context->is_tmp_id = 1;

    return VA_STATUS_SUCCESS;
}

static VAStatus
intel_encoder_check_misc_parameter(VADriverContextP ctx,
                                  struct encode_state *encode_state,
                                  struct intel_encoder_context *encoder_context)
{

    if (encode_state->misc_param[VAEncMiscParameterTypeQualityLevel] &&
        encode_state->misc_param[VAEncMiscParameterTypeQualityLevel]->buffer) {
        VAEncMiscParameterBuffer* pMiscParam = (VAEncMiscParameterBuffer*)encode_state->misc_param[VAEncMiscParameterTypeQualityLevel]->buffer;
        VAEncMiscParameterBufferQualityLevel* param_quality_level = (VAEncMiscParameterBufferQualityLevel*)pMiscParam->data;
        encoder_context->quality_level = param_quality_level->quality_level;

        if (encoder_context->quality_level == 0)
            encoder_context->quality_level = ENCODER_DEFAULT_QUALITY;
        else if (encoder_context->quality_level > encoder_context->quality_range)
            goto error;
   }

    return VA_STATUS_SUCCESS;

error:
    return VA_STATUS_ERROR_INVALID_PARAMETER;
}

static VAStatus
intel_encoder_check_avc_parameter(VADriverContextP ctx,
                                  struct encode_state *encode_state,
                                  struct intel_encoder_context *encoder_context)
{
    struct i965_driver_data *i965 = i965_driver_data(ctx);
    struct object_surface *obj_surface;	
    struct object_buffer *obj_buffer;
    VAEncPictureParameterBufferH264 *pic_param = (VAEncPictureParameterBufferH264 *)encode_state->pic_param_ext->buffer;
    int i;

    assert(!(pic_param->CurrPic.flags & VA_PICTURE_H264_INVALID));

    if (pic_param->CurrPic.flags & VA_PICTURE_H264_INVALID)
        goto error;

    obj_surface = SURFACE(pic_param->CurrPic.picture_id);
    assert(obj_surface); /* It is possible the store buffer isn't allocated yet */
    
    if (!obj_surface)
        goto error;

    encode_state->reconstructed_object = obj_surface;
    obj_buffer = BUFFER(pic_param->coded_buf);
    assert(obj_buffer && obj_buffer->buffer_store && obj_buffer->buffer_store->bo);

    if (!obj_buffer || !obj_buffer->buffer_store || !obj_buffer->buffer_store->bo)
        goto error;

    encode_state->coded_buf_object = obj_buffer;

    for (i = 0; i < 16; i++) {
        if (pic_param->ReferenceFrames[i].flags & VA_PICTURE_H264_INVALID ||
            pic_param->ReferenceFrames[i].picture_id == VA_INVALID_SURFACE)
            break;
        else {
            obj_surface = SURFACE(pic_param->ReferenceFrames[i].picture_id);
            assert(obj_surface);

            if (!obj_surface)
                goto error;

            if (obj_surface->bo)
                encode_state->reference_objects[i] = obj_surface;
            else
                encode_state->reference_objects[i] = NULL; /* FIXME: Warning or Error ??? */
        }
    }

    for ( ; i < 16; i++)
        encode_state->reference_objects[i] = NULL;
    
    return VA_STATUS_SUCCESS;

error:
    return VA_STATUS_ERROR_INVALID_PARAMETER;
}

static VAStatus
intel_encoder_check_mpeg2_parameter(VADriverContextP ctx,
                                    struct encode_state *encode_state,
                                    struct intel_encoder_context *encoder_context)
{
    struct i965_driver_data *i965 = i965_driver_data(ctx);
    VAEncPictureParameterBufferMPEG2 *pic_param = (VAEncPictureParameterBufferMPEG2 *)encode_state->pic_param_ext->buffer;
    struct object_surface *obj_surface;	
    struct object_buffer *obj_buffer;
    int i = 0;
    
    obj_surface = SURFACE(pic_param->reconstructed_picture);
    assert(obj_surface); /* It is possible the store buffer isn't allocated yet */
    
    if (!obj_surface)
        goto error;
    
    encode_state->reconstructed_object = obj_surface;    
    obj_buffer = BUFFER(pic_param->coded_buf);
    assert(obj_buffer && obj_buffer->buffer_store && obj_buffer->buffer_store->bo);

    if (!obj_buffer || !obj_buffer->buffer_store || !obj_buffer->buffer_store->bo)
        goto error;

    encode_state->coded_buf_object = obj_buffer;

    if (pic_param->picture_type == VAEncPictureTypeIntra) {
    } else if (pic_param->picture_type == VAEncPictureTypePredictive) {
        assert(pic_param->forward_reference_picture != VA_INVALID_SURFACE);
        obj_surface = SURFACE(pic_param->forward_reference_picture);
        assert(obj_surface && obj_surface->bo);

        if (!obj_surface || !obj_surface->bo)
            goto error;

        encode_state->reference_objects[i++] = obj_surface;
    } else if (pic_param->picture_type == VAEncPictureTypeBidirectional) {
        assert(pic_param->forward_reference_picture != VA_INVALID_SURFACE);
        obj_surface = SURFACE(pic_param->forward_reference_picture);
        assert(obj_surface && obj_surface->bo);

        if (!obj_surface || !obj_surface->bo)
            goto error;

        encode_state->reference_objects[i++] = obj_surface;

        assert(pic_param->backward_reference_picture != VA_INVALID_SURFACE);
        obj_surface = SURFACE(pic_param->backward_reference_picture);
        assert(obj_surface && obj_surface->bo);

        if (!obj_surface || !obj_surface->bo)
            goto error;

        encode_state->reference_objects[i++] = obj_surface;
    } else 
        goto error;

    for ( ; i < 16; i++)
        encode_state->reference_objects[i] = NULL;

    return VA_STATUS_SUCCESS;

error:
    return VA_STATUS_ERROR_INVALID_PARAMETER;
}

static VAStatus
intel_encoder_sanity_check_input(VADriverContextP ctx,
                                 VAProfile profile,
                                 struct encode_state *encode_state,
                                 struct intel_encoder_context *encoder_context)
{
    VAStatus vaStatus;

    switch (profile) {
    case VAProfileH264ConstrainedBaseline:
    case VAProfileH264Main:
    case VAProfileH264High:
    case VAProfileH264MultiviewHigh:
    case VAProfileH264StereoHigh:
        vaStatus = intel_encoder_check_avc_parameter(ctx, encode_state, encoder_context);
        break;

    case VAProfileMPEG2Simple:
    case VAProfileMPEG2Main:
        vaStatus = intel_encoder_check_mpeg2_parameter(ctx, encode_state, encoder_context);
        break;

    default:
        vaStatus = VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
        break;
    }

    if (vaStatus != VA_STATUS_SUCCESS)
        goto out;

    vaStatus = intel_encoder_check_yuv_surface(ctx, profile, encode_state, encoder_context);

    if (vaStatus == VA_STATUS_SUCCESS)
        vaStatus = intel_encoder_check_misc_parameter(ctx, encode_state, encoder_context);

out:    
    return vaStatus;
}
 
static VAStatus
intel_encoder_end_picture(VADriverContextP ctx, 
                          VAProfile profile, 
                          union codec_state *codec_state,
                          struct hw_context *hw_context)
{
    struct intel_encoder_context *encoder_context = (struct intel_encoder_context *)hw_context;
    struct encode_state *encode_state = &codec_state->encode;
    VAStatus vaStatus;

    vaStatus = intel_encoder_sanity_check_input(ctx, profile, encode_state, encoder_context);

    if (vaStatus != VA_STATUS_SUCCESS)
        return vaStatus;

    encoder_context->mfc_brc_prepare(encode_state, encoder_context);

    vaStatus = encoder_context->vme_pipeline(ctx, profile, encode_state, encoder_context);

    if (vaStatus == VA_STATUS_SUCCESS)
        encoder_context->mfc_pipeline(ctx, profile, encode_state, encoder_context);
    return VA_STATUS_SUCCESS;
}

static void
intel_encoder_context_destroy(void *hw_context)
{
    struct intel_encoder_context *encoder_context = (struct intel_encoder_context *)hw_context;

    encoder_context->mfc_context_destroy(encoder_context->mfc_context);
    encoder_context->vme_context_destroy(encoder_context->vme_context);
    intel_batchbuffer_free(encoder_context->base.batch);
    free(encoder_context);
}

typedef Bool (* hw_init_func)(VADriverContextP, struct intel_encoder_context *);

static struct hw_context *
intel_enc_hw_context_init(VADriverContextP ctx,
                          struct object_config *obj_config,
                          hw_init_func vme_context_init,
                          hw_init_func mfc_context_init)
{
    struct intel_driver_data *intel = intel_driver_data(ctx);
    struct intel_encoder_context *encoder_context = calloc(1, sizeof(struct intel_encoder_context));
    int i;

    encoder_context->base.destroy = intel_encoder_context_destroy;
    encoder_context->base.run = intel_encoder_end_picture;
    encoder_context->base.batch = intel_batchbuffer_new(intel, I915_EXEC_RENDER, 0);
    encoder_context->input_yuv_surface = VA_INVALID_SURFACE;
    encoder_context->is_tmp_id = 0;
    encoder_context->rate_control_mode = VA_RC_NONE;
    encoder_context->quality_level = ENCODER_DEFAULT_QUALITY;
    encoder_context->quality_range = 1;

    switch (obj_config->profile) {
    case VAProfileMPEG2Simple:
    case VAProfileMPEG2Main:
        encoder_context->codec = CODEC_MPEG2;
        break;
        
    case VAProfileH264ConstrainedBaseline:
    case VAProfileH264Main:
    case VAProfileH264High:
        encoder_context->codec = CODEC_H264;
        encoder_context->quality_range = ENCODER_QUALITY_RANGE;
        break;

    case VAProfileH264StereoHigh:
    case VAProfileH264MultiviewHigh:
        encoder_context->codec = CODEC_H264_MVC;
        break;

    default:
        /* Never get here */
        assert(0);
        break;
    }

    for (i = 0; i < obj_config->num_attribs; i++) {
        if (obj_config->attrib_list[i].type == VAConfigAttribRateControl) {
            encoder_context->rate_control_mode = obj_config->attrib_list[i].value;

            if (encoder_context->codec == CODEC_MPEG2 &&
                encoder_context->rate_control_mode & VA_RC_CBR) {
                WARN_ONCE("Don't support CBR for MPEG-2 encoding\n");
                encoder_context->rate_control_mode &= ~VA_RC_CBR;
            }

            break;
        }
    }

    vme_context_init(ctx, encoder_context);
    assert(encoder_context->vme_context);
    assert(encoder_context->vme_context_destroy);
    assert(encoder_context->vme_pipeline);

    mfc_context_init(ctx, encoder_context);
    assert(encoder_context->mfc_context);
    assert(encoder_context->mfc_context_destroy);
    assert(encoder_context->mfc_pipeline);

    return (struct hw_context *)encoder_context;
}

struct hw_context *
gen6_enc_hw_context_init(VADriverContextP ctx, struct object_config *obj_config)
{
    return intel_enc_hw_context_init(ctx, obj_config, gen6_vme_context_init, gen6_mfc_context_init);
}

struct hw_context *
gen7_enc_hw_context_init(VADriverContextP ctx, struct object_config *obj_config)
{

    return intel_enc_hw_context_init(ctx, obj_config, gen7_vme_context_init, gen7_mfc_context_init);
}

struct hw_context *
gen75_enc_hw_context_init(VADriverContextP ctx, struct object_config *obj_config)
{
    return intel_enc_hw_context_init(ctx, obj_config, gen75_vme_context_init, gen75_mfc_context_init);
}

struct hw_context *
gen8_enc_hw_context_init(VADriverContextP ctx, struct object_config *obj_config)
{
    return intel_enc_hw_context_init(ctx, obj_config, gen8_vme_context_init, gen8_mfc_context_init);
}

