/*
 *  Copyright (c) 2010 The VP8 project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license 
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may 
 *  be found in the AUTHORS file in the root of the source tree.
 */


#include "vpx_ports/config.h"
#include "encodemb.h"
#include "reconinter.h"
#include "quantize.h"
#include "invtrans.h"
#include "recon.h"
#include "reconintra.h"
#include "dct.h"
#include "vpx_mem/vpx_mem.h"

#if CONFIG_RUNTIME_CPU_DETECT
#define IF_RTCD(x) (x)
#else
#define IF_RTCD(x) NULL
#endif
void vp8_subtract_b_c(BLOCK *be, BLOCKD *bd, int pitch)
{
    unsigned char *src_ptr = (*(be->base_src) + be->src);
    short *diff_ptr = be->src_diff;
    unsigned char *pred_ptr = bd->predictor;
    int src_stride = be->src_stride;

    int r, c;

    for (r = 0; r < 4; r++)
    {
        for (c = 0; c < 4; c++)
        {
            diff_ptr[c] = src_ptr[c] - pred_ptr[c];
        }

        diff_ptr += pitch;
        pred_ptr += pitch;
        src_ptr  += src_stride;
    }
}

void vp8_subtract_mbuv_c(short *diff, unsigned char *usrc, unsigned char *vsrc, unsigned char *pred, int stride)
{
    short *udiff = diff + 256;
    short *vdiff = diff + 320;
    unsigned char *upred = pred + 256;
    unsigned char *vpred = pred + 320;

    int r, c;

    for (r = 0; r < 8; r++)
    {
        for (c = 0; c < 8; c++)
        {
            udiff[c] = usrc[c] - upred[c];
        }

        udiff += 8;
        upred += 8;
        usrc  += stride;
    }

    for (r = 0; r < 8; r++)
    {
        for (c = 0; c < 8; c++)
        {
            vdiff[c] = vsrc[c] - vpred[c];
        }

        vdiff += 8;
        vpred += 8;
        vsrc  += stride;
    }
}

void vp8_subtract_mby_c(short *diff, unsigned char *src, unsigned char *pred, int stride)
{
    int r, c;

    for (r = 0; r < 16; r++)
    {
        for (c = 0; c < 16; c++)
        {
            diff[c] = src[c] - pred[c];
        }

        diff += 16;
        pred += 16;
        src  += stride;
    }
}

static void vp8_subtract_mb(const VP8_ENCODER_RTCD *rtcd, MACROBLOCK *x)
{
    ENCODEMB_INVOKE(&rtcd->encodemb, submby)(x->src_diff, x->src.y_buffer, x->e_mbd.predictor, x->src.y_stride);
    ENCODEMB_INVOKE(&rtcd->encodemb, submbuv)(x->src_diff, x->src.u_buffer, x->src.v_buffer, x->e_mbd.predictor, x->src.uv_stride);
}

void vp8_build_dcblock(MACROBLOCK *x)
{
    short *src_diff_ptr = &x->src_diff[384];
    int i;

    for (i = 0; i < 16; i++)
    {
        src_diff_ptr[i] = x->coeff[i * 16];
    }
}

void vp8_transform_mbuv(MACROBLOCK *x)
{
    int i;

    for (i = 16; i < 24; i += 2)
    {
        x->vp8_short_fdct8x4(&x->block[i].src_diff[0], &x->block[i].coeff[0], 16);
    }
}

void vp8_transform_mbuvrd(MACROBLOCK *x)
{
    int i;

    for (i = 16; i < 24; i += 2)
    {
        x->short_fdct8x4rd(&x->block[i].src_diff[0], &x->block[i].coeff[0], 16);
    }
}

void vp8_transform_intra_mby(MACROBLOCK *x)
{
    int i;

    for (i = 0; i < 16; i += 2)
    {
        x->vp8_short_fdct8x4(&x->block[i].src_diff[0], &x->block[i].coeff[0], 32);
    }

    // build dc block from 16 y dc values
    vp8_build_dcblock(x);

    // do 2nd order transform on the dc block
    x->short_walsh4x4(&x->block[24].src_diff[0], &x->block[24].coeff[0], 8);

}

void vp8_transform_intra_mbyrd(MACROBLOCK *x)
{
    int i;

    for (i = 0; i < 16; i += 2)
    {
        x->short_fdct8x4rd(&x->block[i].src_diff[0], &x->block[i].coeff[0], 32);
    }

    // build dc block from 16 y dc values
    vp8_build_dcblock(x);

    // do 2nd order transform on the dc block
    x->short_walsh4x4(&x->block[24].src_diff[0], &x->block[24].coeff[0], 8);
}

void vp8_transform_mb(MACROBLOCK *x)
{
    int i;

    for (i = 0; i < 16; i += 2)
    {
        x->vp8_short_fdct8x4(&x->block[i].src_diff[0], &x->block[i].coeff[0], 32);
    }

    // build dc block from 16 y dc values
    if (x->e_mbd.mbmi.mode != SPLITMV)
        vp8_build_dcblock(x);

    for (i = 16; i < 24; i += 2)
    {
        x->vp8_short_fdct8x4(&x->block[i].src_diff[0], &x->block[i].coeff[0], 16);
    }

    // do 2nd order transform on the dc block
    if (x->e_mbd.mbmi.mode != SPLITMV)
        x->short_walsh4x4(&x->block[24].src_diff[0], &x->block[24].coeff[0], 8);

}

void vp8_transform_mby(MACROBLOCK *x)
{
    int i;

    for (i = 0; i < 16; i += 2)
    {
        x->vp8_short_fdct8x4(&x->block[i].src_diff[0], &x->block[i].coeff[0], 32);
    }

    // build dc block from 16 y dc values
    if (x->e_mbd.mbmi.mode != SPLITMV)
    {
        vp8_build_dcblock(x);
        x->short_walsh4x4(&x->block[24].src_diff[0], &x->block[24].coeff[0], 8);
    }
}

void vp8_transform_mbrd(MACROBLOCK *x)
{
    int i;

    for (i = 0; i < 16; i += 2)
    {
        x->short_fdct8x4rd(&x->block[i].src_diff[0], &x->block[i].coeff[0], 32);
    }

    // build dc block from 16 y dc values
    if (x->e_mbd.mbmi.mode != SPLITMV)
        vp8_build_dcblock(x);

    for (i = 16; i < 24; i += 2)
    {
        x->short_fdct8x4rd(&x->block[i].src_diff[0], &x->block[i].coeff[0], 16);
    }

    // do 2nd order transform on the dc block
    if (x->e_mbd.mbmi.mode != SPLITMV)
        x->short_walsh4x4(&x->block[24].src_diff[0], &x->block[24].coeff[0], 8);
}

void vp8_stuff_inter16x16(MACROBLOCK *x)
{
    vp8_build_inter_predictors_mb_s(&x->e_mbd);
    /*
        // recon = copy from predictors to destination
        {
            BLOCKD *b = &x->e_mbd.block[0];
            unsigned char *pred_ptr = b->predictor;
            unsigned char *dst_ptr = *(b->base_dst) + b->dst;
            int stride = b->dst_stride;

            int i;
            for(i=0;i<16;i++)
                vpx_memcpy(dst_ptr+i*stride,pred_ptr+16*i,16);

            b = &x->e_mbd.block[16];
            pred_ptr = b->predictor;
            dst_ptr = *(b->base_dst) + b->dst;
            stride = b->dst_stride;

            for(i=0;i<8;i++)
                vpx_memcpy(dst_ptr+i*stride,pred_ptr+8*i,8);

            b = &x->e_mbd.block[20];
            pred_ptr = b->predictor;
            dst_ptr = *(b->base_dst) + b->dst;
            stride = b->dst_stride;

            for(i=0;i<8;i++)
                vpx_memcpy(dst_ptr+i*stride,pred_ptr+8*i,8);
        }
    */
}

#if !(CONFIG_REALTIME_ONLY)
extern const TOKENEXTRA vp8_dct_value_tokens[DCT_MAX_VALUE*2];
extern const TOKENEXTRA *vp8_dct_value_tokens_ptr;
extern int vp8_dct_value_cost[DCT_MAX_VALUE*2];
extern int *vp8_dct_value_cost_ptr;

static int cost_coeffs(MACROBLOCK *mb, BLOCKD *b, int type, ENTROPY_CONTEXT *a, ENTROPY_CONTEXT *l)
{
    int c = !type;              /* start at coef 0, unless Y with Y2 */
    int eob = b->eob;
    int pt ;    /* surrounding block/prev coef predictor */
    int cost = 0;
    short *qcoeff_ptr = b->qcoeff;

    VP8_COMBINEENTROPYCONTEXTS(pt, *a, *l);

# define QC( I)  ( qcoeff_ptr [vp8_default_zig_zag1d[I]] )

    for (; c < eob; c++)
    {
        int v = QC(c);
        int t = vp8_dct_value_tokens_ptr[v].Token;
        cost += mb->token_costs [type] [vp8_coef_bands[c]] [pt] [t];
        cost += vp8_dct_value_cost_ptr[v];
        pt = vp8_prev_token_class[t];
    }

# undef QC

    if (c < 16)
        cost += mb->token_costs [type] [vp8_coef_bands[c]] [pt] [DCT_EOB_TOKEN];

    return cost;
}

static int mbycost_coeffs(MACROBLOCK *mb)
{
    int cost = 0;
    int b;
    TEMP_CONTEXT t;
    int type = 0;

    MACROBLOCKD *x = &mb->e_mbd;

    vp8_setup_temp_context(&t, x->above_context[Y1CONTEXT], x->left_context[Y1CONTEXT], 4);

    if (x->mbmi.mode == SPLITMV)
        type = 3;

    for (b = 0; b < 16; b++)
        cost += cost_coeffs(mb, x->block + b, type,
                            t.a + vp8_block2above[b], t.l + vp8_block2left[b]);

    return cost;
}

#define RDFUNC(RM,DM,R,D,target_rd) ( ((128+(R)*(RM)) >> 8) + (DM)*(D) )

void vp8_optimize_b(MACROBLOCK *x, int i, int type, ENTROPY_CONTEXT *a, ENTROPY_CONTEXT *l, const VP8_ENCODER_RTCD *rtcd)
{
    BLOCK *b = &x->block[i];
    BLOCKD *bd = &x->e_mbd.block[i];
    short *dequant_ptr = &bd->dequant[0][0];
    int nzpos[16] = {0};
    short saved_qcoefs[16];
    short saved_dqcoefs[16];
    int baserate, baseerror, baserd;
    int rate, error, thisrd;
    int k;
    int nzcoefcount = 0;
    int nc, bestnc = 0;
    int besteob;

    // count potential coefficient to be optimized
    for (k = !type; k < 16; k++)
    {
        int qcoef = abs(bd->qcoeff[k]);
        int coef = abs(b->coeff[k]);
        int dq   = dequant_ptr[k];

        if (qcoef && (qcoef * dq > coef) && (qcoef * dq < coef + dq))
        {
            nzpos[nzcoefcount] = k;
            nzcoefcount++;
        }
    }

    // if nothing here, do nothing for this block.
    if (!nzcoefcount)
    {
        *a = *l = (bd->eob != !type);
        return;
    }

    // save a copy of quantized coefficients
    vpx_memcpy(saved_qcoefs, bd->qcoeff, 32);
    vpx_memcpy(saved_dqcoefs, bd->dqcoeff, 32);

    besteob   = bd->eob;
    baserate  = cost_coeffs(x, bd, type, a, l);
    baseerror = ENCODEMB_INVOKE(&rtcd->encodemb, berr)(b->coeff, bd->dqcoeff) >> 2;
    baserd    = RDFUNC(x->rdmult, x->rddiv, baserate, baseerror, 100);

    for (nc = 1; nc < (1 << nzcoefcount); nc++)
    {
        //reset coefficients
        vpx_memcpy(bd->qcoeff,  saved_qcoefs,  32);
        vpx_memcpy(bd->dqcoeff, saved_dqcoefs, 32);

        for (k = 0; k < nzcoefcount; k++)
        {
            int pos = nzpos[k];

            if ((nc & (1 << k)))
            {
                int cur_qcoef = bd->qcoeff[pos];

                if (cur_qcoef < 0)
                {
                    bd->qcoeff[pos]++;
                    bd->dqcoeff[pos] = bd->qcoeff[pos] * dequant_ptr[pos];
                }
                else
                {
                    bd->qcoeff[pos]--;
                    bd->dqcoeff[pos] = bd->qcoeff[pos] * dequant_ptr[pos];
                }
            }
        }

        {
            int eob = -1;
            int rc;
            int m;

            for (m = 0; m < 16; m++)
            {
                rc   = vp8_default_zig_zag1d[m];

                if (bd->qcoeff[rc])
                    eob = m;
            }

            bd->eob = eob + 1;
        }

        rate  = cost_coeffs(x, bd, type, a, l);
        error = ENCODEMB_INVOKE(&rtcd->encodemb, berr)(b->coeff, bd->dqcoeff) >> 2;
        thisrd = RDFUNC(x->rdmult, x->rddiv, rate, error, 100);

        if (thisrd < baserd)
        {
            baserd = thisrd;
            bestnc = nc;
            besteob = bd->eob;
        }
    }

    //reset coefficients
    vpx_memcpy(bd->qcoeff,  saved_qcoefs, 32);
    vpx_memcpy(bd->dqcoeff, saved_dqcoefs, 32);

    if (bestnc)
    {
        for (k = 0; k < nzcoefcount; k++)
        {
            int pos = nzpos[k];

            if (bestnc & (1 << k))
            {
                int cur_qcoef = bd->qcoeff[pos];

                if (cur_qcoef < 0)
                {
                    bd->qcoeff[pos]++;
                    bd->dqcoeff[pos] = bd->qcoeff[pos] * dequant_ptr[pos];
                }
                else
                {
                    bd->qcoeff[pos]--;
                    bd->dqcoeff[pos] = bd->qcoeff[pos] * dequant_ptr[pos];
                }
            }
        }

#if 0
        {
            int eob = -1;
            int rc;
            int m;

            for (m = 0; m < 16; m++)
            {
                rc   = vp8_default_zig_zag1d[m];

                if (bd->qcoeff[rc])
                    eob = m;
            }

            bd->eob = eob + 1;
        }
#endif
    }

#if 1
    bd->eob = besteob;
#endif
#if 0
    {
        int eob = -1;
        int rc;
        int m;

        for (m = 0; m < 16; m++)
        {
            rc   = vp8_default_zig_zag1d[m];

            if (bd->qcoeff[rc])
                eob = m;
        }

        bd->eob = eob + 1;
    }

#endif
    *a = *l = (bd->eob != !type);
    return;
}


void vp8_optimize_y2b(MACROBLOCK *x, int i, int type, ENTROPY_CONTEXT *a, ENTROPY_CONTEXT *l, const VP8_ENCODER_RTCD *rtcd)
{

    BLOCK *b = &x->block[i];
    BLOCKD *bd = &x->e_mbd.block[i];
    short *dequant_ptr = &bd->dequant[0][0];

    int baserate, baseerror, baserd;
    int rate, error, thisrd;
    int k;

    if (bd->eob == 0)
        return;

    baserate  = cost_coeffs(x, bd, type, a, l);
    baseerror = ENCODEMB_INVOKE(&rtcd->encodemb, berr)(b->coeff, bd->dqcoeff) >> 4;
    baserd = RDFUNC(x->rdmult, x->rddiv, baserate, baseerror, 100);

    for (k = 0; k < 16; k++)
    {
        int cur_qcoef = bd->qcoeff[k];

        if (!cur_qcoef)
            continue;

        if (cur_qcoef < 0)
        {
            bd->qcoeff[k]++;
            bd->dqcoeff[k] = bd->qcoeff[k] * dequant_ptr[k];
        }
        else
        {
            bd->qcoeff[k]--;
            bd->dqcoeff[k] = bd->qcoeff[k] * dequant_ptr[k];
        }

        if (bd->qcoeff[k] == 0)
        {
            int eob = -1;
            int rc;
            int l;

            for (l = 0; l < 16; l++)
            {
                rc   = vp8_default_zig_zag1d[l];

                if (bd->qcoeff[rc])
                    eob = l;
            }

            bd->eob = eob + 1;
        }

        rate  =   cost_coeffs(x, bd, type, a, l);
        error = ENCODEMB_INVOKE(&rtcd->encodemb, berr)(b->coeff, bd->dqcoeff) >> 4;
        thisrd = RDFUNC(x->rdmult, x->rddiv, rate, error, 100);

        if (thisrd > baserd)
        {
            bd->qcoeff[k] = cur_qcoef;
            bd->dqcoeff[k] = cur_qcoef * dequant_ptr[k];
        }
        else
        {
            baserd = thisrd;
        }

    }

    {
        int eob = -1;
        int rc;

        for (k = 0; k < 16; k++)
        {
            rc   = vp8_default_zig_zag1d[k];

            if (bd->qcoeff[rc])
                eob = k;
        }

        bd->eob = eob + 1;
    }

    return;
}


void vp8_optimize_mb(MACROBLOCK *x, const VP8_ENCODER_RTCD *rtcd)
{
    int b;
    TEMP_CONTEXT t, t2;
    int type = 0;

    vp8_setup_temp_context(&t, x->e_mbd.above_context[Y1CONTEXT], x->e_mbd.left_context[Y1CONTEXT], 4);

    if (x->e_mbd.mbmi.mode == SPLITMV || x->e_mbd.mbmi.mode == B_PRED)
        type = 3;

    for (b = 0; b < 16; b++)
    {
        //vp8_optimize_bplus(x, b, type, t.a + vp8_block2above[b], t.l + vp8_block2left[b]);
        vp8_optimize_b(x, b, type, t.a + vp8_block2above[b], t.l + vp8_block2left[b], rtcd);
    }

    vp8_setup_temp_context(&t, x->e_mbd.above_context[UCONTEXT], x->e_mbd.left_context[UCONTEXT], 2);
    vp8_setup_temp_context(&t2, x->e_mbd.above_context[VCONTEXT], x->e_mbd.left_context[VCONTEXT], 2);

    for (b = 16; b < 20; b++)
    {
        //vp8_optimize_bplus(x, b, vp8_block2type[b], t.a + vp8_block2above[b], t.l + vp8_block2left[b]);
        vp8_optimize_b(x, b, vp8_block2type[b], t.a + vp8_block2above[b], t.l + vp8_block2left[b], rtcd);
    }

    for (b = 20; b < 24; b++)
    {
        //vp8_optimize_bplus(x, b, vp8_block2type[b], t2.a + vp8_block2above[b], t2.l + vp8_block2left[b]);
        vp8_optimize_b(x, b, vp8_block2type[b], t2.a + vp8_block2above[b], t2.l + vp8_block2left[b], rtcd);
    }
}



static void vp8_find_mb_skip_coef(MACROBLOCK *x)
{
    int i;

    x->e_mbd.mbmi.mb_skip_coeff = 1;

    if (x->e_mbd.mbmi.mode != B_PRED && x->e_mbd.mbmi.mode != SPLITMV)
    {
        for (i = 0; i < 16; i++)
        {
            x->e_mbd.mbmi.mb_skip_coeff &= (x->e_mbd.block[i].eob < 2);
        }

        for (i = 16; i < 25; i++)
        {
            x->e_mbd.mbmi.mb_skip_coeff &= (!x->e_mbd.block[i].eob);
        }
    }
    else
    {
        for (i = 0; i < 24; i++)
        {
            x->e_mbd.mbmi.mb_skip_coeff &= (!x->e_mbd.block[i].eob);
        }
    }
}


void vp8_optimize_mby(MACROBLOCK *x, const VP8_ENCODER_RTCD *rtcd)
{
    int b;
    TEMP_CONTEXT t;
    int type = 0;

    if (!x->e_mbd.above_context[Y1CONTEXT])
        return;

    if (!x->e_mbd.left_context[Y1CONTEXT])
        return;

    vp8_setup_temp_context(&t, x->e_mbd.above_context[Y1CONTEXT], x->e_mbd.left_context[Y1CONTEXT], 4);

    if (x->e_mbd.mbmi.mode == SPLITMV || x->e_mbd.mbmi.mode == B_PRED)
        type = 3;

    for (b = 0; b < 16; b++)
    {
        vp8_optimize_b(x, b, type, t.a + vp8_block2above[b], t.l + vp8_block2left[b], rtcd);
    }

}

void vp8_optimize_mbuv(MACROBLOCK *x, const VP8_ENCODER_RTCD *rtcd)
{
    int b;
    TEMP_CONTEXT t, t2;

    if (!x->e_mbd.above_context[UCONTEXT])
        return;

    if (!x->e_mbd.left_context[UCONTEXT])
        return;

    if (!x->e_mbd.above_context[VCONTEXT])
        return;

    if (!x->e_mbd.left_context[VCONTEXT])
        return;


    vp8_setup_temp_context(&t, x->e_mbd.above_context[UCONTEXT], x->e_mbd.left_context[UCONTEXT], 2);
    vp8_setup_temp_context(&t2, x->e_mbd.above_context[VCONTEXT], x->e_mbd.left_context[VCONTEXT], 2);

    for (b = 16; b < 20; b++)
    {
        vp8_optimize_b(x, b, vp8_block2type[b],
                       t.a + vp8_block2above[b], t.l + vp8_block2left[b], rtcd);

    }

    for (b = 20; b < 24; b++)
    {
        vp8_optimize_b(x, b, vp8_block2type[b],
                       t2.a + vp8_block2above[b], t2.l + vp8_block2left[b], rtcd);
    }

}
#endif

void vp8_encode_inter16x16(const VP8_ENCODER_RTCD *rtcd, MACROBLOCK *x)
{
    vp8_build_inter_predictors_mb(&x->e_mbd);

    vp8_subtract_mb(rtcd, x);

    vp8_transform_mb(x);

    vp8_quantize_mb(x);

#if !(CONFIG_REALTIME_ONLY)
#if 1

    if (x->optimize && x->rddiv > 1)
    {
        vp8_optimize_mb(x, rtcd);
        vp8_find_mb_skip_coef(x);
    }

#endif
#endif

    vp8_inverse_transform_mb(IF_RTCD(&rtcd->common->idct), &x->e_mbd);

    vp8_recon16x16mb(IF_RTCD(&rtcd->common->recon), &x->e_mbd);
}


/* this funciton is used by first pass only */
void vp8_encode_inter16x16y(const VP8_ENCODER_RTCD *rtcd, MACROBLOCK *x)
{
    vp8_build_inter_predictors_mby(&x->e_mbd);

    ENCODEMB_INVOKE(&rtcd->encodemb, submby)(x->src_diff, x->src.y_buffer, x->e_mbd.predictor, x->src.y_stride);

    vp8_transform_mby(x);

    vp8_quantize_mby(x);

    vp8_inverse_transform_mby(IF_RTCD(&rtcd->common->idct), &x->e_mbd);

    vp8_recon16x16mby(IF_RTCD(&rtcd->common->recon), &x->e_mbd);
}


void vp8_encode_inter16x16uv(const VP8_ENCODER_RTCD *rtcd, MACROBLOCK *x)
{
    vp8_build_inter_predictors_mbuv(&x->e_mbd);

    ENCODEMB_INVOKE(&rtcd->encodemb, submbuv)(x->src_diff, x->src.u_buffer, x->src.v_buffer, x->e_mbd.predictor, x->src.uv_stride);

    vp8_transform_mbuv(x);

    vp8_quantize_mbuv(x);

    vp8_inverse_transform_mbuv(IF_RTCD(&rtcd->common->idct), &x->e_mbd);

    vp8_recon_intra_mbuv(IF_RTCD(&rtcd->common->recon), &x->e_mbd);
}


void vp8_encode_inter16x16uvrd(const VP8_ENCODER_RTCD *rtcd, MACROBLOCK *x)
{
    vp8_build_inter_predictors_mbuv(&x->e_mbd);
    ENCODEMB_INVOKE(&rtcd->encodemb, submbuv)(x->src_diff, x->src.u_buffer, x->src.v_buffer, x->e_mbd.predictor, x->src.uv_stride);

    vp8_transform_mbuvrd(x);

    vp8_quantize_mbuv(x);

}
