/*
 * This is a simple program to test the multi-core microblaze SoC.
 */

#include <stdio.h>


#include "sysace_stdio.h"

#include "DecTest.h"

#include "xil_exception.h"
#include "xintc.h"
#include "xtmrctr.h"


/* global variable */
AVCHandle *mHandle=NULL;
//int res_allocate[THREAD_COUNT];
//int thr_ready[THREAD_COUNT];



uint8 **intra_pred_top;
uint8 **intra_pred_top_cb;
uint8 **intra_pred_top_cr;



static int32_t Malloc(void *userData, int32_t size, int32_t attrs) {
    return (int32_t)(smp_malloc(size));
}

static void Free(void *userData, int32_t ptr) {
    free((void *)(ptr));
}



static XIntc xIntc;
static XTmrCtr xTickTimer;
volatile int tick_cnt;


int setupInterruptController() {

	XStatus status;
	int xReturn = 0;

	/* initialize the interrupt controller */
	status = XIntc_Initialize(&xIntc, XPAR_XPS_INTC_0_DEVICE_ID);

	if (status == XST_SUCCESS) {
	    /* configure the ISR to service all interrupts when a single interrupt occurs */
	    XIntc_SetIntrSvcOption(xIntc.BaseAddress, XIN_SVC_ALL_ISRS_OPTION);
	    /* start the interrupt controller, note that this doesn't actually enable interrupts */
	    status = XIntc_Start(&xIntc, XIN_REAL_MODE);
	    if (status == XST_SUCCESS) {
			xReturn = 1;
	    }
	}
	return xReturn;

}

void enableTimerInterrupt() {

	XIntc_Enable(&xIntc, XPAR_XPS_INTC_0_XPS_TIMER_0_INTERRUPT_INTR);
	microblaze_enable_interrupts();

}

int connectInterruptHandler() {

	XStatus status;
	int xReturn = 0;

	/* pass the handler information to the interrupt controller */
	status = XIntc_Connect(&xIntc, XPAR_XPS_INTC_0_XPS_TIMER_0_INTERRUPT_INTR, XTmrCtr_InterruptHandler, &xTickTimer);

	if (status == XST_SUCCESS) {
		/* enable the connected interrupt within the interrupt controller */
		enableTimerInterrupt();
		xReturn = 1;
	}

	return xReturn;

}


void _TimerHandler(void *CallBackRef, u8 TmrCtrNumber) {

	tick_cnt++;

}

int setupTickTimer() {

	XStatus status;
	int xReturn = 0;
	/* initialize the tick timer to periodically generate an interrupt */
	status = XTmrCtr_Initialize(&xTickTimer, XPAR_XPS_TIMER_0_DEVICE_ID);

	if (status == XST_SUCCESS) {
		XTmrCtr_SetOptions(&xTickTimer, 0, XTC_INT_MODE_OPTION | XTC_AUTO_RELOAD_OPTION | XTC_DOWN_COUNT_OPTION);
		XTmrCtr_SetResetValue(&xTickTimer, 0, ( ( unsigned long ) XPAR_XPS_TIMER_0_CLOCK_FREQ_HZ ) / ( ( unsigned long ) 100));

		/* register and enable the timer/counter handler with the interrupt controller,
		 * then register our tick handler with the specific timer/counter */
		XTmrCtr_SetHandler(&xTickTimer, &_TimerHandler, &xTickTimer);
		//xReturn = connectInterruptHandler(XPAR_XPS_INTC_2_XPS_TIMER_1_INTERRUPT_INTR, XTmrCtr_InterruptHandler, &xTickTimer);
		xReturn = connectInterruptHandler();

		if (xReturn == 1) {
			/* start the timer counting */
			XTmrCtr_Start(&xTickTimer, 0);
			xReturn = 1;
		}
	}

	return xReturn;

}


int waveFront()
{


    tick_cnt = 0;

    setupInterruptController();
    setupTickTimer();


    FILE *in_fd,*out_fd;

    char *fileName = "a:\\f1.264";

    //AVCHandle *mHandle=NULL;
	int mSPSSeen,mPPSSeen;
	int H264_Size,H264_Offset;
	uint8_t *H264_File;

    in_fd = sysace_fopen (fileName ,"r" );

    mutex_lock(APP_MUTEX_NUM);
	printf("input_filename %s \n",fileName);
	mutex_unlock(APP_MUTEX_NUM);


	H264_File = (unsigned char *) smp_malloc (3145728);

	H264_Size = sysace_fread(H264_File,3145728,1,in_fd);

    sysace_fclose(in_fd);

    out_fd= sysace_fopen("o3.yuv","w");


	mHandle=smp_malloc(sizeof(AVCHandle));

	AVCDecInit( mHandle , &mSPSSeen , &mPPSSeen , &H264_Offset );

	const uint8_t *NALUnit;
	int NALSize,Read_res=0,Dec_res=0;

	printf("Start decode \n");

//	mutex_unlock(INITIAL_MUTEX);

	/* lock each row */
	int i;
	for( i = 0 ; i < ROW_MUTEX_LENGTH ; i++){
		mutex_lock(i+ROW_MUTEX_BASE);
	}

	int* slice_end_flag;
	slice_end_flag = BR_FLAG_ADDR;
	*slice_end_flag = 0;


	int start_time = tick_cnt;


	while(H264_File!=NULL)
	{
		Read_res=ReadNALUnit( H264_File , H264_Offset , H264_Size , &NALUnit , &NALSize );

		if(Read_res==-1)
			break;//fali
		Dec_res=DecNALUnit (mHandle,NALUnit,NALSize,&mSPSSeen,&mPPSSeen);
		if(Dec_res== -1)
		{
			break;//fali
		}else if(Dec_res ==0 ) // Dec_res == 1 , OK
		{				       // next NAL
			if (NALSize + 4 == H264_Size)
			{
				free(H264_File);
				H264_File = NULL;
			} else
			{
				H264_Offset = H264_Offset+NALSize+4;
				H264_Size = H264_Size-NALSize-4;
			}

		}else if (Dec_res ==1) // Dec_res ==1
		{                     // get YUV data to output_filename
			int index;
			int Release;
			AVCFrameIO Output;
			Output.YCbCr[0] = Output.YCbCr[1] = Output.YCbCr[2] = NULL;

			AVCDec_Status status = PVAVCDecGetOutput(mHandle, &index, &Release, &Output);
#ifdef dumpFile
			sysace_fwrite(Output.YCbCr[0],Output.pitch*Output.height,1,out_fd);
			sysace_fwrite(Output.YCbCr[1],(Output.pitch/2)*(Output.height/2),1,out_fd);
			sysace_fwrite(Output.YCbCr[2],(Output.pitch/2)*(Output.height/2),1,out_fd);
#endif
			if (status != AVCDEC_SUCCESS)
			{
				mutex_lock(APP_MUTEX_NUM);
				printf("PVAVCDecGetOutput returned error %d\n", status);
				mutex_unlock(APP_MUTEX_NUM);
				break;
			}
		}
	}


	// get last YUV data
	AVCDec_Status status =AVCDEC_SUCCESS;
	while(status==AVCDEC_SUCCESS){
		int index;
		int Release;
		AVCFrameIO Output;
		Output.YCbCr[0] = Output.YCbCr[1] = Output.YCbCr[2] = NULL;
		AVCDec_Status status = PVAVCDecGetOutput(mHandle, &index, &Release, &Output);
		if (status != AVCDEC_SUCCESS) {
			break;
		}

#ifdef dumpFile
		sysace_fwrite(Output.YCbCr[0],Output.pitch*Output.height,1,out_fd);
		sysace_fwrite(Output.YCbCr[1],(Output.pitch/2)*(Output.height/2),1,out_fd);
		sysace_fwrite(Output.YCbCr[2],(Output.pitch/2)*(Output.height/2),1,out_fd);
#endif

	}

	printf("time : %d\n",tick_cnt-start_time);



	if(H264_File!=NULL)
		free (H264_File);
	sysace_fclose (out_fd);
	system("pause");
	free(mHandle);

	mutex_lock(APP_MUTEX_NUM);
	printf("Decode Over \n");
	mutex_unlock(APP_MUTEX_NUM);


    return 0;
}

void AVCDecInit(AVCHandle * mHandle,int *mSPSSeen,int *mPPSSeen,int * H264_Offset){

	memset(mHandle, 0, sizeof(AVCHandle));
    mHandle->AVCObject = NULL;
    mHandle->userData = NULL;
    //mHandle->CBAVC_DPBAlloc = ActivateSPSWrapper; //those function for android 2.3 use
    //mHandle->CBAVC_FrameBind = BindFrameWrapper;  //if #define  PV_MEMORY_POOL
    //mHandle->CBAVC_FrameUnbind = UnbindFrame;     //  will need those function;
    mHandle->CBAVC_Malloc = Malloc;                 // if u want how to implement those function
    mHandle->CBAVC_Free = Free;                     // u can see AVCDecoder.cpp
	*mSPSSeen = 0;
    *mPPSSeen = 0;
	*H264_Offset=0;
}

int ReadNALUnit(const uint8_t * H264_File , int H264_Offset ,int H264_Size ,const uint8_t ** NALUnit ,int *NALSize ){

	const uint8_t *data =
        (const uint8_t *)H264_File + H264_Offset;

	int size = H264_Size;

	if(size < 4){
		return -1 ; //fail
	}
	if(memcmp(kStartCode, data, 4)){
		return -1 ; //fail
	}

	int offset = 4;
    while (offset + 3 < size && memcmp(kStartCode, &data[offset], 4)) {//find next NAL startcode
        ++offset;
    }

    *NALUnit = &data[4];
    if (offset + 3 >= size) {
        *NALSize = size - 4;
    } else {
        *NALSize = offset - 4;
    }
	return 1 ;

}

int DecNALUnit (AVCHandle *mHandle,const uint8_t *NALUnit,const int NALSize,int *mSPSSeen,int *mPPSSeen)
{
    int nalType;
    int nalRefIdc;
    AVCDec_Status res =PVAVCDecGetNALType((uint8_t *)(NALUnit), NALSize,&nalType, &nalRefIdc);

    if (res != AVCDEC_SUCCESS) {
    	mutex_lock(APP_MUTEX_NUM);
		printf("cannot determine nal type\n");
		mutex_unlock(APP_MUTEX_NUM);
    } else if (nalType == AVC_NALTYPE_SPS || nalType == AVC_NALTYPE_PPS|| (*mSPSSeen &&* mPPSSeen)) {
        switch (nalType) {
            case AVC_NALTYPE_SPS:
            {
                *mSPSSeen = 1;
                res = PVAVCDecSeqParamSet(
                        mHandle, (uint8_t *)(NALUnit),
                        NALSize);

                if (res != AVCDEC_SUCCESS) {
                	mutex_lock(APP_MUTEX_NUM);
					printf("PVAVCDecSeqParamSet returned error %d\n", res);
                    mutex_unlock(APP_MUTEX_NUM);
					return -1;//fail
                }

                break;
            }

            case AVC_NALTYPE_PPS:
            {
				*mPPSSeen = 1;
                res = PVAVCDecPicParamSet(
                        mHandle, (uint8_t *)(NALUnit),
                        NALSize);

                if (res != AVCDEC_SUCCESS) {
                	mutex_lock(APP_MUTEX_NUM);
                	printf("PVAVCDecPicParamSet returned error %d", res);
                	mutex_unlock(APP_MUTEX_NUM);
                	return -1;
                }
				return 0;//ok,but no data
                break;
            }

            case AVC_NALTYPE_SLICE:
            case AVC_NALTYPE_IDR:
            {
			     res = PVAVCDecodeSlice(
                        mHandle, (uint8_t *)(NALUnit),
                        NALSize);

                if (res == AVCDEC_PICTURE_OUTPUT_READY) {
                   // Do _not_ release input buffer yet.
					return 1;
                    break;
                }

                if (res == AVCDEC_PICTURE_READY || res == AVCDEC_SUCCESS) {

                } else {
                	mutex_lock(APP_MUTEX_NUM);
					printf("PVAVCDecodeSlice returned error %d", res);
					mutex_unlock(APP_MUTEX_NUM);
					return -1;
				}
                break;
            }

            case AVC_NALTYPE_SEI:
            {
//                res = PVAVCDecSEI(
//                        mHandle, (uint8_t *)(NALUnit),
//                        NALSize);

                if (res != AVCDEC_SUCCESS) {
                    return -1;
                }

                break;
            }

            case AVC_NALTYPE_AUD:
            case AVC_NALTYPE_FILL:
            case AVC_NALTYPE_EOSEQ:
			{

                break;
            }

            default:
            {
              //  LOGE("Should not be here, unknown nalType %d", nalType);
              //  CHECK(!"Should not be here");
				return -1;
                break;
            }
        }
    } else {
        // We haven't seen SPS or PPS yet.
    }
	return 0;//ok,but no data

}

int thr_decode_one_row(int arg) {

	int thr_index = arg-1;


	int row_mutex_num[7];
	int mutex_num = 6;
	int mutex_count = 0;
	int i;

	switch (arg){
	case 1:
		for(i=0;i<6;i++)
			row_mutex_num[i] = ROW_MUTEX_BASE+3*i;
			row_mutex_num[6] = ROW_MUTEX_BASE+ROW_MUTEX_LENGTH;
		break;
	case 2:
		for(i=0;i<6;i++)
			row_mutex_num[i] = ROW_MUTEX_BASE+1+3*i;
			row_mutex_num[6] = ROW_MUTEX_BASE+ROW_MUTEX_LENGTH+1;
		break;
	case 3:
		for(i=0;i<6;i++)
			row_mutex_num[i] = ROW_MUTEX_BASE+2+3*i;
			row_mutex_num[6] = ROW_MUTEX_BASE+ROW_MUTEX_LENGTH+2;
		break;
	default:
		mutex_lock(APP_MUTEX_NUM);
		printf("wrong core id !! \n");
		mutex_unlock(APP_MUTEX_NUM);
		mutex_num = 0;
		break;
	}
	/* lock for end */


	mutex_lock(1);
	xil_printf("core %d start\n\r",arg);
	mutex_unlock(1);


	while(true){

		/* lock row */
		mutex_lock(row_mutex_num[mutex_count]);




		/* read new info. to decode */
		// row number
		//thr_ready[thr_index] = 0;
		AVCHandle *tmp_mHandle = mHandle;

		AVCDecObject *decvid = (AVCDecObject*) tmp_mHandle->AVCObject;
		AVCCommonObj *video = decvid->common;
		uint CurrMbAddr;
		AVCMacroblock *currMB ;

//		uint PicSizeInMbs = video->PicSizeInMbs;

		// initialize first MB location
		//new , if 640*480 , the first MB location is 0 40 80 120 ... 1160 in each single row
		//CurrMbAddr = res_allocate[thr_index] * video->PicWidthInMbs;
		CurrMbAddr = (row_mutex_num[mutex_count]-ROW_MUTEX_BASE) * video->PicWidthInMbs;
		//end


		int mbNum = (int)video->PicWidthInMbs;


		uint8 tmp_intra_pred_left[17];
		uint8 tmp_intra_pred_left_cb[9];
		uint8 tmp_intra_pred_left_cr[9];

		do{

//			if (CurrMbAddr >= PicSizeInMbs){
//				mutex_lock(APP_MUTEX_NUM);
//				xil_printf("AVCDEC_FAIL\n\r");
//				mutex_unlock(APP_MUTEX_NUM);
//			}


			currMB = &(video->mblock[CurrMbAddr]);

			int checkNeighbor = checkNeighborMB(video,currMB);

			while( !checkNeighbor  ){
				checkNeighbor = checkNeighborMB(video,currMB);
				smp_sleep(500);
			}


			memcpy(currMB->intra_pred_left,tmp_intra_pred_left,sizeof(tmp_intra_pred_left));
			memcpy(currMB->intra_pred_left_cb,tmp_intra_pred_left_cb,sizeof(tmp_intra_pred_left_cb));
			memcpy(currMB->intra_pred_left_cr,tmp_intra_pred_left_cr,sizeof(tmp_intra_pred_left_cr));

			/* read_macroblock and decode_one_macroblock() */


			if(currMB->mbMode == AVC_SKIP ){
				InterMBPrediction(video,currMB);
				currMB->mb_skip_run--;
			}
			else if (currMB->mbMode == AVC_I4 || currMB->mbMode == AVC_I16)
			{
				IntraMBPrediction(video,currMB);
			}
			else
			{
				InterMBPrediction(video,currMB);
			}

			//write back
			memcpy(tmp_intra_pred_left,currMB->intra_pred_left,sizeof(tmp_intra_pred_left));
			memcpy(tmp_intra_pred_left_cb,currMB->intra_pred_left_cb,sizeof(tmp_intra_pred_left_cb));
			memcpy(tmp_intra_pred_left_cr,currMB->intra_pred_left_cr,sizeof(tmp_intra_pred_left_cr));




#ifdef MB_BASED_DEBLOCK

			MBInLoopDeblock(video,currMB); /* MB-based deblocking */

//			if (video->currPicParams->num_slice_groups_minus1 == 0)
//			{
//				MBInLoopDeblock(video,currMB); /* MB-based deblocking */
//			}
//			else    /* this mode cannot be used if the number of slice group is not one. */
//			{
//				mutex_lock(APP_MUTEX_NUM);
//				printf("Deblock - AVCDEC_FAIL\n");
//				mutex_unlock(APP_MUTEX_NUM);
//			}
#endif


			mbNum--;

			currMB->finishDec_flag = 1;

			//Current MB be decoded , enable flag
			CurrMbAddr++;

		}
		while ( mbNum > 0 ); /* even if no more data, but last few MBs are skipped */

		// finish , back to waiting

		int* slice_end_flag;
		slice_end_flag = BR_FLAG_ADDR;

		mutex_unlock(row_mutex_num[mutex_count]);	//finish

		if( (video->PicHeightInMbs-1) == (row_mutex_num[mutex_count]-ROW_MUTEX_BASE)){
			*slice_end_flag = 1;
		}
		//thr_ready[thr_index] = 1;

		mutex_count++;
		if(mutex_count == 6){	// end , wait for reset

			mutex_lock(row_mutex_num[mutex_count]);
			mutex_unlock(row_mutex_num[mutex_count]);
			mutex_count = 0;


		}

	}


	return 0;
}



// if return true , MB is ready
int checkNeighborMB(AVCCommonObj* video , AVCMacroblock* currMB){

	microblaze_invalidate_dcache_range(&video->mblock[currMB->mbAddrB].finishDec_flag,sizeof(int));
	microblaze_invalidate_dcache_range(&video->mblock[currMB->mbAddrC].finishDec_flag,sizeof(int));
	if(currMB->mbAvailB && currMB->mbAvailC){
		if( video->mblock[currMB->mbAddrB].finishDec_flag && video->mblock[currMB->mbAddrC].finishDec_flag )
			return true;
	}
	else if( currMB->mbAvailB && !currMB->mbAvailC ){
		if( video->mblock[currMB->mbAddrB].finishDec_flag )
			return true;
	}
	else
		return true;

	return false;
}



