/*
 * Copyright (C) 2007 Loren Merritt <lorenm@u.washington.edu>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "common.h"

void xlsc_macroblock_init( xlsc_t *h )
{
#define INIT(fenc)\
    h->mb.fenc##_block[0] = h->mb.fenc;\
    h->mb.fenc##_block[1] = h->mb.fenc+8;\
    h->mb.fenc##_block[2] = h->mb.fenc+8*FENC_STRIDE;\
    h->mb.fenc##_block[3] = h->mb.fenc+8+8*FENC_STRIDE;
    INIT(fenc);
    INIT(fdec);
#undef INIT

    h->block_offset[0] = 0;
    h->block_offset[1] = 8;
    h->block_offset[2] = 8*h->stride;
    h->block_offset[3] = 8+8*h->stride;
}

void xlsc_macroblock_frame_start( xlsc_t *h )
{
    h->mb.intra = 1;
    h->qp = h->fdec->qp;
    h->lambda2 = h->param.lambda_mult * h->qp * h->qp >> 6;
    h->macroblocks = h->fdec->macroblocks;
}

void xlsc_predict_mv( xlsc_t *h, mv_t *mvp )
{
    mb_t *left = h->macroblocks + h->mb.xy - 1;
    mb_t *top  = h->macroblocks + h->mb.xy - h->mb_width;
    if( h->mb.x == 0 )
    {
        if( h->mb.y == 0 )
            *mvp = (mv_t){0,0};
        else
            *mvp = top->mv;
    }
    else
    {
        if( h->mb.y == 0 )
            *mvp = left->mv;
        else
        {
            mv_t n[3] = { left->mv, top->mv };
            if( h->mb.x == h->mb_width-1 )
                n[2] = top[-1].mv;
            else
                n[2] = top[1].mv;
            mvp->x = mid_pred( n[0].x, n[1].x, n[2].x );
            mvp->y = mid_pred( n[0].y, n[1].y, n[2].y );
        }
    }
}

void xlsc_macroblock_load( xlsc_t *h, int x, int y )
{
    int off = (x + y*h->stride) * 16;

    h->mb.x = x;
    h->mb.y = y;
    h->mb.xy = x + y * h->mb_width;

    h->mb.fdec_p = h->fdec->plane+off;
    h->mb.fref   = h->fref->plane+off;

    if( !h->decoding )
    {
        h->mb.fenc_p = h->fenc->plane+off;
        h->dsp.copy( h->mb.fenc, h->mb.fenc_p, FENC_STRIDE, h->stride, 16 );
    }

    if( !h->fdec->intra )
    {
        if( h->param.umv || h->decoding )
        {
            h->mb.mv_min.x = -4-32*(h->mb.x + 1);
            h->mb.mv_min.y = -4-32*(h->mb.y + 1);
            h->mb.mv_max.x =  4+32*(h->mb_width - h->mb.x);
            h->mb.mv_max.y =  4+32*(h->mb_height - h->mb.y);
        }
        else
        {
            // FIXME also assumes no subpel
            h->mb.mv_min.x = -32*h->mb.x;
            h->mb.mv_min.y = -32*h->mb.y;
            h->mb.mv_max.x =  32*(h->mb_width - h->mb.x - 1);
            h->mb.mv_max.y =  32*(h->mb_height - h->mb.y - 1);
        }

        xlsc_predict_mv( h, &h->mb.mvp );
    }
}

void xlsc_macroblock_save( xlsc_t *h )
{
    h->dsp.copy( h->mb.fdec_p, h->mb.fdec, h->stride, FENC_STRIDE, 16 );
    mb_t *mb = h->macroblocks + h->mb.xy;
    mb->intra = h->mb.intra;
    mb->mv = h->mb.intra ? h->mb.mvp : h->mb.mv;
    if( !h->fdec->intra )
        h->stat.mb_type[mb->intra]++;
}

// transposed zigzag, since the asm dct produces transposed coefs
#define ZIGZAG\
    Z( 0, 0); Z( 1, 8); Z( 2, 1); Z( 3, 2);\
    Z( 4, 9); Z( 5,16); Z( 6,24); Z( 7,17);\
    Z( 8,10); Z( 9, 3); Z(10, 4); Z(11,11);\
    Z(12,18); Z(13,25); Z(14,32); Z(15,40);\
    Z(16,33); Z(17,26); Z(18,19); Z(19,12);\
    Z(20, 5); Z(21, 6); Z(22,13); Z(23,20);\
    Z(24,27); Z(25,34); Z(26,41); Z(27,48);\
    Z(28,56); Z(29,49); Z(30,42); Z(31,35);\
    Z(32,28); Z(33,21); Z(34,14); Z(35, 7);\
    Z(36,15); Z(37,22); Z(38,29); Z(39,36);\
    Z(40,43); Z(41,50); Z(42,57); Z(43,58);\
    Z(44,51); Z(45,44); Z(46,37); Z(47,30);\
    Z(48,23); Z(49,31); Z(50,38); Z(51,45);\
    Z(52,52); Z(53,59); Z(54,60); Z(55,53);\
    Z(56,46); Z(57,39); Z(58,47); Z(59,54);\
    Z(60,61); Z(61,62); Z(62,55); Z(63,63);

void xlsc_zigzag( int16_t *dst, int16_t *src )
{
#define Z(d,s) dst[d] = src[s]
    ZIGZAG
#undef Z
}

void xlsc_unzigzag( int16_t *dst, int16_t *src )
{
#define Z(s,d) dst[d] = src[s]
    ZIGZAG
#undef Z
}
#undef ZIGZAG

static void mc_mb( xlsc_t *h )
{
    int xi = h->mb.mv.x >> 1;
    int yi = h->mb.mv.y >> 1;
    int xf = h->mb.mv.x & 1;
    int yf = h->mb.mv.y & 1;
    h->dsp.mc[XLSC_PIXEL_16][(yf<<1)+xf]( h->mb.fdec, h->mb.fref + xi + yi*(h->stride), FENC_STRIDE, h->stride, 16 );
}

void xlsc_macroblock_encode( xlsc_t *h )
{
    if( !h->mb.intra )
        mc_mb( h );

    int b;
    for( b=0; b<4; b++ )
    {
        int16_t *dct = h->mb.dct[b];
        uint8_t *fenc = h->mb.fenc_block[b];
        uint8_t *fdec = h->mb.fdec_block[b];
        h->dsp.sub_dct[h->mb.intra]( dct, fenc, fdec );
        h->dsp.quant( dct, h->qp, h->quant_bias[h->qp] );
        xlsc_zigzag( h->mb.dct_zigzag[b], dct );
        h->dsp.dequant( dct, h->qp );
        h->dsp.add_idct[h->mb.intra]( fdec, dct );
    }
}

void xlsc_macroblock_decode( xlsc_t *h )
{
    if( !h->mb.intra )
    {
        h->mb.mv.x = clip( h->mb.mv.x, h->mb.mv_min.x, h->mb.mv_max.x );
        h->mb.mv.y = clip( h->mb.mv.y, h->mb.mv_min.y, h->mb.mv_max.y );
        mc_mb( h );
    }

    int b;
    for( b=0; b<4; b++ )
    {
        h->dsp.dequant( h->mb.dct[b], h->qp );
        h->dsp.add_idct[h->mb.intra]( h->mb.fdec_block[b], h->mb.dct[b] );
    }

    h->dsp.copy( h->mb.fdec_p, h->mb.fdec, h->stride, FENC_STRIDE, 16 );
    mb_t *mb = h->macroblocks + h->mb.xy;
    mb->intra = h->mb.intra;
    mb->mv = h->mb.mv;
}
