/*******************************************************************
                MC - Motion Compensation module
 *******************************************************************/

#define MC_C
#include "mc.h"

#define FROM_FRAME 0x00
#define FROM_TOP   0x10
#define FROM_BOTOM 0x20
#define TO_FRAME   0x00
#define TO_TOP     0x01
#define TO_BOTOM   0x02
#define PREDICTION_FRAME_TO_FRAME (FROM_FRAME|TO_FRAME)
#define PREDICTION_FRAME_TO_TOP   (FROM_FRAME|TO_TOP)
#define PREDICTION_FRAME_TO_BOTOM (FROM_FRAME|TO_BOTOM)
#define PREDICTION_TOP_TO_TOP     (FROM_TOP|TO_TOP)
#define PREDICTION_TOP_TO_BOTOM   (FROM_TOP|TO_BOTOM)
#define PREDICTION_BOTOM_TO_TOP   (FROM_BOTOM|TO_TOP)
#define PREDICTION_BOTOM_TO_BOTOM (FROM_BOTOM|TO_BOTOM)

int mc(MC_BUFFER *buf, MC_PARAMETER *prm, int x, int y);

static void prediction(FRAME *in, FRAME *out, int type, int xv, int yv, int x, int y, int first, int chroma_format);
static void component_prediction(unsigned char *in, unsigned char *out, int width, int height, int p_step, int r_step, int xv, int yv, int first);

int mc(MC_BUFFER *buf, MC_PARAMETER *prm, int x, int y)
{
	int first;
	int current_field;
	int type;
	FRAME *prediction_frame;

	first = 1;
	
	if( prm->macroblock_motion_forward || (prm->picture_coding_type == 2) ){
		if(prm->picture_structure == 3){
			if( (prm->prediction_type == PREDICTION_TYPE_FRAME_BASED) || !prm->macroblock_motion_forward ){
				
				prediction(buf->forward, buf->current, PREDICTION_FRAME_TO_FRAME, prm->PMV[0][0][0], prm->PMV[0][0][1], x, y, first, prm->chroma_format);
				
			}else if(prm->prediction_type == PREDICTION_TYPE_FIELD_BASED){
				
				if(prm->motion_vertical_field_select[0][0]){
					prediction(buf->forward, buf->current, PREDICTION_BOTOM_TO_TOP, prm->PMV[0][0][0], prm->PMV[0][0][1] >> 1, x, y, first, prm->chroma_format);
				}else{
					prediction(buf->forward, buf->current, PREDICTION_TOP_TO_TOP, prm->PMV[0][0][0], prm->PMV[0][0][1] >> 1, x, y, first, prm->chroma_format);
				}

				if(prm->motion_vertical_field_select[1][0]){
					prediction(buf->forward, buf->current, PREDICTION_BOTOM_TO_BOTOM, prm->PMV[1][0][0], prm->PMV[1][0][1] >> 1, x, y, first, prm->chroma_format);
				}else{
					prediction(buf->forward, buf->current, PREDICTION_TOP_TO_BOTOM, prm->PMV[1][0][0], prm->PMV[1][0][1] >> 1, x, y, first, prm->chroma_format);
				}
				
			}else if(prm->prediction_type == PREDICTION_TYPE_DUAL_PRIME){

				prediction(buf->forward, buf->current, PREDICTION_TOP_TO_TOP, prm->PMV[0][0][0], prm->PMV[0][0][1]>>1, x, y, 1, prm->chroma_format);
				prediction(buf->forward, buf->current, PREDICTION_BOTOM_TO_TOP, prm->DMV[0][0], prm->DMV[0][1], x, y, 0, prm->chroma_format);
				
				prediction(buf->forward, buf->current, PREDICTION_BOTOM_TO_BOTOM, prm->PMV[1][0][0], prm->PMV[1][0][1]>>1, x, y, 1, prm->chroma_format);
				prediction(buf->forward, buf->current, PREDICTION_TOP_TO_BOTOM, prm->DMV[1][0], prm->DMV[1][1], x, y, 0, prm->chroma_format);

			}
		}else{
			
			if(prm->picture_structure == 2){
				type = TO_BOTOM;
				current_field = 1;
			}else{
				type = TO_TOP;
				current_field = 0;
			}

			if(prm->motion_vertical_field_select[0][0]){
				type += FROM_BOTOM;
			}else{
				type += FROM_TOP;
			}

			if( (prm->picture_coding_type == 2) && prm->second_field && (current_field != prm->motion_vertical_field_select[0][0]) ){
				prediction_frame = buf->current;
			}else{
				prediction_frame = buf->forward;
			}

			if( (prm->prediction_type == PREDICTION_TYPE_FIELD_BASED) || !prm->macroblock_motion_forward ){
				
				prediction(buf->current, prediction_frame, type, prm->PMV[0][0][0], prm->PMV[0][0][1], x, y, first, prm->chroma_format);
				prediction(buf->current, prediction_frame, type, prm->PMV[0][0][0], prm->PMV[0][0][1], x, y+16, first, prm->chroma_format);
				
			}else if(prm->prediction_type == PREDICTION_TYPE_16x8_MC){

				prediction(buf->current, prediction_frame, type, prm->PMV[0][0][0], prm->PMV[0][0][1], x, y, first, prm->chroma_format);

				if(prm->motion_vertical_field_select[1][0]){
					type = (type & 0xf) + FROM_BOTOM;
				}else{
					type = (type & 0xf) + FROM_TOP;
				}

				if( (prm->picture_coding_type == 2) && prm->second_field && (current_field != prm->motion_vertical_field_select[1][0]) ){
					prediction_frame = buf->current;
				}else{
					prediction_frame = buf->forward;
				}
				
				prediction(buf->current, prediction_frame, type, prm->PMV[1][0][0], prm->PMV[1][0][1], x, y+16, first, prm->chroma_format);

			}else if(prm->prediction_type == PREDICTION_TYPE_DUAL_PRIME){
				
				if(prm->second_field){
					prediction_frame = buf->current;
				}else{
					prediction_frame = buf->forward;
				}

				type &= 0xF;
				type |= type << 4;
				
				prediction(buf->current, buf->forward, type, prm->PMV[0][0][0], prm->PMV[0][0][1], x, y, 1, prm->chroma_format);
				prediction(buf->current, buf->forward, type, prm->PMV[0][0][0], prm->PMV[0][0][1], x, y+16, 1, prm->chroma_format);

				type &= 0xF;
				if(type == TO_TOP){
					type |= FROM_BOTOM;
				}else{
					type |= FROM_TOP;
				}
				
				prediction(buf->current, prediction_frame, type, prm->DMV[0][0], prm->DMV[0][1], x, y, 0, prm->chroma_format);
				prediction(buf->current, prediction_frame, type, prm->DMV[0][0], prm->DMV[0][1], x, y+16, 0, prm->chroma_format);

			}
		}
		first = 0;
	}

	if(prm->macroblock_motion_backward){
		if(prm->picture_structure == 3){
			if(prm->prediction_type == PREDICTION_TYPE_FRAME_BASED){
				
				prediction(buf->backward, buf->current, PREDICTION_FRAME_TO_FRAME, prm->PMV[0][1][0], prm->PMV[0][1][1], x, y, first, prm->chroma_format);
				
			}else{
				
				if(prm->motion_vertical_field_select[0][1]){
					type = PREDICTION_BOTOM_TO_TOP;
				}else{
					type = PREDICTION_TOP_TO_TOP;
				}
				prediction(buf->backward, buf->current, type, prm->PMV[0][1][0], prm->PMV[0][1][1] >> 1, x, y, first, prm->chroma_format);

				if(prm->motion_vertical_field_select[1][1]){
					type = PREDICTION_BOTOM_TO_BOTOM;
				}else{
					type = PREDICTION_TOP_TO_BOTOM;
				}
				prediction(buf->backward, buf->current, type, prm->PMV[1][1][0], prm->PMV[1][1][1] >> 1, x, y, first, prm->chroma_format);
				
			}
		}else{
			
			if(prm->motion_vertical_field_select[0][1]){
				type = PREDICTION_BOTOM_TO_BOTOM;
			}else{
				type = PREDICTION_TOP_TO_TOP;
			}
			
			if(prm->prediction_type == PREDICTION_TYPE_FIELD_BASED){

				prediction(buf->backward, buf->current, type, prm->PMV[0][1][0], prm->PMV[0][1][1], x, y, first, prm->chroma_format);
				prediction(buf->backward, buf->current, type, prm->PMV[0][1][0], prm->PMV[0][1][1], x, y+16, first, prm->chroma_format);
				
			}else if(prm->prediction_type == PREDICTION_TYPE_16x8_MC){

				prediction(buf->backward, buf->current, type, prm->PMV[0][1][0], prm->PMV[0][1][1], x, y, first, prm->chroma_format);
				
				if(prm->motion_vertical_field_select[1][1]){
					type = PREDICTION_BOTOM_TO_BOTOM;
				}else{
					type = PREDICTION_TOP_TO_TOP;
				}

				prediction(buf->backward, buf->current, type, prm->PMV[1][1][0], prm->PMV[1][1][1], x, y+16, first, prm->chroma_format);
				
			}
		}
	}

	return 1;
}
				
static void prediction(FRAME *in, FRAME *out, int type, int xv, int yv, int x, int y, int first, int chroma_format)
{
	int r_offset, p_offset;
	int r_step, p_step;
	int width, height;

	switch(type){
	case PREDICTION_FRAME_TO_FRAME:
		r_offset = in->width * y + x;
		p_offset = in->width * y + x;
		r_step = in->width;
		p_step = in->width;
		width = 16;
		height = 16;
		break;
	case PREDICTION_FRAME_TO_TOP:
		r_offset = in->width * y + x;
		p_offset = in->width * y + x;
		r_step = in->width;
		p_step = in->width * 2;
		width = 16;
		height = 8;
		break;
	case PREDICTION_FRAME_TO_BOTOM:
		r_offset = in->width * (y+1) + x;
		p_offset = in->width * (y+1) + x;
		r_step = in->width;
		p_step = in->width * 2;
		width = 16;
		height = 8;
		break;
	case PREDICTION_TOP_TO_TOP:
		r_offset = in->width * y + x;
		p_offset = in->width * y + x;
		r_step = in->width * 2;
		p_step = in->width * 2;
		width = 16;
		height = 8;
		break;
	case PREDICTION_TOP_TO_BOTOM:
		r_offset = in->width * y + x;
		p_offset = in->width * (y+1) + x;
		r_step = in->width * 2;
		p_step = in->width * 2;
		width = 16;
		height = 8;
		break;
	case PREDICTION_BOTOM_TO_TOP:
		r_offset = in->width * (y+1) + x;
		p_offset = in->width * y + x;
		r_step = in->width * 2;
		p_step = in->width * 2;
		width = 16;
		height = 8;
		break;
	case PREDICTION_BOTOM_TO_BOTOM:
		r_offset = in->width * (y+1) + x;
		p_offset = in->width * (y+1) + x;
		r_step = in->width * 2;
		p_step = in->width * 2;
		width = 16;
		height = 8;
		break;
	default:
		return;
	}
	
	component_prediction(in->y+r_offset, out->y+p_offset, width, height, r_step, p_step, xv, yv, first);

	if(chroma_format != 3){
		width /= 2;
		xv /= 2;
		x /= 2;
		r_offset -= x;
		p_offset -= x;
	}
	if(chroma_format == 1){
		height /= 2;
		yv /= 2;
		y /= 2;
		r_offset -= in->width * y;
		p_offset -= in->width * y;
	}

	component_prediction(in->u+r_offset, out->u+p_offset, width, height, r_step, p_step, xv, yv, first);
	component_prediction(in->v+r_offset, out->v+p_offset, width, height, r_step, p_step, xv, yv, first);
}

static void component_prediction(unsigned char *in, unsigned char *out, int width, int height, int r_step, int p_step, int xv, int yv, int first)
{
	int xiv,yiv;
	int xh,yh;
	int i, j;
	unsigned int w;
	unsigned char *reference;
	unsigned char *prediction;

	xiv = xv >> 1;
	xh = xv & 1;
	
	yiv = yv >> 1;
	yh = yv & 1;

	reference = in + (yiv * r_step) + xiv;
	prediction = out;

	if(!xh && !yh){ /* no horizontal nor vertical herf-pel */
		if(first){
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					prediction[i] = reference[i];
				}
				reference += p_step;
				prediction += p_step;
			}
		}else{
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+prediction[i];
					prediction[i] = w >> 1;
				}
				reference += p_step;
				prediction += p_step;
			}
		}
	}else if(!xh && yh){ /* no horizontal but vertical half-pel */
		if(first){
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+reference[i+r_step]+1;
					prediction[i] = w >> 1;
				}
				reference += p_step;
				prediction += p_step;
			}
		}else{
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+reference[i+r_step]+1;
					w >>= 1;
					w += prediction[i];
					prediction[i] = w >> 1;
				}
				reference += p_step;
				prediction += p_step;
			}
		}
	}else if(xh && !yh){ /* horizontal but no vertical half-pel */
		if(first){
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+reference[i+1]+1;
					prediction[i] = w >> 1;
				}
				reference += p_step;
				prediction += p_step;
			}
		}else{
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+reference[i+1]+1;
					w >>= 1;
					w += prediction[i];
					prediction[i] = w >> 1;
				}
				reference += p_step;
				prediction += p_step;
			}
		}
	}else{ /* horizontal and vertical half-pel */
		if(first){
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+reference[i+r_step]+reference[i+1]+reference[i+r_step+1]+2;
					prediction[i] = w >> 2;
				}
				reference += p_step;
				prediction += p_step;
			}
		}else{
			for(j=0;j<height;j++){
				for(i=0;i<width;i++){
					w = reference[i]+reference[i+r_step]+reference[i+1]+reference[i+r_step+1]+2;
					w >>= 2;
					w += prediction[i];
					prediction[i] = w >> 1;
				}
				reference += p_step;
				prediction += p_step;
			}
		}
	}
}

