/*******************************************************************
                picture decoding (reconstract) module
 *******************************************************************/

#define PICTURE_C
#include "picture.h"

/* in block_mmx.asm */
extern void __stdcall copy_i_block_to_frame_mmx(short *in, unsigned char *out, int step);
extern void __stdcall add_diff_to_frame_mmx(short *in, unsigned char *out, int step);


int decode_picture(VIDEO_STREAM *in, MC_BUFFER *out, DECODE_PICTURE_PARAMETER *prm);

void add_block_data_to_frame(short *in, FRAME *out, READ_BLOCK_OPTION *opt, int x, int y, int block_number);
void add_block_data_to_frame_mmx(short *in, FRAME *out, READ_BLOCK_OPTION *opt, int x, int y, int block_number);

static void check_motion_vector(MC_PARAMETER *prm, int x, int y, int width, int height);

int decode_picture(VIDEO_STREAM *in, MC_BUFFER *out, DECODE_PICTURE_PARAMETER *prm)
{
	int i;

	SLICE_HEADER sh;
	MACROBLOCK   mb;

	short block[64];

	int x, y, address, inc, width, height, max;

	address = 0;
	width = out->current->width;
	height = out->current->height;
	max = width * height / 16;
	if(prm->mc_parameter.picture_structure != 3){
		max /= 2;
	}

	while(vs_next_start_code(in)){
		
		if(!read_slice_header(in, &sh, &(prm->slice_option))){
			return 0;
		}

		slice_header_to_read_block_option(&sh, &(prm->block_option));

		reset_motion_vector_predictor(&mb);

		inc = get_macroblock_address_increment(in);
		if( ((inc-1)*16) == (address % width) ){
			inc = 1;
		}else{
			return 0;
		}

		do{
			read_macroblock(in, &mb, &(prm->macroblock_option));
			macroblock_to_mc_parameter(&mb, &(prm->mc_parameter));
			macroblock_to_read_block_option(&mb, &(prm->block_option));

			x = address % width;
			y = address / width * 16;
			if(prm->mc_parameter.picture_structure != 3){
				y *= 2;
			}
			check_motion_vector(&(prm->mc_parameter), x, y, width, height);
			mc(out, &(prm->mc_parameter), x, y);

			for(i=0;i<mb.block_count;i++){
				if(mb.pattern_code[i]){
					if(!read_block(in, block, i, &(prm->block_option))){
						return 0;
					}
					prm->idct_func(block);
					prm->add_block_func(block, out->current, &(prm->block_option), x, y, i);
				}
			}
			address += 16;
			
			if(address >= max){ /* all macroblocks have decoded */
				return 1;
			}
			
			inc = get_macroblock_address_increment(in);
			if(inc == 0){ /* goto next slice */
				break;
			}

			if(inc != 1){ /* skipped macroblock */
				reset_dc_dct_predictor(&(prm->block_option));
				if(prm->macroblock_option.picture_coding_type == 2){
					reset_motion_vector_predictor(&mb);
					macroblock_to_mc_parameter(&mb, &(prm->mc_parameter));
				}
				if(prm->mc_parameter.picture_structure == 3){
					prm->mc_parameter.prediction_type = PREDICTION_TYPE_FRAME_BASED;
				}else{
					prm->mc_parameter.prediction_type = PREDICTION_TYPE_FIELD_BASED;
					prm->mc_parameter.motion_vertical_field_select[0][1] = (prm->mc_parameter.picture_structure == 2);
					prm->mc_parameter.motion_vertical_field_select[0][1] = (prm->mc_parameter.picture_structure == 2);
				}
			}

			for(i=0;i<inc-1;i++){

				x = address % width;
				y = address / width * 16;
				if(prm->mc_parameter.picture_structure != 3){
					y *= 2;
				}
				mc(out, &(prm->mc_parameter), x, y);

				address += 16;
			}

		}while(1);
	}

	return 1;
}

static const int cc_table[] = {
	0, 0, 0, 0, 1, 2, 1, 2, 1, 2, 1, 2,
};

static const int x_offset[12] = {
	0, 8, 0, 8, 0, 0, 0, 0, 8, 8, 8, 8,
};

static const int y_offset[12][4][2] = {
	{/* block_number - 0 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			0, 0,
		},
		{/* picture_structure - 2 */
			1, 1,
		},
		{/* picture_structure - 3 */
			0, 0,
		},
	},
	{/* block_number - 1 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			0, 0,
		},
		{/* picture_structure - 2 */
			1, 1,
		},
		{/* picture_structure - 3 */
			0, 0,
		},
	},
	{/* block_number - 2 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			16, 16,
		},
		{/* picture_structure - 2 */
			17, 17,
		},
		{/* picture_structure - 3 */
			8, 1,
		},
	},
	{/* block_number - 3 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			16, 16,
		},
		{/* picture_structure - 2 */
			17, 17,
		},
		{/* picture_structure - 3 */
			8, 1,
		},
	},
	{/* block_number - 4 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			0, 0,
		},
		{/* picture_structure - 2 */
			1, 1,
		},
		{/* picture_structure - 3 */
			0, 0,
		},
	},
	{/* block_number - 5 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			0, 0,
		},
		{/* picture_structure - 2 */
			1, 1,
		},
		{/* picture_structure - 3 */
			0, 0,
		},
	},
	{/* block_number - 6 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			16, 16,
		},
		{/* picture_structure - 2 */
			17, 17,
		},
		{/* picture_structure - 3 */
			8, 1,
		},
	},
	{/* block_number - 7 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			16, 16,
		},
		{/* picture_structure - 2 */
			17, 17,
		},
		{/* picture_structure - 3 */
			8, 1,
		},
	},
	{/* block_number - 8 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			0, 0,
		},
		{/* picture_structure - 2 */
			1, 1,
		},
		{/* picture_structure - 3 */
			0, 0,
		},
	},
	{/* block_number - 9 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			0, 0,
		},
		{/* picture_structure - 2 */
			1, 1,
		},
		{/* picture_structure - 3 */
			0, 0,
		},
	},
	{/* block_number - 10 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			16, 16,
		},
		{/* picture_structure - 2 */
			17, 17,
		},
		{/* picture_structure - 3 */
			8, 1,
		},
	},
	{/* block_number - 11 */
		{/* dummy */
			0, 0,
		},
		{/* picture_structure - 1 */
			16, 16,
		},
		{/* picture_structure - 2 */
			17, 17,
		},
		{/* picture_structure - 3 */
			8, 1,
		},
	},
};

static const int y_shift[3][4] = {
	{/* cc == 0, y */
		0, 0, 0, 0,
	},
	{/* cc == 1, u */
		0, 1, 0, 0,
	},
	{/* cc == 2, v */
		0, 1, 0, 0,
	},
};

static const int x_shift[3][4] = {
	{/* cc == 0, y */
		0, 0, 0, 0,
	},
	{/* cc == 1, u */
		0, 1, 1, 0,
	},
	{/* cc == 2, v */
		0, 1, 1, 0,
	}
};

static const int step_shift[3][4][4][2] = {
	{/* cc == 0 */
		{/* dummy */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				0, 0,
			},
			{/* chroma_format == 2 */
				0, 0,
			},
			{/* chroma_format == 3 */
				0, 0,
			},
		},
		{/* picture_structure == 1 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				1, 1,
			},
			{/* chroma_format == 2 */
				1, 1,
			},
			{/* chroma_format == 3 */
				1, 1,
			},
		},
		{/* picture_structure == 2 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				1, 1,
			},
			{/* chroma_format == 2 */
				1, 1,
			},
			{/* chroma_format == 3 */
				1, 1,
			},
		},
		{/* picture_structure == 3 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				0, 1,
			},
			{/* chroma_format == 2 */
				0, 1,
			},
			{/* chroma_format == 3 */
				0, 1,
			},
		},
	},
	{/* cc == 1 */
		{/* dummy */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				0, 0,
			},
			{/* chroma_format == 2 */
				0, 0,
			},
			{/* chroma_format == 3 */
				0, 0,
			},
		},
		{/* picture_structure == 1 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				1, 1,
			},
			{/* chroma_format == 2 */
				1, 1,
			},
			{/* chroma_format == 3 */
				1, 1,
			},
		},
		{/* picture_structure == 2 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				1, 1,
			},
			{/* chroma_format == 2 */
				1, 1,
			},
			{/* chroma_format == 3 */
				1, 1,
			},
		},
		{/* picture_structure == 3 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				0, 0,
			},
			{/* chroma_format == 2 */
				0, 1,
			},
			{/* chroma_format == 3 */
				0, 1,
			},
		},
	},
	{/* cc == 2 */
		{/* dummy */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				0, 0,
			},
			{/* chroma_format == 2 */
				0, 0,
			},
			{/* chroma_format == 3 */
				0, 0,
			},
		},
		{/* picture_structure == 1 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				1, 1,
			},
			{/* chroma_format == 2 */
				1, 1,
			},
			{/* chroma_format == 3 */
				1, 1,
			},
		},
		{/* picture_structure == 2 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				1, 1,
			},
			{/* chroma_format == 2 */
				1, 1,
			},
			{/* chroma_format == 3 */
				1, 1,
			},
		},
		{/* picture_structure == 3 */
			{/* dummy */
				0, 0,
			},
			{/* chroma_format == 1 */
				0, 0,
			},
			{/* chroma_format == 2 */
				0, 1,
			},
			{/* chroma_format == 3 */
				0, 1,
			},
		},
	},
};

void add_block_data_to_frame(short *in, FRAME *out, READ_BLOCK_OPTION *opt, int x, int y, int block_number)
{
	int i,j;
	int step, cc;
	unsigned char *p;
	
	cc = cc_table[block_number];
	
	switch(cc){
	case 0:
		p = out->y;
		break;
	case 1:
		p = out->u;
		break;
	case 2:
		p = out->v;
        break;
    default:
        return;    
	};

	x >>= x_shift[cc][opt->chroma_format];
	y >>= y_shift[cc][opt->chroma_format];
	step = out->width << step_shift[cc][opt->picture_structure][opt->chroma_format][opt->dct_type];
	p += (y + y_offset[block_number][opt->picture_structure][opt->dct_type]) * out->width;
	p += x + x_offset[block_number];
	
	if(opt->macroblock_intra){
		for(i=0;i<8;i++){
			for(j=0;j<8;j++){
				p[j] = uchar_clip_table[UCHAR_CLIP_TABLE_OFFSET+in[j]];
			}
			p += step;
			in += 8;
		}
	}else{
		for(i=0;i<8;i++){
			for(j=0;j<8;j++){
				p[j] = uchar_clip_table[UCHAR_CLIP_TABLE_OFFSET+p[j]+in[j]];
			}
			p += step;
			in += 8;
		}
	}
}

typedef void ( __stdcall *ADD_BLOCK_CORE)(short *, unsigned char *, int);

void add_block_data_to_frame_mmx(short *in, FRAME *out, READ_BLOCK_OPTION *opt, int x, int y, int block_number)
{
	int step, cc;
	unsigned char *p;

	static const ADD_BLOCK_CORE table[2] = {
		add_diff_to_frame_mmx, copy_i_block_to_frame_mmx,
	};
	
	cc = cc_table[block_number];
	
	switch(cc){
	case 0:
		p = out->y;
		break;
	case 1:
		p = out->u;
		break;
	case 2:
		p = out->v;
		break;
	default:
		return;    
	};

	x >>= x_shift[cc][opt->chroma_format];
	y >>= y_shift[cc][opt->chroma_format];
	step = out->width << step_shift[cc][opt->picture_structure][opt->chroma_format][opt->dct_type];
	p += (y + y_offset[block_number][opt->picture_structure][opt->dct_type]) * out->width;
	p += x + x_offset[block_number];
	
	table[opt->macroblock_intra](in, p, step);
}

static void check_motion_vector(MC_PARAMETER *prm, int x, int y, int width, int height)
{
	int r,s;

	for(r=0;r<2;r++){
		for(s=0;s<2;s++){
			if(prm->PMV[r][s][0] < -2 * x){
				prm->PMV[r][s][0] = -2 * x;
			}else if(prm->PMV[r][s][0] > (width-x-16)*2){
				prm->PMV[r][s][0] = (width-x-16)*2;
			}
			if(prm->PMV[r][s][1] < -2 * y){
				prm->PMV[r][s][1] = - 2 * y;
			}else if(prm->PMV[r][s][1] > (height-y-16)*2){
				prm->PMV[r][s][1] = (height-y-16)*2;
			}
		}
	}
}

