/*********************************************************************
*	COggVideoPlayer
*	SOURCE FILE
*	Autor:	Michal Jirouš
*	Datum: 16.7.2008
*	Soubor: oggvideoplayer.cpp
*	Popis: Prehravac ogg video souboru
**********************************************************************/

#include "oggvideoplayer.h"
#include "sys_console.h"

using namespace systemConsole;

int COggVideoPlayer::queue_page( ogg_page *page )
{
	if( theora_p )
		ogg_stream_pagein( &m_OggStreamState, page);
	return 0;
}

int COggVideoPlayer::buffer_data(FILE *in,ogg_sync_state *m_OggSyncState)
{
	char *buffer = ogg_sync_buffer( m_OggSyncState, 4096);
	long bytes = (long)fread( buffer, 1, 4096, in);
	ogg_sync_wrote( m_OggSyncState, bytes);
	return bytes;
}

int COggVideoPlayer::dump_comments(theora_comment *m_TheoraComment)
{
	/*int i, len;
	char *value;

	printf("Encoded by %s\n",m_TheoraComment->vendor);
	if( m_TheoraComment->comments )
	{
		printf("theora comment header:\n");
		for( i = 0; i < m_TheoraComment->comments; i++)
		{
			if( m_TheoraComment->user_comments[i])
			{
				len = m_TheoraComment->comment_lengths[i];
				value = (char*)malloc(len+1);
				memcpy( value, m_TheoraComment->user_comments[i], len);
				value[len]='\0';
				printf( "\t%s\n", value);
				free( value );
			}
		}
	}*/
	return 0;
}

void COggVideoPlayer::video_write(void)
{
	int i;
	yuv_buffer yuv;
	int crop_offset;
	theora_decode_YUVout(&m_TheoraState,&yuv);

	/* Lock SDL_yuv_overlay */
	if ( SDL_MUSTLOCK( m_pMainSurface ) ) {
	if ( SDL_LockSurface( m_pMainSurface ) < 0 ) return;
	}
	if (SDL_LockYUVOverlay(m_pYUV_overlay) < 0) return;

	/* let's draw the data on a SDL screen (*screen) */
	/* deal with border stride */
	/* reverse u and v for SDL */
	/* and crop input properly, respecting the encoded frame rect */
	/* problems may exist for odd frame rect for some encodings */
	crop_offset=m_TheoraInfo.offset_x+yuv.y_stride*m_TheoraInfo.offset_y;
	for(i=0;i<m_pYUV_overlay->h;i++)
	memcpy(m_pYUV_overlay->pixels[0]+m_pYUV_overlay->pitches[0]*i,
		   yuv.y+crop_offset+yuv.y_stride*i,
		   m_pYUV_overlay->w);
	crop_offset=(m_TheoraInfo.offset_x/2)+(yuv.uv_stride)*(m_TheoraInfo.offset_y/2);
	for(i=0;i<m_pYUV_overlay->h/2;i++){
	memcpy(m_pYUV_overlay->pixels[1]+m_pYUV_overlay->pitches[1]*i,
		   yuv.v+crop_offset+yuv.uv_stride*i,
		   m_pYUV_overlay->w/2);
	memcpy(m_pYUV_overlay->pixels[2]+m_pYUV_overlay->pitches[2]*i,
		   yuv.u+crop_offset+yuv.uv_stride*i,
		   m_pYUV_overlay->w/2);
	}

	/* Unlock SDL_yuv_overlay */
	if ( SDL_MUSTLOCK( m_pMainSurface ) )
		SDL_UnlockSurface( m_pMainSurface );

	SDL_UnlockYUVOverlay(m_pYUV_overlay);


	//Pro OpenGL se zde delala konverze na RGB

	/*for(int i=0; i < yuv.y_height; i++)
	{
		for( int j = 0; j < yuv.y_width; j++ )
		{
			int Y_index = yuv.y_stride*i+j;
			int V_index = yuv.uv_stride*(i/2) + (j/2);
			int U_index = V_index;
			int r_t, g_t, b_t;
			r_t = (int)(1.164*( (double)yuv.y[ Y_index  ] - 16.0) + 1.596*( (double)yuv.v[ V_index ] - 128.0));
			g_t = (int)(1.164*( (double)yuv.y[ Y_index  ] - 16.0) - 0.813*( (double)yuv.v[ V_index ] - 128.0) - 0.391*( (double)yuv.u[ U_index ] - 128.0));
			b_t = (int)(1.164*( (double)yuv.y[ Y_index  ] - 16.0) + 2.018*( (double)yuv.u[ U_index ] - 128.0));

			if( r_t > 255 ) r_t = 255;
			if( r_t < 0 ) r_t = 0;
			if( g_t > 255 ) g_t = 255;
			if( g_t < 0 ) g_t = 0;
			if( b_t > 255 ) b_t = 255;
			if( b_t < 0 ) b_t = 0;

			m_pVideoData[( (yuv.y_height - 1 - i) * yuv.y_width + j) * 3 ] = r_t;
			m_pVideoData[( (yuv.y_height - 1 - i) * yuv.y_width + j) * 3 + 1 ] = g_t;
			m_pVideoData[( (yuv.y_height - 1 - i) * yuv.y_width + j) * 3 + 2 ] = b_t;
		}
	}*/
}

COggVideoPlayer::COggVideoPlayer()
{
	m_pMainSurface = NULL;
	m_pYUV_overlay = NULL;

	infile = NULL;
	theora_p = 0;
	stateflag = 0;

	/* single frame video buffering */
	videobuf_ready = 0;
	videobuf_granulepos = -1;
	videobuf_time = 0;
}

bool COggVideoPlayer::loadFile( const char * filename )
{
	if( !m_pMainSurface )
		return false;

	errno_t error ;
	error = fopen_s( &infile, filename, "rb");
	if( error != 0 )
		return false;

	ogg_sync_init( &m_OggSyncState );
	theora_comment_init( &m_TheoraComment );
	theora_info_init( &m_TheoraInfo );

	/* Only interested in Theora streams */
	while( !stateflag )
	{
		int ret=buffer_data( infile, &m_OggSyncState );
		if( ret == 0 )
			break;
		while( ogg_sync_pageout( &m_OggSyncState, &m_OggPage ) > 0 )
		{
			ogg_stream_state test;

			/* is this a mandated initial header? If not, stop parsing */
			if( !ogg_page_bos( &m_OggPage ) )
			{
				/* don't leak the page; get it into the appropriate stream */
				queue_page( &m_OggPage );
				stateflag = 1;
				break;
			}

			ogg_stream_init( &test, ogg_page_serialno( &m_OggPage ) );
			ogg_stream_pagein( &test, &m_OggPage);
			ogg_stream_packetout( &test, &m_OggPacket);

			/* identify the codec: try theora */
			if( !theora_p && theora_decode_header( &m_TheoraInfo, &m_TheoraComment, &m_OggPacket ) >= 0 )
			{
				/* it is theora -- save this stream state */
				memcpy( &m_OggStreamState, &test, sizeof( test ) );
				theora_p = 1;
			}else{
				/* whatever it is, we don't care about it */
				ogg_stream_clear( &test );
			}
		}
		/* fall through m_OggStreamState non-bos page parsing */
	}

	/* we're expecting more header packets. */
	while( theora_p && theora_p < 3 )
	{
		int ret;

		/* look for further theora headers */
		while( theora_p && (theora_p<3) && ( ret = ogg_stream_packetout( &m_OggStreamState, &m_OggPacket ) ) )
		{
			if( ret < 0 )
			{
				console << "Error parsing Theora stream headers; corrupt stream?" << endline;
				return false;
			}
			if( theora_decode_header( &m_TheoraInfo, &m_TheoraComment, &m_OggPacket ) )
			{
				console << "Error parsing Theora stream headers; corrupt stream?" << endline;
				return false;
			}
			theora_p++;
			if( theora_p == 3 )
				break;
		}

		/* The header pages/packets will arrive before anything else we
		care about, or the stream is not obeying spec */

		if( ogg_sync_pageout( &m_OggSyncState, &m_OggPage ) > 0)
			queue_page(&m_OggPage); /* demux into the appropriate stream */
		else
		{
			int ret = buffer_data( infile, &m_OggSyncState ); /* someone needs more data */
			if( ret == 0 )
			{
				console << "End of file while searching for codec headers." << endline;
				return false;
			}
		}
	}
	dump_comments( &m_TheoraComment );

	/* and now we have it all.  initialize decoders */
	if( theora_p )
		theora_decode_init( &m_TheoraState, &m_TheoraInfo );
	else
	{
		/* tear down the partial theora setup */
		theora_info_clear( &m_TheoraInfo );
		theora_comment_clear( &m_TheoraComment );
	}

	stateflag=0; /* playback has not begun */
	/* queue any remaining pages from data we buffered but that did not
	contain headers */
	while( ogg_sync_pageout( &m_OggSyncState, &m_OggPage ) > 0 )
	{
		queue_page( &m_OggPage );
	}

	m_pVideoData = new unsigned char[m_TheoraInfo.width * m_TheoraInfo.height * 3];
	if( m_pVideoData == NULL )
		return false;

	m_uiVideoWidth = m_TheoraInfo.width;
	m_uiVideoHeight = m_TheoraInfo.height;


	if(m_pYUV_overlay)
		SDL_FreeYUVOverlay(m_pYUV_overlay);


	m_pYUV_overlay = SDL_CreateYUVOverlay(m_TheoraInfo.width, m_TheoraInfo.height, SDL_YV12_OVERLAY, m_pMainSurface);
	m_Rect.h = m_TheoraInfo.height;
	m_Rect.w = m_TheoraInfo.width;
	m_Rect.x = (m_pMainSurface->w / 2) - (m_TheoraInfo.width  / 2 );
	m_Rect.y = (m_pMainSurface->h / 2) - (m_TheoraInfo.height / 2 );

	return true;
}

void COggVideoPlayer::draw()
{
	SDL_DisplayYUVOverlay( m_pYUV_overlay, &m_Rect);
}

//nacteni noveho frame ze souboru
bool COggVideoPlayer::update(  )
{
	bool updated = false;
	while( !updated)
	{

		while(theora_p && !videobuf_ready)
		{
			/* theora is one in, one out... */
			if( ogg_stream_packetout( &m_OggStreamState, &m_OggPacket ) > 0 )
			{

				theora_decode_packetin( &m_TheoraState, &m_OggPacket );
				videobuf_granulepos =m_TheoraState.granulepos;
				videobuf_time = theora_granule_time( &m_TheoraState, videobuf_granulepos );
				videobuf_ready = 1;
			}
			else
				break;
		}

		if( !videobuf_ready && feof( infile ) ) 
			return false;	//zadny dalsi frame uz neni = konec

		if( !videobuf_ready )
		{
			/* no data yet for somebody.  Grab another page */
			buffer_data( infile, &m_OggSyncState );
			while( ogg_sync_pageout( &m_OggSyncState, &m_OggPage ) > 0 )
			{
				queue_page( &m_OggPage );
			}
		}

		/* dumpvideo frame, and get new one */
		else 
		{
			updated = true;
			video_write();
		}

		videobuf_ready=0;
	}
	return true;
}


//resetovani pred pouzitim k prehravani dalsiho souboru
void COggVideoPlayer::reset()
{
	if(theora_p)
	{
		ogg_stream_clear( &m_OggStreamState );
		theora_clear( &m_TheoraState );
		theora_comment_clear( &m_TheoraComment );
		theora_info_clear( &m_TheoraInfo );
	}

	ogg_sync_clear( &m_OggSyncState );
	theora_p = 0;
	stateflag = 0;
	videobuf_ready = 0;
	videobuf_granulepos = 0;
	videobuf_time = 0;

	if(m_pYUV_overlay)
	{
		SDL_FreeYUVOverlay(m_pYUV_overlay);
		m_pYUV_overlay = NULL;
	}
	
	if( m_pMainSurface )
	{
		SDL_FillRect( m_pMainSurface, NULL, SDL_MapRGB( m_pMainSurface->format, 0, 0, 0 ) );
		SDL_UpdateRect( m_pMainSurface, 0, 0, m_pMainSurface->w, m_pMainSurface->h );
	}
}
