/***************************************************************************
 *
 *	CRI Middleware SDK
 *
 *	Copyright (c) 2005-2006 CRI-MW
 *
 *	Appli.	: Sofdec Tutorial
 *	Module	: Sofdec Tutorial No.1(Playing a single Sofdec file)
 *	File	: sfd_t01_simple_play.c
 *
 ***************************************************************************/
/***************************************************************************
 *	DESC. : Playing a single Sofdec from memory.
 *
 *	This program is for ...
 *		PLAYSTATION(R)3 Programmer Tool Runtime Library 082.006
 *		DEH-R1000
 *
 *	Please set necessary movie file in home directory of logical
 *	consolle server.
 ***************************************************************************/

/*--- standard ---*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*--- SCE ---*/
#include <sdk_version.h>
#include <PSGL/psgl.h>
#include <PSGL/psglu.h>
#include <sys/process.h>
#include <sys/spu_initialize.h>
#include <sys/sys_time.h>
#include <sys/timer.h>
#include <sys/memory.h>
#include <sys/paths.h>
#include <cell/gcm.h>
#include <cell/cell_fs.h>
#include <cell/audio.h>
#include <cell/mixer.h>
#include <sysutil/sysutil_sysparam.h>
/*--- CRI-MW ---*/
#include <cri_mw.h>

#define DATA_DIR		SYS_APP_HOME "/"
#define SHADER_DIR		SYS_APP_HOME "/"
#define SAMPLE_SFD		DATA_DIR "sample_1280x720_16Mbps_30fps.sfd"
#define SHADERS_BIN		SHADER_DIR "shaders.bin"
#define VERT_PROG		SHADER_DIR "smp_vert.vpo"
#define FRAG_PROG		SHADER_DIR "smp_frag.fpo"
#define USE_HEADPHONE	(1)

Char8			tutor_data_dir[512];			// Data directory
void			*tutot_mem_ptr = NULL;
PSGLdevice		*tutor_psgl_device = NULL;		// PSGL device
CGcontext		tutor_cg_context = NULL;		// Cg context
CGprogram		tutor_vert_prog;				// Cg loaded vertex program
CGprogram		tutor_frag_prog;				// Cg loaded fragment program
GLuint 			tutor_texobj;					// PSGL texture object
GLfloat			tutor_vertex[12] = {
	-1.f,  1.f, 0.f,	 1.f,  1.f, 0.f,	-1.f, -1.f, 0.f,	 1.f, -1.f, 0.f
};
GLfloat 		tutor_tc[8] = {
	0.f, 0.f,	1.f, 0.f,	0.f, 1.f,	1.f, 1.f
};
CellAANHandle	tutor_sur_handle;
Char8 			tutor_texture[1280 * 720 * 4] __attribute__((aligned(128)));

void tutor_init(void);
void tutor_finish(void);
void tutor_init_video(void);
void tutor_finish_video(void);
void tutor_init_sound(void);
void tutor_finish_sound(void);
void tutor_setup_middleware();
void tutor_shutdown_middleware();
void tutor_err_func(void *obj, Char8 *msg);
void *tutor_load_file(const char *fname, char *mem);
int tutor_sound_callback(void *arg, Uint32 counter, Uint32 num_samples);
void tutor_draw_flip(void);

int main(void)
{
	MwsfdInitPrm	iprm;			// Initialization parameter
	MwsfdCrePrm		cprm;			// Creation parameter
	MwsfdFrmObj		frm;			// Decoded movie frame data
	MWPLY			ply;			// Player handle
	Char8			memfile[64];

	// Initialize the PSGL and more
	tutor_init();

	// Setup configurations of the file system, the sound system and the thread mechanism
	tutor_setup_middleware();

	// Initialize the Sofdec library
	memset(&iprm, 0, sizeof(iprm));
	iprm.vhz			= MWSFD_VHZ_60_00;
	iprm.disp_cycle		= 1;
	iprm.disp_latency	= 1;
	iprm.dec_svr		= MWSFD_DEC_SVR_IDLE;
	mwPlyInitSfdFx(&iprm);

	// Create the Sofdec player handle
	memset(&cprm, 0, sizeof(cprm));
	cprm.compo_mode		= MWSFD_COMPO_AUTO;
	cprm.ftype			= MWSFD_FTYPE_SFD;
//	cprm.ftype			= MWSFD_FTYPE_VONLYSFD;
	cprm.max_bps		= 30*1000*1000;
	cprm.max_width		= 1280;
	cprm.max_height		= 720;
	cprm.nfrm_pool_wk	= 5;
	cprm.max_stm		= 1;
	cprm.wksize			= mwPlyCalcWorkCprmSfd(&cprm);
	cprm.work			= (Sint8 *)malloc(cprm.wksize);
	ply = mwPlyCreateSofdec(&cprm);

	// Start playing the Sofdec file
#if 0
	// for HOSTFS, CFS, DISCFS (playback of streaming)
	mwPlyStartFnameLp(ply, SAMPLE_SFD);
#else
	// for PSEUDOFS (playback of memory)
	tutot_mem_ptr = tutor_load_file(SAMPLE_SFD, memfile);
	mwPlyStartFnameLp(ply, memfile);
#endif

	// Wait for PLAYEND of the Sofdec player handle
	for (;;) {
		// Wait for V-sync and decode the image at the same time
		ADXM_WaitVsync();
		// Call the server function
		ADXM_ExecMain();	// Decode the image here when you select MAIN decoding mode

		// Get decoded current movie image
		mwPlyGetCurFrm(ply, &frm);
		if (frm.bufadr != NULL) {
			// Set the size of the output buffer (pitch is equal to texture width)
			mwPlyFxSetOutBufPitchHeight(ply, frm.width * 4, frm.height);
			// Set to load the movie image onto YUVA texture
			mwPlyFxCnvFrmYuva8_PS3(ply, &frm, (Uint8*)tutor_texture);
			// Release the movie image
			mwPlyRelCurFrm(ply);
			// Draw the movie image
			tutor_draw_flip();
		}
		// Check the playing state
		if (mwPlyGetStat(ply) == MWSFD_STAT_PLAYEND) {
			break;
		}
	}

	// Destroy the Sofdec player handle
	mwPlyDestroy(ply);
	free(cprm.work);
	// Cleanup the Sofdec library
	mwPlyFinishSfdFx();

	// Shutdown configurations of the file system, the sound system and the thread mechanism
	tutor_shutdown_middleware();

	// Cleanup DirectSound and more
	tutor_finish();

	return 0;
}

void tutor_init(void)
{
	// Setting for data path
	strcpy(tutor_data_dir, DATA_DIR);

	// Initialize SysUtil
	if (cellSysutilInit() < 0) {
		tutor_err_func(NULL, "cellSysutilInit");
	}

	// Initialize other system device
	tutor_init_video();
	tutor_init_sound();
}

void tutor_finish(void)
{
	if (tutot_mem_ptr != NULL) {
		sys_memory_free((sys_addr_t)tutot_mem_ptr);
	}

	// Finalize other system device
	tutor_finish_sound();
	tutor_finish_video();

	// Exit this thread
	sys_process_exit(0);
}

void tutor_init_video(void)
{
	// Initialize SPU
	sys_spu_initialize(6, 2);

	// Get Video State
	CellVideoResolution resolution;
	CellVideoOutState videoState;
	Uint32 display_width, display_height, color_pitch;
	cellVideoOutGetState(CELL_VIDEO_OUT_PRIMARY, 0, &videoState);
	cellVideoOutGetResolution(videoState.displayMode.resolutionId, &resolution);
	// Configure Video out
	CellVideoOutConfiguration v_config;
	memset(&v_config, 0, sizeof(v_config));
	display_width = resolution.width;
	display_height = resolution.height;
	color_pitch = display_width * 4;	// ARGB: 1pix=4byte
	v_config.resolutionId = videoState.displayMode.resolutionId;
	v_config.format			= CELL_VIDEO_BUFFER_COLOR_FORMAT_X8R8G8B8;
	v_config.pitch			= color_pitch;
	if (cellVideoOutConfigure(CELL_VIDEO_OUT_PRIMARY, &v_config, NULL, 0) < 0) {
		tutor_err_func(NULL, "cellVideoOutConfigure");
	}

	// Initialize PSGL
	PSGLinitOptions options;
	options.enable = PSGL_INIT_MAX_SPUS | PSGL_INIT_INITIALIZE_SPUS;
	options.maxSPUs = 1;
	options.initializeSPUs = GL_FALSE;
	psglInit(&options);

	// Create a PSGL device
	PSGLbufferParameters params = {
		colorBits:24,
		alphaBits:8,
		depthBits:24,
		stencilBits:8,
		deviceType:PSGL_DEVICE_TYPE_AUTO,
		TVStandard:PSGL_TV_STANDARD_HD720P,
		TVFormat:PSGL_TV_FORMAT_YCRCB,
		bufferingMode:PSGL_BUFFERING_MODE_DOUBLE,
		antiAliasing:GL_TRUE
	};
	if ((tutor_psgl_device = psglCreateDevice(&params)) == NULL) {
		tutor_err_func(NULL, "Failed to create a PSGL device.");
		for (;;);
	}

	// Create a PSGL context
	PSGLcontext *tutor_psgl_context;
	if ((tutor_psgl_context = psglCreateContext()) == NULL) {
		tutor_err_func(NULL, "Failed to create a PSGL context.");
		for (;;);
	}
	psglMakeCurrent(tutor_psgl_context, tutor_psgl_device);

#if 0
	psglLoadShaderLibrary(SHADERS_BIN);
	if (glGetError() != GL_NO_ERROR) {
		tutor_err_func(NULL, "Failed to load shaders.bin");
	}
#endif

	// Create a CG context
	tutor_cg_context = cgCreateContext();

	psglResetCurrentContext();

	// Reset the screen
	glViewport(0, 0, display_width, display_height);
	glScissor(0, 0, display_width, display_height);
	glClearColor(0.f, 0.f, 0.f, 1.f);
	glEnable(GL_DEPTH_TEST);
	glFlush();

	// Load vertex and fragment program
	tutor_vert_prog = cgCreateProgramFromFile(tutor_cg_context, 
			CG_BINARY, VERT_PROG, CG_PROFILE_SCE_VP_TYPEC, NULL, NULL);
	tutor_frag_prog = cgCreateProgramFromFile(tutor_cg_context, 
			CG_BINARY, FRAG_PROG, CG_PROFILE_SCE_FP_TYPEC, NULL, NULL);
	if ((tutor_vert_prog == NULL) || (tutor_frag_prog == NULL)) {
		tutor_err_func(NULL, (void*)cgGetErrorString(cgGetError()));
	}

	// Bind and enable the vertex and fragment programs
	cgGLBindProgram(tutor_vert_prog);
	cgGLBindProgram(tutor_frag_prog);
	cgGLEnableProfile(CG_PROFILE_SCE_VP_TYPEC);
	cgGLEnableProfile(CG_PROFILE_SCE_FP_TYPEC);

	// YUVA8
	glGenTextures(1, &tutor_texobj);
	glBindTexture(GL_TEXTURE_2D, tutor_texobj);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1280, 720, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, tutor_texture);

	// Set to Cg parameter
	cgGLSetTextureParameter(cgGetNamedParameter(tutor_frag_prog, "diffuseMapY"), tutor_texobj);
	cgGLSetStateMatrixParameter(cgGetNamedParameter(tutor_vert_prog, "ModelViewProj"),
			CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetStateMatrixParameter(cgGetNamedParameter(tutor_vert_prog, "ModelView"),
			CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetStateMatrixParameter(cgGetNamedParameter(tutor_vert_prog, "ModelViewIT"),
			CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE_TRANSPOSE);
	cgGLSetParameterPointer(cgGetNamedParameter(tutor_vert_prog, "Pobject"), 
			3, GL_FLOAT, 0, tutor_vertex);
	cgGLSetParameterPointer(cgGetNamedParameter(tutor_vert_prog, "YTexUV"), 
			2, GL_FLOAT, 0, tutor_tc);

	// create vertex buffer
	GLuint vbo;
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(tutor_vertex), NULL, GL_STREAM_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(tutor_vertex), tutor_vertex);
	glEnableClientState(GL_VERTEX_ARRAY);

	// set the background color
	glClearColor(0.f, 0.f, .120f, 1.f);


	glEnable(GL_TEXTURE_2D);
}

void tutor_finish_video(void)
{
	PSGLcontext *tutor_psgl_context = psglGetCurrentContext();
	if (tutor_psgl_context != NULL) {
		psglDestroyContext(tutor_psgl_context);
	}

	psglDestroyDevice(tutor_psgl_device);
	psglExit();
}

void tutor_init_sound(void)
{
	CellAudioOutConfiguration a_config;
	CellSurMixerConfig sur_mixer_config;

	/* audio configuration */
	memset(&a_config, 0, sizeof(a_config));
	a_config.channel	= 2;
	a_config.downMixer	= CELL_AUDIO_DOWNMIXER_NONE;
	if (cellAudioOutConfigure(CELL_AUDIO_OUT_PRIMARY, &a_config, NULL, 0) < 0) {
		tutor_err_func(NULL, "cellAudioOutConfigure");
	}

	// Initialize AudioServer
	if (cellAudioInit() < 0){
		tutor_err_func(NULL, "cellAudioInit");
	}

	/* ### NOTE ###															*/
	/* The ADX sound system uses the CellSurMixer(libmixer).				*/
	/* You have to configure and create the CellSurMixer.					*/

	/* SurMixer configuration */
	memset(&sur_mixer_config, 0, sizeof(sur_mixer_config));
	sur_mixer_config.priority	= 400;
	sur_mixer_config.chStrips1	= 0;
	sur_mixer_config.chStrips2	= 0;
	sur_mixer_config.chStrips6	= 0;
	sur_mixer_config.chStrips8	= 0;

	/* Create SurMixer */
	if (cellSurMixerCreate(&sur_mixer_config) < 0) {
		tutor_err_func(NULL, "cellSurMixerCreate");
	}

	/* ### NOTE ###															*/
	/* If application does not input audio data to SurMixer, you can set	*/
	/* ADXPS3_SoundNotifyCallback() to SurMixer directly.					*/
	/* Set notify callback */
	if (cellSurMixerSetNotifyCallback(tutor_sound_callback, NULL) < 0) {
		tutor_err_func(NULL, "cellSurMixerSetNotifyCallback");
	}

#if (USE_HEADPHONE == 1)
	printf("Set TotalLevel for Headphone\n");
	/* Set total level for headphone */
	if (cellSurMixerSetParameter(CELL_SURMIXER_PARAM_TOTALLEVEL, -24.0) < 0) {
		tutor_err_func(NULL, "cellSurMixerSetParameter");
	}
#endif

	return;
}

void tutor_finish_sound(void)
{
	if (cellSurMixerFinalize() < 0) {
		tutor_err_func(NULL, "cellSurMixerFinalize");
	}

	// Finalize AudioServer
	if (cellAudioQuit() < 0) {
		tutor_err_func(NULL, "cellAudioQuit");
	}
}

void tutor_setup_middleware(void)
{
	Adxps3SoundConfig config;

	/* Get SurMixer handle */
	if (cellSurMixerGetAANHandle(&tutor_sur_handle) < 0){
		tutor_err_func(NULL, "cellSurMixerGetAANHandle");
	}
	// Initialize configuraion
	memset(&config, 0, sizeof(config));
	config.mixer_handle = tutor_sur_handle;
//	config.mixer_handle = NULL;	// NULL handle for no audio playback
	config.num_output_channels = 2;
	config.num_max_voices = 8;
	config.work_size = ADXPS3_CalcSoundWorkSize(&config);
	config.work = malloc(config.work_size);
	if (config.work == NULL) {
		tutor_err_func(NULL, "Failed to allocate memory for sound system");
		return;
	}

	// Set up middleware sound system
	ADXPS3_SetupSound(&config);

	// Set up middleware file streaming I/O
	ADXPS3_SetupPs3Fs(NULL);

	// Set up middleware thread system
	ADXM_SetupFramework(ADXM_FRAMEWORK_PS3PPU_MULTI_THREAD, NULL);

	// ATTN: This must be called after all of ADX*_SetupXxx().
	if (cellSurMixerStart() < 0){
		tutor_err_func(NULL, "cellSurMixerStart");
	}
}

void tutor_shutdown_middleware(void)
{
	// Pause mixer immediatelly
	if (cellSurMixerPause(CELL_SURMIXER_CONT_PAUSE_ON_IMMEDIATE) < 0){
		tutor_err_func(NULL, "cellSurMixerPause");
	}

	// Shutdown middleware thread system
	ADXM_ShutdownFramework();
	
	// Shutdown middleware streaming file I/O
	ADXPS3_ShutdownPs3Fs();

	// Shutdown middleware sound system
	ADXPS3_ShutdownSound();
}

void tutor_err_func(void *obj, Char8 *msg)
{
	// Show the error message and quit
	printf("ERROR: %s\n", msg);
	for (;;)
		;
}

// load movie file
void *tutor_load_file(const char *fname, char *memfile)
{
	ADXF adxf;
	sys_addr_t addr;
	void *buf;
	Sint32 fsize, fsct, asize, stat;

	adxf = ADXF_Open(fname, NULL);
	if (adxf == NULL) {
		tutor_err_func(NULL, "ADXF_Open");
		return NULL;
	}

	fsize = ADXF_GetFsizeByte(adxf);
	fsct = ADXF_GetFsizeSct(adxf);
	asize = (fsize / 0x100000 + 1 ) * 0x100000;	// (round up by 1MB) + 1MB

	if (sys_memory_allocate(asize, SYS_MEMORY_PAGE_SIZE_1M, &addr) != SUCCEEDED) {
		tutor_err_func(NULL, "sys_memory_allocate");
	}
	if (addr == NULL) {
		tutor_err_func(NULL, "Failed to allocate memory for movie file");
		ADXF_Close(adxf);
		return NULL;
	}

	buf = (void*)(((long)addr+127)&~127);	// 128byte aligned
	ADXF_ReadNw(adxf, fsct, buf);
	do  {
		ADXM_ExecMain();
		stat = ADXF_GetStat(adxf);
	} while (stat != ADXF_STAT_READEND);

	ADXF_Close(adxf);

	sprintf(memfile, "MFS:%08X.%08X", (UintPtr)buf, fsize);
	printf("%s\n", memfile);

	return (void*)addr;
}

// callback func for surround mixer
int tutor_sound_callback(void *arg, Uint32 counter, Uint32 num_samples)
{
	Sint32 ret;
	ret = ADXPS3_SoundNotifyCallback(arg, counter, num_samples);

	return ret;
}

void tutor_draw_flip(void)
{
	// Enable swap synchronized to Vsync
	glEnable(GL_VSYNC_SCE);
	// Clear buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glColor4f(1.f, 1.f, 1.f, 1.f);

	// YUVA8
	glBindTexture(GL_TEXTURE_2D, tutor_texobj);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1280, 720, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, tutor_texture);

	// Enable parameter for vertex program
	cgGLEnableClientState(cgGetNamedParameter(tutor_vert_prog, "Pobject"));
	cgGLEnableClientState(cgGetNamedParameter(tutor_vert_prog, "YTexUV"));
	// Enable parameter for fragment program
	glActiveTexture(GL_TEXTURE0);
	cgGLEnableTextureParameter(cgGetNamedParameter(tutor_frag_prog, "diffuseMapY"));

	// Texture coordinates
	glMultiTexCoord4f(GL_TEXTURE0, 1.0f, 1.0f, 0.0f, 1.0f);	// 1st
	glMultiTexCoord4f(GL_TEXTURE0, 1.0f, 0.0f, 0.0f, 1.0f);	// 2nd
	glMultiTexCoord4f(GL_TEXTURE0, 0.0f, 0.0f, 0.0f, 1.0f); // 3rd
	glMultiTexCoord4f(GL_TEXTURE0, 0.0f, 1.0f, 0.0f, 1.0f);	// 4th

	// Draw the quad
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	// Disable parameters
	cgGLDisableClientState(cgGetNamedParameter(tutor_vert_prog, "Pobject"));
	cgGLDisableClientState(cgGetNamedParameter(tutor_vert_prog, "YTexUV"));
	cgGLDisableTextureParameter(cgGetNamedParameter(tutor_frag_prog, "diffuseMapY"));

	// Swap surface
	psglSwap();
}

/* end of file */
