/******************************************************************************
 *
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.
 *
 *****************************************************************************
 * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
*/
/**
*******************************************************************************
* @file
*  ideint_api.c
*
* @brief
*  This file contains the definitions of the core  processing of the de-
* interlacer.
*
* @author
*  Ittiam
*
* @par List of Functions:
*
* @remarks
*  None
*
*******************************************************************************
*/
/*****************************************************************************/
/* File Includes                                                             */
/*****************************************************************************/
/* System include files */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

/* User include files */
#include "icv_datatypes.h"
#include "icv_macros.h"
#include "icv_platform_macros.h"
#include "icv.h"
#include "icv_variance.h"
#include "icv_sad.h"
#include "ideint.h"

#include "ideint_defs.h"
#include "ideint_structs.h"

#include "ideint_utils.h"
#include "ideint_cac.h"
#include "ideint_debug.h"
#include "ideint_function_selector.h"

/**
*******************************************************************************
*
* @brief
*  Return deinterlacer context size
*
* @par   Description
*  Return deinterlacer context size, application will allocate this memory
*  and send it as context to process call
*
* @param[in] None
*
* @returns
* Size of deinterlacer context
*
* @remarks
* None
*
*******************************************************************************
*/
WORD32 ideint_ctxt_size(void)
{
    return sizeof(ctxt_t);
}

/**
*******************************************************************************
*
* @brief
* Deinterlace given fields and produce a frame
*
* @par   Description
*  Deinterlacer function that deinterlaces given fields and produces a frame
*
* @param[in] pv_ctxt
*  Deinterlacer context returned by ideint_create()
*
* @param[in] ps_prv_fld
*  Previous field (can be null, in which case spatial filtering is done
*  unconditionally)
*
* @param[in] ps_cur_fld
*  Current field
*
* @param[in] ps_nxt_fld
*  Next field
*
* @param[in] ps_out_frm
*  Output frame
*
* @param[in] ps_params
*  Parameters
*
* @param[in] start_row
*  Start row
*
* @param[in] num_rows
*  Number of rows to be processed
*
* @returns
*  IDEINT_ERROR_T
*
* @remarks
*
*******************************************************************************
*/
IDEINT_ERROR_T ideint_process(void *pv_ctxt,
                              icv_pic_t *ps_prv_fld,
                              icv_pic_t *ps_cur_fld,
                              icv_pic_t *ps_nxt_fld,
                              icv_pic_t *ps_out_frm,
                              ideint_params_t *ps_params,
                              WORD32 start_row,
                              WORD32 num_rows)
{
    ctxt_t *ps_ctxt;
    WORD32 num_blks_x, num_blks_y;
    WORD32 num_comp;
    WORD32 i, row, col;
    WORD32 rows_remaining;

    if(NULL == pv_ctxt)
        return IDEINT_INVALID_CTXT;

    ps_ctxt = (ctxt_t *)pv_ctxt;

    /* Copy the parameters */
    if(ps_params)
    {
        ps_ctxt->s_params = *ps_params;
    }
    else
    {
        /* Use default params if ps_params is NULL */
        ps_ctxt->s_params.i4_cur_fld_top = 1;
        ps_ctxt->s_params.e_mode = IDEINT_MODE_SPATIAL;
        ps_ctxt->s_params.e_arch = ideint_default_arch();
        ps_ctxt->s_params.e_soc = ICV_SOC_GENERIC;
        ps_ctxt->s_params.i4_disable_weave = 0;
        ps_ctxt->s_params.pf_aligned_alloc = NULL;
        ps_ctxt->s_params.pf_aligned_free = NULL;
    }

    /* Start row has to be multiple of 8 */
    if(start_row & 0x7)
    {
        return IDEINT_START_ROW_UNALIGNED;
    }

    /* Initialize variances */
    ps_ctxt->ai4_vrnc_avg_fb[0] = VAR_AVG_LUMA;
    ps_ctxt->ai4_vrnc_avg_fb[1] = VAR_AVG_CHROMA;
    ps_ctxt->ai4_vrnc_avg_fb[2] = VAR_AVG_CHROMA;

    ideint_init_function_ptr(ps_ctxt);

    rows_remaining = ps_out_frm->ai4_ht[0] - start_row;
    num_rows = MIN(num_rows,
                                        rows_remaining);

    IDEINT_CORRUPT_PIC(ps_out_frm, 0xCD);

    //Weave two fields to get a frame
    if(IDEINT_MODE_WEAVE == ps_ctxt->s_params.e_mode)
    {
        if(0 == ps_ctxt->s_params.i4_disable_weave)
        {
            if(ps_ctxt->s_params.i4_cur_fld_top)
                ideint_weave_pic(ps_cur_fld, ps_nxt_fld, ps_out_frm,
                                 start_row,
                                 num_rows);
            else
                ideint_weave_pic(ps_nxt_fld, ps_cur_fld, ps_out_frm,
                                 start_row,
                                 num_rows);
        }
        return IDEINT_ERROR_NONE;
    }

    num_comp = 3;

    for(i = 0; i < num_comp; i++)
    {
        UWORD8 *pu1_prv, *pu1_out;
        UWORD8 *pu1_top, *pu1_bot, *pu1_dst;
        WORD32 cur_strd, out_strd, dst_strd;

        WORD32 st_thresh;
        WORD32 vrnc_avg_st;
        WORD32 disable_cac_sad;
        WORD32 comp_row_start, comp_row_end;
        num_blks_x = ALIGN8(ps_out_frm->ai4_wd[i]) >> 3;
        num_blks_y = ALIGN8(ps_out_frm->ai4_ht[i]) >> 3;
        comp_row_start = start_row;
        comp_row_end = comp_row_start + num_rows;

        if(i)
        {
            comp_row_start >>= 1;
            comp_row_end >>= 1;
        }

        comp_row_end = MIN(comp_row_end, ps_out_frm->ai4_ht[i]);

        comp_row_start =  ALIGN8(comp_row_start) >> 3;
        comp_row_end  = ALIGN8(comp_row_end) >> 3;
        st_thresh        = ST_THRESH;
        vrnc_avg_st      = VAR_AVG_LUMA;

        if(i)
        {
            st_thresh = ST_THRESH >> 1;
            vrnc_avg_st = VAR_AVG_CHROMA;
        }

        out_strd = ps_out_frm->ai4_strd[i];
        if(ps_ctxt->s_params.i4_cur_fld_top)
        {
            cur_strd = ps_cur_fld->ai4_strd[i];
        }
        else
        {
            cur_strd = ps_nxt_fld->ai4_strd[i];
        }


        disable_cac_sad = 0;
        /* If previous field is not provided, then change to SPATIAL mode */
        if(ps_prv_fld->apu1_buf[i] == NULL)
        {
            disable_cac_sad = 1;
        }

        for(row = comp_row_start; row < comp_row_end; row++)
        {
            pu1_out = ps_out_frm->apu1_buf[i];
            pu1_out += (ps_out_frm->ai4_strd[i] * row << 3);

            pu1_prv = ps_prv_fld->apu1_buf[i];
            pu1_prv += (ps_prv_fld->ai4_strd[i] * row << 2);

            if(ps_ctxt->s_params.i4_cur_fld_top)
            {
                pu1_top = ps_cur_fld->apu1_buf[i];
                pu1_bot = ps_nxt_fld->apu1_buf[i];
            }
            else
            {
                pu1_top = ps_nxt_fld->apu1_buf[i];
                pu1_bot = ps_cur_fld->apu1_buf[i];
            }
            pu1_top += (cur_strd * row << 2);
            pu1_bot += (cur_strd * row << 2);

            for(col = 0; col < num_blks_x; col++)
            {
                WORD32 cac, sad, vrnc;
                WORD32 th_num, th_den;
                UWORD8 au1_dst[BLK_WD * BLK_HT];
                WORD32 blk_wd, blk_ht;
                WORD32 input_boundary;
                cac = 0;
                sad = 0;
                th_den = 0;
                th_num = st_thresh;
                vrnc = 0;

                disable_cac_sad = 0;
                /* If previous field is not provided, then change to SPATIAL mode */
                if(ps_prv_fld->apu1_buf[i] == NULL)
                {
                    disable_cac_sad = 1;
                }
                /* For boundary blocks when input dimensions are not multiple of 8,
                 * then change to spatial mode */
                input_boundary = 0;

                blk_wd = BLK_WD;
                blk_ht = BLK_HT;

                if((((num_blks_x - 1) == col) && (ps_out_frm->ai4_wd[i] & 0x7)) ||
                    (((num_blks_y - 1) == row) && (ps_out_frm->ai4_ht[i] & 0x7)))
                {
                    disable_cac_sad = 1;
                    input_boundary = 1;

                    if(((num_blks_x - 1) == col) && (ps_out_frm->ai4_wd[i] & 0x7))
                        blk_wd = (ps_out_frm->ai4_wd[i] & 0x7);

                    if(((num_blks_y - 1) == row) && (ps_out_frm->ai4_ht[i] & 0x7))
                        blk_ht = (ps_out_frm->ai4_ht[i] & 0x7);

                }

                if(0 == disable_cac_sad)
                {
                    /* Compute SAD */
                    PROFILE_DISABLE_SAD
                    sad = ps_ctxt->pf_sad_8x4(pu1_prv, pu1_bot, cur_strd,
                                              cur_strd,
                                              BLK_WD,
                                              BLK_HT >> 1);
                    /* Compute Variance */
                    PROFILE_DISABLE_VARIANCE
                    vrnc = ps_ctxt->pf_variance_8x4(pu1_top, cur_strd, BLK_WD,
                                                    BLK_HT >> 1);

                    th_num = st_thresh;

                    th_num *= vrnc_avg_st +
                              ((MOD_IDX_ST_NUM * vrnc) >> MOD_IDX_ST_SHIFT);

                    th_den = vrnc +
                             ((MOD_IDX_ST_NUM * vrnc_avg_st) >> MOD_IDX_ST_SHIFT);

                    if((sad * th_den) <= th_num)
                    {
                        /* Calculate Combing Artifact if SAD test fails */
                        PROFILE_DISABLE_CAC
                        cac = ps_ctxt->pf_cac_8x8(pu1_top, pu1_bot, cur_strd, cur_strd);
                    }
                }

                pu1_dst = pu1_out;
                dst_strd = out_strd;

                /* In case boundary blocks are not complete (dimensions non-multiple of 8)
                 * Use intermediate buffer as destination and copy required pixels to output
                 * buffer later
                 */
                if(input_boundary)
                {
                    pu1_dst = au1_dst;
                    dst_strd = BLK_WD;
                    ideint_weave_blk(pu1_top, pu1_bot, pu1_dst, dst_strd,
                                     cur_strd, blk_wd, blk_ht);
                }

                /* Weave the two fields unconditionally */
                if(0 == ps_ctxt->s_params.i4_disable_weave)
                {
                    ideint_weave_blk(pu1_top, pu1_bot, pu1_dst, dst_strd,
                                     cur_strd, blk_wd, blk_ht);
                }

                if(disable_cac_sad || cac || (sad * th_den > th_num))
                {
                    /* Pad the input fields in an intermediate buffer if required */
                    if((0 == row) || (0 == col) ||
                       ((num_blks_x - 1) == col) || ((num_blks_y - 1) == row))
                    {
                        UWORD8 *pu1_dst_top;
                        UWORD8 au1_pad[(BLK_HT + 4) * (BLK_WD + 4)];

                        ideint_pad_blk(pu1_top, pu1_bot, au1_pad, cur_strd, row,
                                       col, num_blks_y, num_blks_x, blk_wd, blk_ht);

                        pu1_dst_top = au1_pad + 2 * (BLK_WD + 4) + 2;

                        PROFILE_DISABLE_SPATIAL
                        ps_ctxt->pf_spatial_filter(pu1_dst_top, pu1_dst + dst_strd,
                                                   (BLK_WD + 4) * 2,
                                                   dst_strd * 2);
                    }
                    else
                    {
                        PROFILE_DISABLE_SPATIAL
                        ps_ctxt->pf_spatial_filter(pu1_top, pu1_dst + dst_strd,
                                                   cur_strd, dst_strd * 2);

                    }
                }

                /* copy required pixels to output buffer for boundary blocks
                 * when dimensions are not multiple of 8
                 */
                if(input_boundary)
                {
                    WORD32 j;

                    for(j = 0; j < blk_ht; j++)
                    {
                        memcpy(pu1_out + j * out_strd, au1_dst + j * BLK_WD, blk_wd);
                    }
                }
                pu1_prv += 8;
                pu1_top += 8;
                pu1_bot += 8;
                pu1_out += 8;
            }
        }
    }
    return IDEINT_ERROR_NONE;
}