/********************************************************************************
 *																				*
 * This code has been developed by Project Mayo. This software is an			*
 * implementation of a part of one or more MPEG-4 Video tools as				*
 * specified in ISO/IEC 14496-2 standard.  Those intending to use this			*
 * software module in hardware or software products are advised that its		*
 * use may infringe existing patents or copyrights, and any such use			*
 * would be at such party's own risk.  The original developer of this			*
 * software module and his/her company, and subsequent editors and their		*
 * companies (including Project Mayo), will have no liability for use of		*
 * this software or modifications or derivatives thereof.						*
 *																				*
 ********************************************************************************
 *																				*	
 * 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.											*
 *																				*
 * The GPL can be found at: http://www.gnu.org/copyleft/gpl.html				*
 *																				*
 * Authors:																		*
 *																				*
 *	Andrea	Graziani	(Ag):													*
 *		- Original source code (Open Divx Decoder 0.4a).						*		
 *																				*
 *	Marc	Dukette		(Md) and												*
 *	Pedro	Mateu		(Pm):													*
 *		- Heavily modified and optimized code + MIPS ASM						*
 *																				*
 ********************************************************************************/

// mp4_mblock.c //
#define GLOBAL
#include "global.h"
#include "mp4_mblock.h"
#include "mp4_predict.h"

// idct.c 

extern void IDCT_S (idct_block_t *DCT_Block, uint8_t *DestU8, int Stride, unsigned char Mode);

// recon.c 

extern void reconstruct (int bx, int by, int mode);

static void clearblock(idct_block_t * psblock);
static unsigned short getDCsizeLum();
static unsigned short getDCsizeChr();
static int getDCdiff(int dct_dc_size);
static int Get_DC_Scaler(int block_num);
static void blockIntra(int block_num, idct_block_t *block, int coded);
static void blockInter(int block_num, idct_block_t *block);
int LastDropped;
/**/

static void blockIntra(int block_num, idct_block_t *block, int coded)
{
	int i;
	int dct_dc_size, dct_dc_diff;

	// dc coeff
	mp4_hdr.dc_scaler = Get_DC_Scaler(block_num); // calculate DC scaler

	if (block_num < 4) dct_dc_size = getDCsizeLum();
	else	dct_dc_size = getDCsizeChr();
	
	if (dct_dc_size)
		dct_dc_diff = getDCdiff(dct_dc_size);
	else dct_dc_diff = 0;
	if (dct_dc_size > 8)							//*//
			flushbits(1); // marker bit				//*//

	clearblock(block);	

	block[0] = (idct_block_t) dct_dc_diff;

	// dc reconstruction, prediction direction
	dc_recon(block_num, block);

	if (coded) 
	{
		int event,level;
		const unsigned char * zigzag; // zigzag scan dir
		register int q_scale = (mp4_hdr.quantizer) << 1;
		register int q_add = ((q_scale>>1) & 1) ? (q_scale>>1) : ((q_scale>>1) - 1);
		
		if (mp4_hdr.ac_pred_flag == 1) {

			if (coeff_pred.predict_dir == TOP)
				zigzag = alternate_horizontal_scan;
			else
				zigzag = alternate_vertical_scan;
		
		}
		
		else {
			zigzag = zig_zag_scan;
		}
		
		i = 1;
		do // event vld
		{
			event = vld_intra_dct();
			level = event>>16;
			i+= (event&0xFF);
			if (level > 0) {
				block[zigzag[i]] = (q_scale * level) + q_add;
			}
			else {
				block[zigzag[i]] = (q_scale * level) - q_add;
			}
			
			i++;
		} while (!((event>>8)&0xFF));
		
	}
	
	// ac reconstruction
	ac_recon(block_num, block);
}

/**/

static void blockInter(int block_num, idct_block_t *block)
{
	int event;
	int level;
	int i;
	register int q_scale = (mp4_hdr.quantizer) << 1;
	register int q_add = ((q_scale>>1) & 1) ? (q_scale>>1) : ((q_scale>>1) - 1);
	i = 0;
	
	clearblock(block);	

	do // event vld
	{
		event = vld_inter_dct();
		level = event>>16;
		i+= (event&0xFF);
		if (level > 0) {
			block[zig_zag_scan[i]] = (q_scale * level) + q_add;
		}
		else {
			block[zig_zag_scan[i]] = (q_scale * level) - q_add;
		}
		i++;
	} while (!((event>>8)&0xFF));
}

/**/

static unsigned short getDCsizeLum()
{
	unsigned short code;
	unsigned char i;

	code = showbits(11);

	for (i=0;i<8;i++)
	{
		if ((code >> i)==1)
		{
			flushbits(11-i);
			return 12-i;
		}
	}

	code >>= 8;

	if (code == 1) {
		flushbits(3);
		return 4;
	} 
	else if (code == 2) {
		flushbits(3);
		return 3;
	} 
	else if (code == 3) {
		flushbits(3);
		return 0;
	}
	
	code >>=1;
	
	if (code == 2) {
		flushbits(2);
		return 2;
	} 
	else if (code == 3) {
		flushbits(2);
		return 1;
	}     

	return 0;
}

static unsigned short getDCsizeChr()
{

	unsigned char i;
	unsigned short code;
	
	code = showbits(12);

	for (i=0;i<10;i++)
	{
		if ((code >> i)==1)
		{
			flushbits(12-i);
			return 12-i;
		}
	}

	return (3 - getbits(2));
}

/**/

static int getDCdiff(int dct_dc_size)
{
#ifdef MIPS_ASM
__asm ("jal		getbits;"
	   "addi	$8,$4,-1;"//dct_dc_size-1
	   "srlv	$9,$2,$8;"//code >>
	   "bne		$9,$0,MSB_NO_0;"
	   "li		$25,1;"
	   "sllv	$25,$25,$4;"//<<dct_dc_size
	   "addi	$25,$25,-1;"
	   "xor		$2,$2,$25;"
	   "neg		$2;"
	   "MSB_NO_0:"
	   
	   );
#else
	int code = getbits(dct_dc_size);
	int msb = code >> (dct_dc_size - 1);
	if (msb == 0) return (-(code^((1<<dct_dc_size) - 1)));
	else return code;
#endif
}

/**/

static int Get_DC_Scaler(int block_num) 
{
	int quant = mp4_hdr.quantizer;
	if (block_num < 4) {
		if (quant >24) return (quant - 16)<<1;
		else if (quant>8) return (quant + 8);
		else if (quant>4) return quant<<1;
		else return 8;
	}
	else {
		if (quant > 24) return (quant - 6);
		else if (quant > 4) return (quant + 13)>>1;
		else return 8;
	}
}

#ifdef MIPS_ASM_64
static void clearblock (idct_block_t *psblock) 
	{ 
	__asm ("addi $2,$0,2;" 
		"comienzo:sdr $0,0($4);" 
		"sdr $0,8($4);" 
		"sdr $0,16($4);" 
		"sdr $0,24($4);" 
		"sdr $0,32($4);" 
		"sdr $0,40($4);" 
		"sdr $0,48($4);" 
		"sdr $0,56($4);" 
		"addi $4,$4,64;" 
		"addi $2,$2,-1;" 
		"bgtz $2,comienzo;"); 
	} 
#else

static void clearblock (idct_block_t *psblock)
{
	int i;	

	for (i=0; i<64;i++)
		psblock[i]=0;	
}
#endif


/**/

static short getMCBPC();
static short getCBPY();

/**/

static void addblock (int comp, int bx, int by,int idct_mode)
{
	unsigned char *rfp;

	if (comp < 4) // luminance
	{
		// pixel coordinates
		bx <<= 4;
		by <<= 4;
	    // frame DCT coding
		rfp = frame_ref[0] + mp4_hdr.coded_picture_width * (by + ((comp & 2) << 2)) + bx + ((comp & 1) << 3);
		IDCT_S(ld.block, rfp, mp4_hdr.coded_picture_width,idct_mode);
		return;
	} 
	else // chrominance
	{
		// pixel coordinates
		bx <<= 3;
		by <<= 2;  //chrominance  
		// frame DCT coding
		rfp = frame_ref[(comp & 1) + 1] + mp4_hdr.coded_picture_width * by + bx;
		IDCT_S(ld.block, rfp, mp4_hdr.coded_picture_width>>1,idct_mode);
	}
}

/**/
void IVOP_macroblock()
{
	unsigned char j;
	mp4_hdr.mcbpc = getMCBPC(); // mcbpc
	mp4_hdr.derived_mb_type = mp4_hdr.mcbpc & 7;
	mp4_hdr.cbpc = (mp4_hdr.mcbpc >> 4) & 3;

	//INTRA
	
	mp4_hdr.ac_pred_flag = getbits1();
	mp4_hdr.cbp = (getCBPY() << 2) | mp4_hdr.cbpc;
		
	if (mp4_hdr.derived_mb_type== INTRA_Q){
			mp4_hdr.quantizer += DQtab[getbits(2)]; //DQtab[dquant]
			if (mp4_hdr.quantizer > 31)
				mp4_hdr.quantizer = 31;
			else if (mp4_hdr.quantizer < 1)
				mp4_hdr.quantizer = 1;
	}
	// texture decoding add
	for (j = 0; j < 6; j++) {
		blockIntra(j, ld.block, mp4_hdr.cbp & (1 << (5 - j)));
		addblock(j, mp4_hdr.mb_xpos, mp4_hdr.mb_ypos,0);
	}
		#ifdef Direct_YUV
		if (Render_Flag)
			Direct_YUV_to_RGB(mp4_hdr.mb_xpos, mp4_hdr.mb_ypos);
		#endif

	// not coded macroblock
	
	if (mp4_hdr.mb_xpos < (mp4_hdr.mb_xsize-1)) {
		mp4_hdr.mb_xpos++;
	}
	else {
		mp4_hdr.mb_ypos++;
		mp4_hdr.mb_xpos = 0;
	}
}

void PVOP_macroblock()
{
	unsigned char j;
	unsigned char interFlag;

	// coded macroblock 
	
	if (!getbits1()) {         //if coded

		mp4_hdr.mcbpc = getMCBPC(); // mcbpc
		mp4_hdr.derived_mb_type = mp4_hdr.mcbpc & 7;
		mp4_hdr.cbpc = (mp4_hdr.mcbpc >> 4) & 3;

		modemap[mp4_hdr.mba] = mp4_hdr.derived_mb_type; 
		switch(mp4_hdr.derived_mb_type)
		{
			case INTRA:
				interFlag=0;
				mp4_hdr.ac_pred_flag = getbits1();
				mp4_hdr.cbp = (getCBPY() << 2) | mp4_hdr.cbpc;
				#ifdef MIPS_ASM_64
					__asm (	"sdr		$0,0($4);"
						    "sdr		$0,8($4);",&MV[mp4_hdr.mb_xpos][0]);
				#else 
					{
						int i;
					for (i = 0; i < 4; i++) {
						MV[mp4_hdr.mb_xpos][i] = 0;
					}
					}
				#endif
				break;
			case INTRA_Q:
				interFlag=0;
				mp4_hdr.ac_pred_flag = getbits1();
				mp4_hdr.cbp = (getCBPY() << 2) | mp4_hdr.cbpc;
				mp4_hdr.quantizer += DQtab[getbits(2)]; //DQtab[dquant]
				if (mp4_hdr.quantizer > 31)
					mp4_hdr.quantizer = 31;
				else if (mp4_hdr.quantizer < 1)
					mp4_hdr.quantizer = 1;
					#ifdef MIPS_ASM_64
						__asm (	"sdr		$0,0($4);"
								"sdr		$0,8($4);",&MV[mp4_hdr.mb_xpos][0]);
					#else 
						{
						int i;
						for (i = 0; i < 4; i++) {
							MV[mp4_hdr.mb_xpos][i] = 0;
						}
						}
					#endif
				break;
			case INTER_Q:
				interFlag=1;
				mp4_hdr.cbp = (getCBPY() << 2) | mp4_hdr.cbpc;
				mp4_hdr.quantizer += DQtab[getbits(2)]; //DQtab[dquant]
				if (mp4_hdr.quantizer > 31)
					mp4_hdr.quantizer = 31;
				else if (mp4_hdr.quantizer < 1)
					mp4_hdr.quantizer = 1;
				setMV(-1); // mv
				break;
			case INTER:
				interFlag=1;
				mp4_hdr.cbp = (getCBPY() << 2) | mp4_hdr.cbpc;
				setMV(-1); // mv
				break;
			case INTER4V:
				interFlag=1;
				mp4_hdr.cbp = (getCBPY() << 2) | mp4_hdr.cbpc;
				for (j = 0; j < 4; j++) {
					setMV(j); // mv
				}
				break;
			default: //case STUFFING:
				return;
			}
		
		// motion compensation
		if (interFlag) 
		{
			reconstruct(mp4_hdr.mb_xpos, mp4_hdr.mb_ypos, mp4_hdr.derived_mb_type);
			// texture decoding add
			for (j = 0; j < 6; j++) {
				
				if (mp4_hdr.cbp & (1 << (5 - j))) //if coded
				{
					blockInter(j, ld.block);
					addblock(j, mp4_hdr.mb_xpos, mp4_hdr.mb_ypos,1);
				}
			}	
		}
		else 
		{
			rescue_predict();  //Restore AC_DC values
			for (j = 0; j < 6; j++) {
				blockIntra(j, ld.block, mp4_hdr.cbp & (1 << (5 - j)));
				addblock(j, mp4_hdr.mb_xpos, mp4_hdr.mb_ypos,0);
			}
		}
		#ifdef Direct_YUV
		if (Render_Flag)
			Direct_YUV_to_RGB(mp4_hdr.mb_xpos, mp4_hdr.mb_ypos);
		#endif
	}

	// not coded macroblock
	else {
			#ifdef MIPS_ASM_64
			__asm (	"sdr		$0,0($4);"
					"sdr		$0,8($4);",&MV[mp4_hdr.mb_xpos][0]);
			#else 	
	
			MV[mp4_hdr.mb_xpos][0]= 0;
			MV[mp4_hdr.mb_xpos][1]= 0;
			MV[mp4_hdr.mb_xpos][2]= 0;
			MV[mp4_hdr.mb_xpos][3]= 0;
		
			#endif
		
		
		if (modemap[mp4_hdr.mba] != NOT_CODED||(mp4_hdr.last_prediction_type==I_VOP)){

			modemap[mp4_hdr.mba] = NOT_CODED; // [Review] used only in P-VOPs
			reconstruct(mp4_hdr.mb_xpos, mp4_hdr.mb_ypos, mp4_hdr.derived_mb_type);
		}
		#ifdef Direct_YUV
		if (Render_Flag&&(LastDropped))
			Direct_YUV_to_RGB(mp4_hdr.mb_xpos, mp4_hdr.mb_ypos);
		#endif
	}

	if (mp4_hdr.mb_xpos < (mp4_hdr.mb_xsize-1)) {
		mp4_hdr.mb_xpos++;
	}
	else {
		mp4_hdr.mb_ypos++;
		mp4_hdr.mb_xpos = 0;
	}
}

/**/

static short getMCBPC()
{
	short code;
	
	code = showbits(9);

	if (mp4_hdr.prediction_type == I_VOP)
	{
		if (code == 1) {
			flushbits(9); // stuffing
			return 0;
		}
		else if (code < 8) return -1;
		code >>= 3;
		if (code >= 32) {
			flushbits(1);
			return 3;
		}
		flushbits(MCBPCtabIntra[(code<<1)+1]);
		return MCBPCtabIntra[code<<1];
	}
	else
	{
		if (code == 0)	return -1;
		if (code >= 256)
		{
			flushbits(1);
			return 0;
		}
		flushbits(MCBPCtabInter[(code<<1)+1]);
		return MCBPCtabInter[code<<1];
	}
}

/**/

static short getCBPY()
{

	short code;
	
	code = showbits(6);

	if (code < 2) return -1;
	if (code >= 48) {
		flushbits(2);
		code = 15;
	} 
	else {
		flushbits(CBPYtab[(code<<1)+1]);
		code = CBPYtab[code<<1];
	}
	if (!((mp4_hdr.derived_mb_type == 3) ||	(mp4_hdr.derived_mb_type == 4)))
		  code = 15 - code;
	return code;
}






