/*
 * 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
 */

#ifndef _COMMON_H
#define _COMMON_H

#include <assert.h>
#include <limits.h>
#include <malloc.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FRAME_PAD 32
#define FENC_STRIDE 16

#define COPY1_IF_LT(a,b)         if( (a)>(b) ) { (a)=(b); }
#define COPY2_IF_LT(a,b,c,d)     if( (a)>(b) ) { (a)=(b); (c)=(d); }
#define COPY3_IF_LT(a,b,c,d,e,f) if( (a)>(b) ) { (a)=(b); (c)=(d); (e)=(f); }
#define FIX8(f) ((int)(f*(1<<8)+.5))
#define MIN(a,b) ( (a)<(b) ? (a) : (b) )
#define MAX(a,b) ( (a)>(b) ? (a) : (b) )
#define MV_EQ(a,b) (!(((a).x != (b).x) | ((a).y != (b).y)))
#define XCHG(type,a,b) do { type t = a; a = b; b = t; } while(0)

#if defined(__GNUC__) && (__GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ > 0)
#   define UNUSED __attribute__((unused))
#   define ALWAYS_INLINE __attribute__((always_inline)) inline
#   define NOINLINE __attribute__((noinline))
#else
#   define UNUSED
#   define ALWAYS_INLINE inline
#   define NOINLINE
#endif

#ifdef _MSC_VER
#   define DECLARE_ALIGNED( type, var, n ) __declspec(align(n)) type var
#else
#   define DECLARE_ALIGNED( type, var, n ) type var __attribute__((aligned(n)))
#endif

#include "xlsc.h"
#include "tables.h"
#include "rangecoder.h"
#include "dsp.h"

typedef struct
{
    int x, y;
} mv_t;

typedef struct
{
    int intra;
    mv_t mv;
} mb_t;

typedef struct
{
    uint8_t coef[2][183];
    uint8_t mv[9];
    uint8_t mb[1];
} xlsc_cabrc_model_t;

typedef struct
{
    int frame_num;
    int intra;
    int qp;

    uint8_t *plane;
    uint8_t *buffer;

    mb_t *macroblocks;
    int16_t (*dct)[4][64];
    xlsc_cabrc_model_t model; // model from the end of the frame, is not updated for each mb
} xlsc_frame_t;

struct xlsc_t
{
    xlsc_param_t param;

    int decoding;

    int mb_width;
    int mb_height;
    int mb_count;

    struct
    {
        uint8_t *buffer;
        int buffer_size;
        int frame_size;
        xlsc_rac_t c;
        int rd_bits;
        FILE *dump;
    } out;

    xlsc_cabrc_model_t model;

    struct
    {
        int x, y, xy;
        int intra;
        DECLARE_ALIGNED( int16_t, dct[4][64], 16 );
        int16_t dct_zigzag[4][64];
        DECLARE_ALIGNED( uint8_t, fenc[16*16], 16 );
        DECLARE_ALIGNED( uint8_t, fdec[16*16], 16 );
        uint8_t *fenc_block[4], *fenc_p;
        uint8_t *fdec_block[4], *fdec_p;
        uint8_t *fref;
        mv_t mv, mvp;
        mv_t mv_min, mv_max; // edge of the frame
    } mb;

    struct
    {
        uint64_t ssd;
        int mb_type[2];
        int frames;

        uint64_t rac_mb;
        uint64_t rac_mv;
        uint64_t rac_cbp;
        uint64_t rac_nnz;
        uint64_t rac_eob;
        uint64_t rac_coef;
    } stat;

    xlsc_dsp_t dsp;

    // fencs/fdecs[0] = most recently encoded frame
    // fencs/fdecs[1] = the frame that will be returned from this invocation of xlsc_encoder_encode
    // the rest are queued for future encoding
    xlsc_frame_t **fencs, **fdecs;
    // fenc/fdec = the frame currently being encoded. not necessarily equal to
    //             fencs/fdecs[1], if we're test-encoding one of the queued frames.
    // fref = the frame before fdec
    xlsc_frame_t *fenc, *fdec, *fref;
    mb_t *macroblocks;
    int gop_size; // distance from previous I-frame
    uint16_t quant_bias[128];
    int qp, lambda2;
    int block_offset[4]; // byte offset between the corner of the mb and the corner of the block
    int stride; // all frames have the same stride anyway, it's easier to reach here
};

static inline int clip( int x, int min, int max )
{
    return x<min ? min : x>max ? max : x;
}

static inline uint8_t clip_uint8( int x )
{
    return clip( x, 0, 255 );
}

static inline int xlsc_log2( unsigned int x )
{
    int n = 0;
    if( x >= 0x10000 )
    {
        n += 16;
        x >>= 16;
    }
    if( x >= 0x100 )
    {
        n += 8;
        x >>= 8;
    }
    return n + xlsc_log2_tab[x];
}

xlsc_frame_t *xlsc_frame_new( xlsc_t *h );
void xlsc_frame_free( xlsc_frame_t *frame );
void xlsc_frame_from_pic( xlsc_t *h, xlsc_frame_t *frame, xlsc_picture_t *pic );
void xlsc_frame_to_pic( xlsc_t *h, xlsc_frame_t *frame, xlsc_picture_t *pic );
void xlsc_frame_expand_border( xlsc_t *h, xlsc_frame_t *frame );
void xlsc_frame_push( xlsc_frame_t **list, xlsc_frame_t *frame );
xlsc_frame_t *xlsc_frame_pop( xlsc_frame_t **list );
void xlsc_frame_unshift( xlsc_frame_t **list, xlsc_frame_t *frame );
xlsc_frame_t *xlsc_frame_shift( xlsc_frame_t **list );
void xlsc_macroblock_init( xlsc_t *h );
void xlsc_macroblock_frame_start( xlsc_t *h );
void xlsc_macroblock_load( xlsc_t *h, int x, int y );
void xlsc_macroblock_save( xlsc_t *h );
void xlsc_macroblock_decode( xlsc_t *h );
void xlsc_macroblock_encode( xlsc_t *h );
void xlsc_macroblock_read( xlsc_t *h );
void xlsc_macroblock_write( xlsc_t *h );
void xlsc_macroblock_size( xlsc_t *h );
void xlsc_macroblock_rac_update( xlsc_t *h );
void xlsc_macroblock_analyse( xlsc_t *h );
void xlsc_predict_mv( xlsc_t *h, mv_t *mvp );
int  xlsc_rd_cost( xlsc_t *h );
int  xlsc_me( xlsc_t *h, mv_t *mv, const mv_t *mvp, int nmvp );
void xlsc_me_rd( xlsc_t *h, int rd_cost );
void xlsc_zigzag( int16_t *dst, int16_t *src );
void xlsc_unzigzag( int16_t *dst, int16_t *src );
void xlsc_lookahead_quant( xlsc_t *h );

#endif //_COMMON_H
