/*
 * Copyright 1993 by the University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation.
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any expressed or implied warranty.
 */

#include <stdio.h>
#include <string.h>
#include "global.h"

void   code_i_mb();
void   put_mb();
short* get_mb();
double get_snr();
short **make_mb();

extern int mb_type;      /* Macro bolck type 1: intra, 2:pred or Bi-Dir */
/****************************************************************************/
/*     Interpolate coding subroutine 'do_interpolate'                       */
/****************************************************************************/
FRAME_STAT*
  do_interpolate(cur_frame,prev_frame,next_frame,
                 quant_scale, dctnum, height, width)
FRAME *cur_frame;
FRAME *prev_frame;
FRAME *next_frame;
int quant_scale, dctnum, height, width; 
{
/*--------------------------- define valuables --------------------------*/
  MB    mblock;       /* MB structure forg current block */
  short *cur_mblock, *prev_mblock, *next_mblock; /* MB data */ 
  short **dec_mb; /* reconstructed mblock */
  int  *fwd_mvx, *fwd_mvy, *bwd_mvx, *bwd_mvy;
                     /* 1 Dimension array of MV's for prev and next frame */
  int   i,j,k;
  short weightavg;  /* temporal value for calculating B mode MB */
  int   n_hor_mb,
        n_vert_mb,
        voff,hoff,  /* MV offset in frame */
        offset1d;   /* MV position in 1 dimenstion array */
  int  prev_fwd_mvx = 0, prev_fwd_mvy = 0; 
  int  prev_bwd_mvx = 0, prev_bwd_mvy = 0; 
  int   intra_prev = 0;    /* mode of previous MB 0: intra, 1: pred */
  int picture_type;  /* 0: Intra, 1: Fwd Pred, 2: Bwd Pred, 3: Bi Pred */
  FRAME_STAT  *FS ;
/*************************************************************/
/*-------------- Initialize & Memory allocations ------------*/
/*************************************************************/
/*------------------ number of MB's in each frame --------------------*/
  n_vert_mb = height/MBSIZE;
  n_hor_mb  = width/MBSIZE;
/*-------------------- Initialization and allocate memory ------------*/
  FS = (FRAME_STAT *)malloc(sizeof(FRAME_STAT));
  FS->block_mode = (char *)malloc(sizeof(char)*n_vert_mb*n_hor_mb);
  FS->bit_num  = (short *)malloc(sizeof(short)*n_vert_mb*n_hor_mb);
  FS->bit_numl = NULL;
  if(dctnum < 64) 
    FS->bit_numl = (short *)malloc(sizeof(short)*n_vert_mb*n_hor_mb);
  FS->frame_mode = 3;
  FS->width = width;
  FS->height = height;
  /*** mbock data ***/
  mblock.data = make_mb();
  /*** allocate reconstructed mblock ***/
  dec_mb  = make_mb();
  /*** allocate memory for MV's ***/
  fwd_mvx = (int *)calloc(n_vert_mb*n_hor_mb,sizeof(int));
  fwd_mvy = (int *)calloc(n_vert_mb*n_hor_mb,sizeof(int));
  bwd_mvx = (int *)calloc(n_vert_mb*n_hor_mb,sizeof(int));
  bwd_mvy = (int *)calloc(n_vert_mb*n_hor_mb,sizeof(int));
/*************************************************************************/
/*                     Begin interpolatemode coding                      */
/*************************************************************************/
#ifdef DEBUG
  fprintf(stderr,"BI-DIRECTION MODE\n");
#endif
/*----------- insert picture (frame) header ------------------*/
  picture_type = 3;         /* 70 bits */
  put_picture_code(picture_type);
/*-- construct MV between current frame and prev/next frame --*/
  if (Xon)
    if(working_update(1,"Bi-Direction mode : Making Forward MV",
		      height/MBSIZE)) goto interrupt_jump_label;
  if(construct_mv(cur_frame->Y,prev_frame->Y,fwd_mvx,fwd_mvy,width,height))
    return NULL;
  if (Xon)
    if(working_update(1,"Bi-Direction mode : Making Backward MV",
		      height/MBSIZE)) goto interrupt_jump_label;
  if(construct_mv(cur_frame->Y,next_frame->Y,bwd_mvx,bwd_mvy,width,height))
    return NULL;/*bwd MV*/
/*-------------------------------------------------------------------*/
#ifdef DEBUG
  fprintf(stderr,"ENCODING ");
#endif
  if (Xon)
    if(working_update(1,"Bi-Direction mode : Encoding",
		      height/MBSIZE)) goto interrupt_jump_label;
/*--------------------------- Loop start ----------------------------*/
for (i=0; i<n_vert_mb; i++) {
    intra_prev = 0;
    voff=i*16;
/*--------------------------- Reset MV's ----------------------------*/
    prev_fwd_mvx = 0; prev_fwd_mvy = 0; 
    prev_bwd_mvx = 0; prev_bwd_mvy = 0; 
/*---------------------- Insert slice header ------------------------*/
    put_slice_code(i+1,quant_scale);
/*-------------------------------------------------------------------*/
#ifdef DEBUG
    fprintf(stderr,".");
#endif
    if (Xon)
      if(working_update(1, NULL, 0)) goto interrupt_jump_label;
/*-------------------------------------------------------------------*/
    for (j=0; j<n_hor_mb; j++) { /* block increment */
      mb_type = 2; /* non-intra */
      offset1d = i*n_hor_mb+j;
      hoff = j*16;  
      /* get MV's */
      cur_mblock  = get_mb(cur_frame, voff, hoff );
      prev_mblock = get_mb(prev_frame,voff+fwd_mvy[offset1d],
                                      hoff+fwd_mvx[offset1d]);
      next_mblock = get_mb(next_frame,voff+bwd_mvy[offset1d],
                                      hoff+bwd_mvx[offset1d]);
      /* select coding mode */
      mblock.type=mode_select(cur_mblock,prev_mblock,next_mblock);
      switch(mblock.type) {
        case 0: /* Intra frame coding */
	        copy_mb(mblock.data[0],cur_mblock);
                mblock.intra = 1;
                FS->block_mode[offset1d] = 0;
                prev_fwd_mvx = 0; prev_fwd_mvy = 0; 
                prev_bwd_mvx = 0; prev_bwd_mvy = 0; 
                break;
        case 1: /* forward prediction */
		copy_mb(mblock.data[0],cur_mblock);
                subtract_mb(mblock.data[0],prev_mblock);
                mblock.intra = 0;
                FS->block_mode[offset1d] = 1;
                mblock.mv[1] = fwd_mvx[offset1d] - prev_fwd_mvx;
                mblock.mv[0] = fwd_mvy[offset1d] - prev_fwd_mvy;
		mblock.mv[1] += big(mblock.mv[1]);
		mblock.mv[0] += big(mblock.mv[0]);
                prev_fwd_mvx = fwd_mvx[offset1d]; 
                prev_fwd_mvy = fwd_mvy[offset1d];
                break;
        case 2: /* backward prediction */
		copy_mb(mblock.data[0],cur_mblock);
                subtract_mb(mblock.data[0],next_mblock);
                mblock.intra = 0;
                FS->block_mode[offset1d] = 2;
                mblock.mv[3] = bwd_mvx[offset1d] - prev_bwd_mvx;
                mblock.mv[2] = bwd_mvy[offset1d] - prev_bwd_mvy;
		mblock.mv[3] += big(mblock.mv[3]);
		mblock.mv[2] += big(mblock.mv[2]);
                prev_bwd_mvx = bwd_mvx[offset1d]; 
                prev_bwd_mvy = bwd_mvy[offset1d];
                break;
        case 3: /* interpolative prediction */
                for(k=0;k<MBSIZE_2 + 2 * DCT_SIZE_2;k++) {
                  weightavg = (short)(((float)(prev_mblock[k] + next_mblock[k]) ) 
                            / 2.0 + 0.5);
		  mblock.data[0][k] = cur_mblock[k] - (short)weightavg;
		}
                mblock.intra = 0;
		FS->block_mode[offset1d] = 3;
                mblock.mv[1] = fwd_mvx[offset1d] - prev_fwd_mvx;
                mblock.mv[0] = fwd_mvy[offset1d] - prev_fwd_mvy;
		mblock.mv[3] = bwd_mvx[offset1d] - prev_bwd_mvx;
		mblock.mv[2] = bwd_mvy[offset1d] - prev_bwd_mvy;
		mblock.mv[3] += big(mblock.mv[3]);
		mblock.mv[2] += big(mblock.mv[2]);
		mblock.mv[1] += big(mblock.mv[1]);
		mblock.mv[0] += big(mblock.mv[0]);
		prev_fwd_mvx = fwd_mvx[offset1d]; 
		prev_fwd_mvy = fwd_mvy[offset1d]; 
		prev_bwd_mvx = bwd_mvx[offset1d]; 
		prev_bwd_mvy = bwd_mvy[offset1d];
                break;
      }
      free(prev_mblock);free(next_mblock);free(cur_mblock);
/*----------------------- call DCT and quantitize reoutine -----------------------*/
      if(mblock.intra == 1) { /* intra mode */
        mb_type = 1; /* intra MB */
        code_i_mb(mblock.data, dec_mb, intra_prev, dctnum, quant_scale);
        intra_prev = 1;
        mblock.mc=0;
      }
      else { /* non intra (Fwd-P, Bwd-P, B) mode */
        mb_type = 2;
        code_i_mb(mblock.data, dec_mb, 0, dctnum, quant_scale);
        intra_prev = 0;
        mblock.mc=1;
      }
/*------------------------- Perform VLC ---------------------------*/
      FS->bit_num[offset1d] =   /* perform VLC on HP code */
              (short)do_vlc(mblock,0, dctnum,((j==0)?1:0), 3, n_hor_mb);
      if(dctnum < 64)
      FS->bit_numl[offset1d] =   /* perform VLC on LP code */
              (short)do_vlc(mblock,1, dctnum,((j==0)?1:0), 3, n_hor_mb);
    }
  }
  free(mblock.data[0]); 
/* compute SNR for original and reconstructed frame */
  FS->SNR = 0; /* get_snr(frame,recon_frame,width,height); */
  if(0) {
  interrupt_jump_label:
    free(FS->block_mode);
    free(FS->bit_num);
    free(FS->block_mode);
    if(FS->bit_numl != NULL) free(FS->bit_numl);
    free(FS);
    FS = NULL;
  }
  free(fwd_mvx);
  free(fwd_mvy);
  free(bwd_mvx);
  free(bwd_mvy);
  for(k=1;k<NUM_P+1;k++) {
    free(mblock.data[k]);
  }
  free(mblock.data);
  for(k=0;k<NUM_P+1;k++) {
    free(dec_mb[k]);
  } 
  free(dec_mb);
#ifdef DEBUG
  fprintf(stderr," Complete!\n");
#endif
  return FS;
}

/****************************************************************************
    mode_select choose the interpolate coding mode for each Macro Block.
    it returns following interger value.
    0: intra, 1: forward prediction 2: backward prediction 
    3: interpolated prediction
*****************************************************************************/
int
  mode_select(org_mb, prev_mb, next_mb)
short *org_mb, *prev_mb, *next_mb;                 /* MV data */
{
  int i,index;
  float var[4], /* var[i]: i = 0: intra    1: forward
                               2: backward 3: interpolate */
        org_mean  = 0,
        org_ssq   = 0, /* sum square */
        weighted  = 0;

  for (i=0;i<4;i++)
    var[i] = 0;

 for(i=0;i<MBSIZE_2;i++) {
    var[1]  += ((org_mb[i] - prev_mb[i])*(org_mb[i] - prev_mb[i]));
    var[2]  += ((org_mb[i] - next_mb[i])*(org_mb[i] - next_mb[i]));
    weighted   = ((float)(prev_mb[i] + next_mb[i]))/2.0;
    var[3]  += ((org_mb[i] - weighted)*(org_mb[i] - weighted));
    org_ssq    += org_mb[i]*org_mb[i];
    org_mean   += org_mb[i];
  }
  org_mean    /= MBSIZE_2;
  org_ssq     /= MBSIZE_2;
  var[0]       = org_ssq-org_mean*org_mean;
  for(i=1;i<4;i++)
    var[i]    /= MBSIZE_2;
  /* find smallest var[i] */
  index=0;
  for(i=1;i<4;i++)
    if(var[i]<=var[index])
      index=i;
  return index;
}






