/*
	This is basically dreamroq, as provided by Mike Melanson and updated by Josh Pearson.
	All I did was write some weird, very suboptimal audio callback, for the one in the sample player did not work for me at first
	which actually was an error on my part, i tried to play a 30 fps video while using the video delay with a value of 25 fps ;-)
	Despite this i used my version of the audio callback anyway, even so its way worse than the original.
	So all credits for this go to the two guys mentioned earlier, thank you very, very much ;-)
	(Of course, i take all the blame for any errors made, and there propably are a lot of them ;-))
*/
extern "C"
{
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>

	#include "dreamroqlib/dreamroqlib.h"
}

namespace kos_gfx
{
	namespace fmv
	{
		#define PCM_BUF_SIZE 1024*1024
		unsigned char *pcm_buf = NULL;
		int pcm_size = 0;
		snd_stream_hnd_t stream_handle=SND_STREAM_INVALID;
		bool audio_ready=false;
		bool done=true, thread_quit=false;
		
		pvr_ptr_t textures[2]={NULL,NULL};
		int current_frame=0;
		float video_delay=0;
		bool video_ready=false;
		int frame=0;
		const float VIDEO_RATE = 30.0f;
		
		int dc_get_time()
		{
			uint32 s, ms;
			uint64 msec;
			timer_ms_gettime(&s, &ms);
			msec = (((uint64)s) * ((uint64)1000)) + ((uint64)ms);
			return (int)msec;
		}
 
		void frame_delay(float AVI_video_rate, float AVI_delay, int frameCounter )
		{    
				float AVI_real_time =  frameCounter / AVI_video_rate;
				float CPU_real_time= ( ( (float)dc_get_time()- AVI_delay ) / 1000.0f );          
				while(CPU_real_time < AVI_real_time)
				{
					  CPU_real_time= ( ( (float)dc_get_time()- AVI_delay ) / 1000.0f );
					  thd_pass();
				};
		}
		
		void* stream_callback(snd_stream_hnd_t hnd,int smp_req, int *smp_recv)
		{
			static int last_pos=0, last_request=0;
			
			if(pcm_size-last_pos>=0 && last_pos!=0)
			{
				memcpy(pcm_buf,pcm_buf+last_pos,pcm_size-last_pos);
				pcm_size-=last_pos;
			}
			else if(last_pos>pcm_size)
			{
				printf("There seems to be a rather sinister issue here, better shut down ;-)\n");
				fflush(stdout);
				exit(1);
			};
			
			last_pos=smp_req>pcm_size?pcm_size:smp_req;
			if(smp_req>pcm_size)
			{
				if(last_request!=0)
				{
					//fill with silence,we apparently are too slow anyway ;_;;
					memset(pcm_buf+pcm_size,0,smp_req-pcm_size);
					last_request=0;
					printf("silence used ;-)\n");fflush(stdout);
				}
				else
				{
					*smp_recv=0;
					last_pos=0;
					last_request=smp_req;
					return pcm_buf;
				};
			}
			else
				last_request=0;
			
			*smp_recv=smp_req;
			return pcm_buf;
		};
		
		void* snd_thd(void* dat)
		{
			while(!done && snd_stream_poll(stream_handle)==0)
				thd_sleep(40);
			thread_quit=true;
			thd_exit(0);
		};
		
		int render_cb(unsigned short *buf, int width, int height, int stride,int texture_height)
		{
			pvr_poly_cxt_t cxt;
			static pvr_poly_hdr_t hdr[2];
			static pvr_vertex_t vert[4];

			float ratio;
			/* screen coordinates of upper left and bottom right corners */
			static int ul_x, ul_y, br_x, br_y;

			if(!video_ready)
			{  
				textures[0] = pvr_mem_malloc(stride * texture_height * 2);
				textures[1] = pvr_mem_malloc(stride * texture_height * 2);
				/* Precompile the poly headers */
				pvr_poly_cxt_txr(&cxt, PVR_LIST_OP_POLY, PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED, stride, texture_height, textures[0], PVR_FILTER_TRILINEAR2);
				pvr_poly_compile(&hdr[0], &cxt);
				pvr_poly_cxt_txr(&cxt, PVR_LIST_OP_POLY, PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED, stride, texture_height, textures[1], PVR_FILTER_TRILINEAR2);
				pvr_poly_compile(&hdr[1], &cxt);

				/* this only works if width ratio <= height ratio */
				ratio = 640.0 / width;
				ul_x = 0;
				br_x = (ratio * stride);
				ul_y = ((480 - ratio * height) / 2);
				br_y = ul_y + ratio * texture_height;

				/* Things common to vertices */
				vert[0].z     = vert[1].z     = vert[2].z     = vert[3].z     = 1.0f; 
				vert[0].argb  = vert[1].argb  = vert[2].argb  = vert[3].argb  = PVR_PACK_COLOR(1.0f, 1.0f, 1.0f, 1.0f);    
				vert[0].oargb = vert[1].oargb = vert[2].oargb = vert[3].oargb = 0;  
				vert[0].flags = vert[1].flags = vert[2].flags = PVR_CMD_VERTEX;         
				vert[3].flags = PVR_CMD_VERTEX_EOL; 

				vert[0].x = ul_x;
				vert[0].y = ul_y;
				vert[0].u = 0.0;
				vert[0].v = 0.0;

				vert[1].x = br_x;
				vert[1].y = ul_y;
				vert[1].u = 1.0;
				vert[1].v = 0.0;

				vert[2].x = ul_x;
				vert[2].y = br_y;
				vert[2].u = 0.0;
				vert[2].v = 1.0;

				vert[3].x = br_x;
				vert[3].y = br_y;
				vert[3].u = 1.0;
				vert[3].v = 1.0;
				video_delay=(float)dc_get_time();
				video_ready=true;
			};

			/* send the video frame as a texture over to video RAM */
			pvr_txr_load(buf, textures[current_frame], stride * texture_height * 2);
					  
			/* Delay the frame to match Frame Rate */
			frame_delay( VIDEO_RATE, video_delay, ++frame );

			pvr_wait_ready();
			pvr_scene_begin();
			pvr_list_begin(PVR_LIST_OP_POLY);

			pvr_prim(&hdr[current_frame], sizeof(pvr_poly_hdr_t));
			pvr_prim(&vert[0], sizeof(pvr_vertex_t));
			pvr_prim(&vert[1], sizeof(pvr_vertex_t));
			pvr_prim(&vert[2], sizeof(pvr_vertex_t));
			pvr_prim(&vert[3], sizeof(pvr_vertex_t));

			pvr_list_finish();
			pvr_scene_finish();

			if (current_frame)
				current_frame = 0;
			else
				current_frame = 1;

			return ROQ_SUCCESS;
		}

		int audio_cb( unsigned char *buf, int size, int channels)
		{
			memcpy(pcm_buf+pcm_size, buf, size);
			pcm_size += size;
			if(pcm_size>=PCM_BUF_SIZE)
			{
				printf("Not enough memory to buffer decoded sound ;_; ;-)\n");
				fflush(stdout);
				exit(1);
			};
			
			if(!audio_ready)
			{
				snd_stream_start(stream_handle,22050,1);
				thd_create(1,snd_thd,NULL);
				audio_ready=true;
			};
			
			return ROQ_SUCCESS;    
		};
		
		void init()
		{
			//audio:
			pcm_buf=(unsigned char*)malloc(PCM_BUF_SIZE);
			if(pcm_buf==NULL)
				exit(1);
			
			snd_stream_init();
			stream_handle=snd_stream_alloc(stream_callback,4096*2);
			
			done=false;
			thread_quit=false;
		};
		
		void shutdown()
		{
			//audio:
			//wait for snd_thd to finish ;-), if audio_ready==false, no thread was created in the first place ;-);
			done=true;
			while(!thread_quit && audio_ready)
				thd_pass();
			
			//this is just to force the callback to set its static member last_pos to 0 ;-)
			//really aweful, sorry ;-);
			stream_callback(stream_handle,0,&pcm_size);
			
			if(stream_handle!=SND_STREAM_INVALID)
			{
				snd_stream_stop(stream_handle);
				snd_stream_destroy(stream_handle);
				stream_handle=SND_STREAM_INVALID;
			};
			snd_stream_shutdown();
			
			free(pcm_buf);
			pcm_buf=NULL;
			
			audio_ready=false;
			
			//video:
        	if(textures[0]!=NULL)
        	{
        		pvr_mem_free(textures[0]);
        		textures[0]=NULL;
        	};
        	
        	if(textures[1]!=NULL)
        	{
				pvr_mem_free(textures[1]);
				textures[1]=NULL;
			};
	        video_ready=false;
	        
    		frame=0;
    		current_frame=0;
    		pcm_size=0;
    		video_delay=0;
		};
		
		void play(std::string filename)
		{
    		printf("dreamroq_play(C) Multimedia Mike Melanson & Josh PH3NOM Pearson 2011\n");
    		int status=dreamroq_play(filename.c_str(),0,render_cb,audio_cb,NULL);
    		printf("dreamroq_play() status = %d\n", status);
			
    		shutdown();
    		init();
		};
	};
};
