/*
 * PicoDrive
 * (C) notaz, 2007,2008
 *
 * This work is licensed under the terms of MAME license.
 * See COPYING file in the top-level directory.
 */

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/syslimits.h> // PATH_MAX

#include <pspthreadman.h>
#include <pspdisplay.h>
#include <psputils.h>
#include <pspgu.h>
#include <pspaudio.h>

#include "psp.h"
#include "emu.h"
#include "mp3.h"
#include "in_psp.h"
#include "asm_utils.h"
#include "../common/emu.h"
#include "../common/input_pico.h"
#include "platform/libpicofe/input.h"
#include "platform/libpicofe/menu.h"
#include "platform/libpicofe/plat.h"

#include <pico/pico_int.h>
#include <pico/cd/genplus_macros.h>
#include <pico/cd/cdd.h>

#define OSD_FPS_X 432

int engineStateSuspend;

#define PICO_PEN_ADJUST_X 4
#define PICO_PEN_ADJUST_Y 2

struct Vertex
{
	short u,v;
	short x,y,z;
};

static struct Vertex __attribute__((aligned(4))) g_vertices[2];
static u16 __attribute__((aligned(16))) localPal[0x100];
static int need_pal_upload = 0;

static u16 __attribute__((aligned(16))) osd_buf[512*8]; // buffer for osd text

static int h32_mode = 0;
static int out_x, out_y;
static int out_w, out_h;

static struct in_default_bind in_psp_defbinds[] =
{
	{ PSP_CTRL_UP,          IN_BINDTYPE_PLAYER12, GBTN_UP },
	{ PSP_CTRL_DOWN,        IN_BINDTYPE_PLAYER12, GBTN_DOWN },
	{ PSP_CTRL_LEFT,        IN_BINDTYPE_PLAYER12, GBTN_LEFT },
	{ PSP_CTRL_RIGHT,       IN_BINDTYPE_PLAYER12, GBTN_RIGHT },
	{ PSP_CTRL_SQUARE,      IN_BINDTYPE_PLAYER12, GBTN_A },
	{ PSP_CTRL_CROSS,       IN_BINDTYPE_PLAYER12, GBTN_B },
	{ PSP_CTRL_CIRCLE,      IN_BINDTYPE_PLAYER12, GBTN_C },
	{ PSP_CTRL_START,       IN_BINDTYPE_PLAYER12, GBTN_START },
	{ PSP_CTRL_TRIANGLE,    IN_BINDTYPE_EMU, PEVB_SWITCH_RND },
	{ PSP_CTRL_LTRIGGER,    IN_BINDTYPE_EMU, PEVB_STATE_SAVE },
	{ PSP_CTRL_RTRIGGER,    IN_BINDTYPE_EMU, PEVB_STATE_LOAD },
	{ PSP_CTRL_SELECT,      IN_BINDTYPE_EMU, PEVB_MENU },
	{ 0, 0, 0 }
};


const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast", NULL };
const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL };
enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT };

#define is_16bit_mode() \
	(currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X))

static int get_renderer(void)
{
	if (PicoIn.AHW & PAHW_32X)
		return currentConfig.renderer32x;
	else
		return currentConfig.renderer;
}

static void change_renderer(int diff)
{
	int *r;
	if (PicoIn.AHW & PAHW_32X)
		r = &currentConfig.renderer32x;
	else
		r = &currentConfig.renderer;
	*r += diff;

	if      (*r >= RT_COUNT)
		*r = 0;
	else if (*r < 0)
		*r = RT_COUNT - 1;
}

static void apply_renderer(void)
{
	PicoIn.opt &= ~POPT_ALT_RENDERER;

	switch (get_renderer()) {
	case RT_16BIT:
		PicoDrawSetOutFormat(PDF_RGB555, 0);
		break;
	case RT_8BIT_ACC:
		PicoDrawSetOutFormat(PDF_8BIT, 0);
		break;
	case RT_8BIT_FAST:
		PicoIn.opt |=  POPT_ALT_RENDERER;
		PicoDrawSetOutFormat(PDF_NONE, 0);
		break;
	}
}

static void osd_text(int x, const char *text)
{
	struct Vertex* vx;
	int len = strlen(text) * 8 / 2;
	int *p, h;
	void *tmp = g_screen_ptr;

	g_screen_ptr = osd_buf;
	for (h = 0; h < 8; h++) {
		p = (int *) (osd_buf+x+512*h);
		p = (int *) ((int)p & ~3); // align
		memset32_uncached(p, 0, len);
	}
	emu_text_out16(x, 0, text);
	g_screen_ptr = tmp;

	vx = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
	vx[0].u = x,         vx[0].v = 0;
	vx[1].u = x + len*2, vx[1].v = 8;
	vx[0].x = x,         vx[0].y = 264;
	vx[1].x = x + len*2, vx[1].y = 272;
	sceGuTexMode(GU_PSM_5650,0,0,0);
	sceGuTexImage(0,512,8,512,osd_buf);
	sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vx);
}


static void set_scaling_params(void)
{
	int fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0;
	g_vertices[0].z = g_vertices[1].z = 0;

	fbimg_height = (int)(out_h * currentConfig.scale + 0.5);
	if (!h32_mode)
		fbimg_width  = (int)(out_w * currentConfig.scale * currentConfig.hscale40 + 0.5);
	else
		fbimg_width  = (int)(out_w * currentConfig.scale * currentConfig.hscale32 + 0.5);

	if (fbimg_width  & 1) fbimg_width++;  // make even
	if (fbimg_height & 1) fbimg_height++;

	if (fbimg_width >= 480) {
		g_vertices[0].u = out_x + (fbimg_width-480)/2;
		g_vertices[1].u = out_x + out_w - (fbimg_width-480)/2 - 1;
		fbimg_width = 480;
		fbimg_xoffs = 0;
	} else {
		g_vertices[0].u = out_x;
		g_vertices[1].u = out_x + out_w;
		fbimg_xoffs = 240 - fbimg_width/2;
	}
	if (fbimg_width > 320 && fbimg_width <= 480) border_hack = 1;

	if (fbimg_height >= 272) {
		g_vertices[0].v = out_y + (fbimg_height-272)/2;
		g_vertices[1].v = out_y + out_h - (fbimg_height-272)/2;
		fbimg_height = 272;
		fbimg_yoffs = 0;
	} else {
		g_vertices[0].v = out_y;
		g_vertices[1].v = out_y + out_h;
		fbimg_yoffs = 136 - fbimg_height/2;
	}

	if (fbimg_xoffs < 0) fbimg_xoffs = 0;
	if (fbimg_yoffs < 0) fbimg_yoffs = 0;
	g_vertices[0].x = fbimg_xoffs;
	g_vertices[0].y = fbimg_yoffs;
	g_vertices[1].x = fbimg_xoffs + fbimg_width;
	g_vertices[1].y = fbimg_yoffs + fbimg_height;
	if (!is_16bit_mode()) {
		// 8-bit modes have an 8 px overlap area on the left
		g_vertices[0].u += 8; g_vertices[1].u += 8;
	}
	if (border_hack) {
		g_vertices[0].u++;    g_vertices[1].u--;
		g_vertices[0].x++;    g_vertices[1].x--;
	}

	/*
	lprintf("set_scaling_params:\n");
	lprintf("offs: %i, %i\n", fbimg_xoffs, fbimg_yoffs);
	lprintf("xy0, xy1: %i, %i; %i, %i\n", g_vertices[0].x, g_vertices[0].y, g_vertices[1].x, g_vertices[1].y);
	lprintf("uv0, uv1: %i, %i; %i, %i\n", g_vertices[0].u, g_vertices[0].v, g_vertices[1].u, g_vertices[1].v);
	*/
}

static void do_pal_update(void)
{
	u32 *dpal=(void *)localPal;
	int i;

	//for (i = 0x3f/2; i >= 0; i--)
	//	dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
	if (PicoIn.AHW & PAHW_SMS) {
		u32 *spal = (void *)PicoMem.cram; 
		for (i = 0; i < 0x20 / 2; i++) {
			u32 t = spal[i];
			t = ((t & 0x00030003)<< 3) | ((t & 0x000c000c)<<7) | ((t & 0x00300030)<<10);
			t |= (t >> 2) | ((t >> 4) & 0x08610861);
			dpal[i] = t;
		}
		Pico.m.dirtyPal = 0;
	} else if (PicoIn.opt & POPT_ALT_RENDERER) {
		do_pal_convert(localPal, PicoMem.cram, currentConfig.gamma, currentConfig.gamma2);
		Pico.m.dirtyPal = 0;
	}
	else if (Pico.est.rendstatus & PDRAW_SONIC_MODE)
	{
		switch (Pico.est.SonicPalCount) {
		case 3: do_pal_convert(localPal+0xc0/2, Pico.est.SonicPal+0xc0, currentConfig.gamma, currentConfig.gamma2);
		case 2: do_pal_convert(localPal+0x80/2, Pico.est.SonicPal+0x80, currentConfig.gamma, currentConfig.gamma2);
		case 1:	do_pal_convert(localPal+0x40/2, Pico.est.SonicPal+0x40, currentConfig.gamma, currentConfig.gamma2);
		default:do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
		}
	}
	else if (Pico.video.reg[0xC]&8) // shadow/hilight?
	{
		do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
		// shadowed pixels
		for (i = 0; i < 0x40 / 2; i++) {
			dpal[0xc0/2 + i] = dpal[i];
			dpal[0x80/2 + i] = (dpal[i] >> 1) & 0x738e738e;
		}
		// hilighted pixels
		for (i = 0; i < 0x40 / 2; i++) {
			u32 t = ((dpal[i] >> 1) & 0x738e738e) + 0x738e738e;
			t |= (t >> 4) & 0x08610861;
			dpal[0x40/2 + i] = t;
		}
	}
 	else
 	{
		do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
		memcpy((int *)dpal+0x40/2, (void *)localPal, 0x40*2);
		memcpy((int *)dpal+0x80/2, (void *)localPal, 0x80*2);
	}
	localPal[0xe0] = 0;
	localPal[0xf0] = 0x001f;

	if (Pico.m.dirtyPal == 2)
		Pico.m.dirtyPal = 0;
	need_pal_upload = 1;
}

static void blitscreen_clut(void)
{
	sceGuTexMode(is_16bit_mode() ? GU_PSM_5650:GU_PSM_T8,0,0,0);
	sceGuTexImage(0,512,512,512,g_screen_ptr);

	if (!is_16bit_mode() && Pico.m.dirtyPal)
		do_pal_update();


	if (need_pal_upload) {
		need_pal_upload = 0;
		sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
	}

#if 1
	if (g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x)
	{
		struct Vertex* vertices;
		int x;

		#define SLICE_WIDTH 32
		for (x = 0; x < g_vertices[1].x; x += SLICE_WIDTH)
		{
			// render sprite
			vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
			memcpy(vertices, g_vertices, 2 * sizeof(struct Vertex));
			vertices[0].u = vertices[0].x = x;
			vertices[1].u = vertices[1].x = x + SLICE_WIDTH;
			sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices);
		}
		// lprintf("listlen: %iB\n", sceGuCheckList()); // ~480 only
	}
	else
#endif
		sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices);
}


static void cd_leds(void)
{
	struct Vertex* vx;
	unsigned int reg, col_g, col_r, *p;

	reg = Pico_mcd->s68k_regs[0];

	p = (unsigned int *)((short *)osd_buf + 512*2+498);
	col_g = (reg & 2) ? 0x06000600 : 0;
	col_r = (reg & 1) ? 0x00180018 : 0;
	*p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
	*p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
	*p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r;

	vx = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
	vx[0].u = 497,          vx[0].v = 1;
	vx[1].u = 497+14,       vx[1].v = 6;
	vx[0].x = 4,            vx[0].y = 1;
	vx[1].x = 4+14,         vx[1].y = 6;
	sceGuTexMode(GU_PSM_5650,0,0,0);
	sceGuTexImage(0,512,8,512,osd_buf);
	sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vx);
}

static void draw_pico_ptr(void)
{
	unsigned char *p = (unsigned char *)g_screen_ptr + 8;

	// only if pen enabled and for 8bit mode
	if (pico_inp_mode == 0 || is_16bit_mode()) return;

	p += 512 * (pico_pen_y + PICO_PEN_ADJUST_Y);
	p += pico_pen_x + PICO_PEN_ADJUST_X;
	if (!(Pico.video.reg[12]&1) && !(PicoIn.opt & POPT_DIS_32C_BORDER))
		p += 32;

	p[  -1] = 0xe0; p[   0] = 0xf0; p[   1] = 0xe0;
	p[ 511] = 0xf0; p[ 512] = 0xf0; p[ 513] = 0xf0;
	p[1023] = 0xe0; p[1024] = 0xf0; p[1025] = 0xe0;
}


// clears whole screen or just the notice area (in all buffers)
static void clearArea(int full)
{
	void *fb = psp_video_get_active_fb();

	if (full) {
		u32  val  = (is_16bit_mode() ? 0x00000000 : 0xe0e0e0e0);
		long sz = 512*272*2;
		memset32_uncached(psp_screen, 0, 512*272*2/4); // frame buffer
		memset32_uncached(fb, 0, 512*272*2/4); // frame buff on display
		memset32(VRAM_CACHED_STUFF, val, 2*sz/4); // 2 draw buffers
	} else {
		memset32_uncached((int *)((char *)psp_screen + 512*264*2), 0, 512*8*2/4);
		memset32_uncached((int *)((char *)fb         + 512*264*2), 0, 512*8*2/4);
	}
}

static void vidResetMode(void)
{
	// setup GU
	sceGuSync(0,0); // sync with prev
	sceGuStart(GU_DIRECT, guCmdList);

	sceGuClutMode(GU_PSM_5650,0,0xff,0);
	sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
	if (currentConfig.scaling)
	     sceGuTexFilter(GU_LINEAR, GU_LINEAR);
	else sceGuTexFilter(GU_NEAREST, GU_NEAREST);
	sceGuTexScale(1.0f,1.0f);
	sceGuTexOffset(0.0f,0.0f);

	Pico.m.dirtyPal = 1;

	sceGuFinish();
	set_scaling_params();
	sceGuSync(0,0);
}

/* sound stuff */
#define SOUND_BLOCK_SIZE_NTSC (1470*2) // 1024 // 1152
#define SOUND_BLOCK_SIZE_PAL  (1764*2)
#define SOUND_BLOCK_COUNT    8

static short __attribute__((aligned(4))) sndBuffer[SOUND_BLOCK_SIZE_PAL*SOUND_BLOCK_COUNT + 44100/50*2];
static short *snd_playptr = NULL, *sndBuffer_endptr = NULL;
static int samples_made = 0, samples_done = 0, samples_block = 0;
static int sound_thread_exit = 0;
static SceUID sound_sem = -1;

static void writeSound(int len);

static int sound_thread(SceSize args, void *argp)
{
	int ret = 0;

	lprintf("sthr: started, priority %i\n", sceKernelGetThreadCurrentPriority());

	while (!sound_thread_exit)
	{
		if (samples_made - samples_done < samples_block) {
			// wait for data (use at least 2 blocks)
			//lprintf("sthr: wait... (%i)\n", samples_made - samples_done);
			while (samples_made - samples_done <= samples_block*2 && !sound_thread_exit)
				ret = sceKernelWaitSema(sound_sem, 1, 0);
			if (ret < 0) lprintf("sthr: sceKernelWaitSema: %i\n", ret);
			continue;
		}

		// lprintf("sthr: got data: %i\n", samples_made - samples_done);

		ret = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, snd_playptr);

		samples_done += samples_block;
		snd_playptr  += samples_block;
		if (snd_playptr >= sndBuffer_endptr)
			snd_playptr = sndBuffer;
		// 1.5 kernel returns 0, newer ones return # of samples queued
		if (ret < 0)
			lprintf("sthr: sceAudioSRCOutputBlocking: %08x; pos %i/%i\n", ret, samples_done, samples_made);

		// shouln't happen, but just in case
		if (samples_made - samples_done >= samples_block*3) {
			//lprintf("sthr: block skip (%i)\n", samples_made - samples_done);
			samples_done += samples_block; // skip
			snd_playptr  += samples_block;
		}

	}

	lprintf("sthr: exit\n");
	sceKernelExitDeleteThread(0);
	return 0;
}

static void sound_init(void)
{
	SceUID thid;
	int ret;

	sound_sem = sceKernelCreateSema("sndsem", 0, 0, 1, NULL);
	if (sound_sem < 0) lprintf("sceKernelCreateSema() failed: %i\n", sound_sem);

	samples_made = samples_done = 0;
	samples_block = SOUND_BLOCK_SIZE_NTSC; // make sure it goes to sema
	sound_thread_exit = 0;
	thid = sceKernelCreateThread("sndthread", sound_thread, 0x12, 0x10000, 0, NULL);
	if (thid >= 0)
	{
		ret = sceKernelStartThread(thid, 0, 0);
		if (ret < 0) lprintf("sound_init: sceKernelStartThread returned %08x\n", ret);
	}
	else
		lprintf("sceKernelCreateThread failed: %i\n", thid);
}

void pemu_sound_start(void)
{
	static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;
	static int mp3_init_done;
	int ret, stereo;

	samples_made = samples_done = 0;

	if (PicoIn.AHW & PAHW_MCD) {
		// mp3...
		if (!mp3_init_done) {
			ret = mp3_init();
			mp3_init_done = 1;
			if (ret) emu_status_msg("mp3 init failed (%i)", ret);
		}
	}

	ret = POPT_EN_FM|POPT_EN_PSG|POPT_EN_STEREO;
	if (PicoIn.sndRate != PsndRate_old || (PicoIn.opt&ret) != (PicoOpt_old&ret) || Pico.m.pal != pal_old) {
		PsndRerate(Pico.m.frame_count ? 1 : 0);
	}
	stereo=(PicoIn.opt&8)>>3;

	samples_block = Pico.m.pal ? SOUND_BLOCK_SIZE_PAL : SOUND_BLOCK_SIZE_NTSC;
	if (PicoIn.sndRate <= 22050) samples_block /= 2;
	sndBuffer_endptr = &sndBuffer[samples_block*SOUND_BLOCK_COUNT];

	lprintf("starting audio: %i, len: %i, stereo: %i, pal: %i, block samples: %i\n",
			PicoIn.sndRate, Pico.snd.len, stereo, Pico.m.pal, samples_block);

	// while (sceAudioOutput2GetRestSample() > 0) psp_msleep(100);
	// sceAudioSRCChRelease();
	ret = sceAudioSRCChReserve(samples_block/2, PicoIn.sndRate, 2); // seems to not need that stupid 64byte alignment
	if (ret < 0) {
		lprintf("sceAudioSRCChReserve() failed: %i\n", ret);
		emu_status_msg("sound init failed (%i), snd disabled", ret);
		currentConfig.EmuOpt &= ~EOPT_EN_SOUND;
	} else {
		PicoIn.writeSound = writeSound;
		memset32((int *)(void *)sndBuffer, 0, sizeof(sndBuffer)/4);
		snd_playptr = sndBuffer_endptr - samples_block;
		samples_made = samples_block; // send 1 empty block first..
		PicoIn.sndOut = sndBuffer;
		PsndRate_old = PicoIn.sndRate;
		PicoOpt_old  = PicoIn.opt;
		pal_old = Pico.m.pal;
	}
}

void pemu_sound_stop(void)
{
	int i;
	if (samples_done == 0)
	{
		// if no data is written between sceAudioSRCChReserve and sceAudioSRCChRelease calls,
		// we get a deadlock on next sceAudioSRCChReserve call
		// so this is yet another workaround:
		memset32((int *)(void *)sndBuffer, 0, samples_block*4/4);
		samples_made = samples_block * 3;
		sceKernelSignalSema(sound_sem, 1);
	}
	sceKernelDelayThread(100*1000);
	samples_made = samples_done = 0;
	for (i = 0; sceAudioOutput2GetRestSample() > 0 && i < 16; i++)
		psp_msleep(100);
	sceAudioSRCChRelease();
}

/* wait until we can write more sound */
void pemu_sound_wait(void)
{
	// TODO: test this
	while (!sound_thread_exit && samples_made - samples_done > samples_block * 4)
		psp_msleep(10);
}

static void sound_deinit(void)
{
	sound_thread_exit = 1;
	sceKernelSignalSema(sound_sem, 1);
	sceKernelDeleteSema(sound_sem);
	sound_sem = -1;
}

static void writeSound(int len)
{
	int ret;

	PicoIn.sndOut += len / 2;
	/*if (PicoIn.sndOut > sndBuffer_endptr) {
		memcpy((int *)(void *)sndBuffer, (int *)endptr, (PicoIn.sndOut - endptr + 1) * 2);
		PicoIn.sndOut = &sndBuffer[PicoIn.sndOut - endptr];
		lprintf("mov\n");
	}
	else*/
	if (PicoIn.sndOut > sndBuffer_endptr) lprintf("snd oflow %i!\n", PicoIn.sndOut - sndBuffer_endptr);
	if (PicoIn.sndOut >= sndBuffer_endptr)
		PicoIn.sndOut = sndBuffer;

	// signal the snd thread
	samples_made += len / 2;
	if (samples_made - samples_done > samples_block*2) {
		// lprintf("signal, %i/%i\n", samples_done, samples_made);
		ret = sceKernelSignalSema(sound_sem, 1);
		//if (ret < 0) lprintf("snd signal ret %08x\n", ret);
	}
}


/* set default configuration values */
void pemu_prep_defconfig(void)
{
	defaultConfig.s_PsndRate = 22050;
	defaultConfig.s_PicoCDBuffers = 64;
	defaultConfig.CPUclock = 333;
	defaultConfig.scaling = 1;     // bilinear filtering for psp
	defaultConfig.scale = 1.20;    // fullscreen
	defaultConfig.hscale40 = 1.25;
	defaultConfig.hscale32 = 1.56;
	defaultConfig.EmuOpt |= EOPT_SHOW_RTC;
}

/* check configuration for inconsistencies */
void pemu_validate_config(void)
{
	if (currentConfig.CPUclock < 33 || currentConfig.CPUclock > 333)
		currentConfig.CPUclock = 333;
	if (currentConfig.scaling < 0.01)
		currentConfig.scaling = 0.01;
	if (currentConfig.hscale40 < 0.01)
		currentConfig.hscale40 = 0.01;
	if (currentConfig.hscale32 < 0.01)
		currentConfig.hscale32 = 0.01;
}

/* finalize rendering a frame */
void pemu_finalize_frame(const char *fps, const char *notice)
{
	int emu_opt = currentConfig.EmuOpt;
	int offs = (psp_screen == VRAM_FB0) ? VRAMOFFS_FB0 : VRAMOFFS_FB1;

	if (PicoIn.AHW & PAHW_PICO)
		draw_pico_ptr();

	sceGuSync(0,0); // sync with prev
	sceGuStart(GU_DIRECT, guCmdList);
	sceGuDrawBuffer(GU_PSM_5650, (void *)offs, 512); // point to back buffer

	blitscreen_clut();

	if (notice)      osd_text(4, notice);
	if (emu_opt & 2) osd_text(OSD_FPS_X, fps);

	if ((emu_opt & 0x400) && (PicoIn.AHW & PAHW_MCD))
		cd_leds();

	sceKernelDcacheWritebackAll();
	sceGuFinish();
}

/* FIXME: move plat_* to plat? */

void plat_debug_cat(char *str)
{
}

/* platform dependend emulator initialization */
void plat_init(void)
{
	flip_after_sync = 1;
	psp_menu_init();
	in_psp_init(in_psp_defbinds);
	in_probe();
	sound_init();
	plat_get_data_dir(rom_fname_loaded, sizeof(rom_fname_loaded));
}

/* platform dependend emulator deinitialization */
void plat_finish(void)
{
	sound_deinit();
}

/* display emulator status messages before holding emulation */
void plat_status_msg_busy_first(const char *msg)
{
	plat_status_msg_busy_next(msg);
}

void plat_status_msg_busy_next(const char *msg)
{
	plat_status_msg_clear();
	pemu_finalize_frame("", msg);
	plat_video_flip();
	emu_status_msg("");
	reset_timing = 1;
}

/* clear status message area */
void plat_status_msg_clear(void)
{
	clearArea(0);
}

/* change the audio volume setting */
void plat_update_volume(int has_changed, int is_up)
{
}

/* prepare for MD screen mode change */
void emu_video_mode_change(int start_line, int line_count, int is_32cols)
{
	h32_mode = is_32cols;
	out_y = start_line; out_x = (is_32cols ? 32 : 0);
	out_h = line_count; out_w = (is_32cols ? 256:320);

	vidResetMode();
	if (h32_mode)	// clear borders from h40 remnants
		clearArea(1);
}

/* render one frame in RGB */
void pemu_forced_frame(int no_scale, int do_emu)
{
	Pico.m.dirtyPal = 1;

	if (!no_scale)
		no_scale = currentConfig.scaling == EOPT_SCALE_NONE;
	emu_cmn_forced_frame(no_scale, do_emu, g_screen_ptr);
}

/* change the platform output rendering */
void plat_video_toggle_renderer(int change, int is_menu_call)
{
	change_renderer(change);

	if (is_menu_call)
		return;

	apply_renderer();
	vidResetMode();
	rendstatus_old = -1;

	if (PicoIn.AHW & PAHW_32X)
		emu_status_msg(renderer_names32x[get_renderer()]);
	else
		emu_status_msg(renderer_names[get_renderer()]);
}

/* set the buffer for emulator output rendering */
void plat_video_set_buffer(void *buf)
{
	if (is_16bit_mode())
		PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2);
	else
		PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch);
}

/* prepare for emulator output rendering */
void plat_video_loop_prepare(void) 
{
	apply_renderer();
	vidResetMode();
	clearArea(1);
}

/* prepare for entering the emulator loop */
void pemu_loop_prep(void)
{
}

/* terminate the emulator loop */
void pemu_loop_end(void)
{
	pemu_sound_stop();
}

// TODO 
void emu_handle_resume(void)
{
	if (!(PicoIn.AHW & PAHW_MCD)) return;

	// reopen first CD track
	if (cdd.toc.tracks[0].fd != NULL)
	{
		lprintf("emu_HandleResume: reopen %s\n", cdd.toc.tracks[0].fname);
		pm_close(cdd.toc.tracks[0].fd);
		cdd.toc.tracks[0].fd = pm_open(cdd.toc.tracks[0].fname);
		lprintf("reopen %s\n", cdd.toc.tracks[0].fd != NULL ? "ok" : "failed");
	}

	mp3_reopen_file();

#if 0	// TODO
	if (!(Pico_mcd->s68k_regs[0x36] & 1))
		cdd_change_track(cdd.index, cdd.lba);
#endif
}

