/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

#include <pspgu.h>
#include <pspdisplay.h>

#include <time.h>
#include <zlib.h>

#include "common/config-manager.h"
#include "common/events.h"
#include "common/scummsys.h"

#include "osys_psp.h"
#include "trace.h"
#include "powerman.h"

#include "backends/saves/psp/psp-saves.h"
#include "backends/timer/default/default-timer.h"
#include "graphics/surface.h"
#include "sound/mixer_intern.h"

#include "backends/platform/psp/pspkeyboard.h"


#define	SAMPLES_PER_SEC	44100

#define PIXEL_SIZE (4)
#define BUF_WIDTH (512)
#define	PSP_SCREEN_WIDTH	480
#define	PSP_SCREEN_HEIGHT	272
#define PSP_FRAME_SIZE (BUF_WIDTH * PSP_SCREEN_HEIGHT * PIXEL_SIZE)
#define MOUSE_SIZE	128
#define	KBD_DATA_SIZE	130560

#define	MAX_FPS	30

unsigned int __attribute__((aligned(16))) displayList[2048];
unsigned short __attribute__((aligned(16))) clut256[256];
unsigned short __attribute__((aligned(16))) mouseClut[256];
unsigned short __attribute__((aligned(16))) cursorPalette[256];
unsigned int __attribute__((aligned(16))) mouseBuf256[MOUSE_SIZE*MOUSE_SIZE];


unsigned long RGBToColour(unsigned long r, unsigned long g, unsigned long b) {
	return (((b >> 3) << 10) | ((g >> 3) << 5) | ((r >> 3) << 0)) | 0x8000;
}

static int timer_handler(int t) {
	DefaultTimerManager *tm = (DefaultTimerManager *)g_system->getTimerManager();
	tm->handler();
	return t;
}

const OSystem::GraphicsMode OSystem_PSP::s_supportedGraphicsModes[] = {
	{ "320x200 (centered)", "320x200 16-bit centered", CENTERED_320X200 },
	{ "435x272 (best-fit, centered)", "435x272 16-bit centered", CENTERED_435X272 },
	{ "480x272 (full screen)", "480x272 16-bit stretched", STRETCHED_480X272 },
	{ "362x272 (4:3, centered)", "362x272 16-bit centered", CENTERED_362X272 },
	{0, 0, 0}
};


OSystem_PSP::OSystem_PSP() : _screenWidth(0), _screenHeight(0), _overlayWidth(0), _overlayHeight(0),
		_offscreen(0), _overlayBuffer(0), _overlayVisible(false), _shakePos(0), _lastScreenUpdate(0),
		_mouseBuf(0), _prevButtons(0), _lastPadCheck(0), _padAccel(0), _mixer(0) {
	memset(_palette, 0, sizeof(_palette));

	_cursorPaletteDisabled = true;

	_samplesPerSec = 0;

	//init SDL
	uint32	sdlFlags = SDL_INIT_AUDIO | SDL_INIT_TIMER;
	SDL_Init(sdlFlags);

	_clut = clut256;
	_mouseBuf = (byte *)mouseBuf256;
	_graphicMode = STRETCHED_480X272;

	_mouseX = PSP_SCREEN_WIDTH >> 1;	// Mouse in the middle of the screen
	_mouseY = PSP_SCREEN_HEIGHT >> 1;


	// Init GU
	sceGuInit();
	sceGuStart(0, displayList);
	sceGuDrawBuffer(GU_PSM_8888, (void *)0, BUF_WIDTH);
	sceGuDispBuffer(PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT, (void*)PSP_FRAME_SIZE, BUF_WIDTH);
	sceGuDepthBuffer((void*)(PSP_FRAME_SIZE * 2), BUF_WIDTH);
	sceGuOffset(2048 - (PSP_SCREEN_WIDTH/2), 2048 - (PSP_SCREEN_HEIGHT/2));
	sceGuViewport(2048, 2048, PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT);
	sceGuDepthRange(0xC350, 0x2710);
	sceGuScissor(0, 0, PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT);
	sceGuEnable(GU_SCISSOR_TEST);
	sceGuFrontFace(GU_CW);
	sceGuEnable(GU_TEXTURE_2D);
	sceGuClear(GU_COLOR_BUFFER_BIT|GU_DEPTH_BUFFER_BIT);
	sceGuFinish();
	sceGuSync(0,0);

	sceDisplayWaitVblankStart();
	sceGuDisplay(1);

}

OSystem_PSP::~OSystem_PSP() {

	free(_offscreen);
	free(_overlayBuffer);
	free(_mouseBuf);
	delete _keyboard;

	_offscreen = 0;
	_overlayBuffer = 0;
	_mouseBuf = 0;
	 sceGuTerm();
}


void OSystem_PSP::initBackend() {
	_savefile = new PSPSaveFileManager;

	_timer = new DefaultTimerManager();

	_keyboard = new PSPKeyboard();
	_keyboard->load();

	setTimerCallback(&timer_handler, 10);

	setupMixer();

	OSystem::initBackend();
}


bool OSystem_PSP::hasFeature(Feature f) {
	return (f == kFeatureOverlaySupportsAlpha || f == kFeatureCursorHasPalette);
}

void OSystem_PSP::setFeatureState(Feature f, bool enable) {
}

bool OSystem_PSP::getFeatureState(Feature f) {
	return false;
}

const OSystem::GraphicsMode* OSystem_PSP::getSupportedGraphicsModes() const {
	return s_supportedGraphicsModes;
}


int OSystem_PSP::getDefaultGraphicsMode() const {
	return STRETCHED_480X272;
}

bool OSystem_PSP::setGraphicsMode(int mode) {
	_graphicMode = mode;
	return true;
}

bool OSystem_PSP::setGraphicsMode(const char *name) {
	int i = 0;

	while (s_supportedGraphicsModes[i].name) {
		if (!strcmpi(s_supportedGraphicsModes[i].name, name)) {
			_graphicMode = s_supportedGraphicsModes[i].id;
			return true;
		}
		i++;
	}

	return false;
}

int OSystem_PSP::getGraphicsMode() const {
	return _graphicMode;
}

void OSystem_PSP::initSize(uint width, uint height, const Graphics::PixelFormat *format) {
	PSPDebugTrace("initSize\n");

	_screenWidth = width;
	_screenHeight = height;

	const int scrBufSize = _screenWidth * _screenHeight * (format ? format->bytesPerPixel : 4);

	_overlayWidth = PSP_SCREEN_WIDTH;	//width;
	_overlayHeight = PSP_SCREEN_HEIGHT;	//height;

	free(_overlayBuffer);
	_overlayBuffer = (OverlayColor *)memalign(16, _overlayWidth * _overlayHeight * sizeof(OverlayColor));

	free(_offscreen);
	_offscreen = (byte *)memalign(16, scrBufSize);
	bzero(_offscreen, scrBufSize);

	clearOverlay();
	memset(_palette, 0xFFFF, 256 * sizeof(unsigned short));

	_mouseVisible = false;
	sceKernelDcacheWritebackAll();
}

int16 OSystem_PSP::getWidth() {
	return _screenWidth;
}

int16 OSystem_PSP::getHeight() {
	return _screenHeight;
}

void OSystem_PSP::setPalette(const byte *colors, uint start, uint num) {
	const byte *b = colors;

	for (uint i = 0; i < num; ++i) {
		_palette[start + i] = RGBToColour(b[0], b[1], b[2]);
		b += 4;
	}

	//copy to CLUT
	memcpy(_clut, _palette, 256 * sizeof(unsigned short));

	//force update of mouse CLUT as well, as it may have been set up before this palette was set
	memcpy(mouseClut, _palette, 256 * sizeof(unsigned short));
	mouseClut[_mouseKeyColour] = 0;

	sceKernelDcacheWritebackAll();
}

void OSystem_PSP::setCursorPalette(const byte *colors, uint start, uint num) {
	const byte *b = colors;

	for (uint i = 0; i < num; ++i) {
		cursorPalette[start + i] = RGBToColour(b[0], b[1], b[2]);
		b += 4;
	}

	cursorPalette[0] = 0;

	_cursorPaletteDisabled = false;

	sceKernelDcacheWritebackAll();
}

void OSystem_PSP::disableCursorPalette(bool disable) {
	_cursorPaletteDisabled = disable;
}

void OSystem_PSP::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) {
	//Clip the coordinates
	if (x < 0) {
		w += x;
		buf -= x;
		x = 0;
	}

	if (y < 0) {
		h += y;
		buf -= y * pitch;
		y = 0;
	}

	if (w > _screenWidth - x) {
		w = _screenWidth - x;
	}

	if (h > _screenHeight - y) {
		h = _screenHeight - y;
	}

	if (w <= 0 || h <= 0)
		return;


	byte *dst = _offscreen + y * _screenWidth + x;

	if (_screenWidth == pitch && pitch == w) {
		memcpy(dst, buf, h * w);
	} else {
		do {
			memcpy(dst, buf, w);
			buf += pitch;
			dst += _screenWidth;
		} while (--h);
	}
	sceKernelDcacheWritebackAll();

}

Graphics::Surface *OSystem_PSP::lockScreen() {
	_framebuffer.pixels = _offscreen;
	_framebuffer.w = _screenWidth;
	_framebuffer.h = _screenHeight;
	_framebuffer.pitch = _screenWidth;
	_framebuffer.bytesPerPixel = 1;

	return &_framebuffer;
}

void OSystem_PSP::unlockScreen() {
	// The screen is always completely update anyway, so we don't have to force a full update here.
	sceKernelDcacheWritebackAll();
}

void OSystem_PSP::updateScreen() {
	u32 now = getMillis();
	if (now - _lastScreenUpdate < 1000 / MAX_FPS)
		return;

	_lastScreenUpdate = now;

	sceGuStart(0, displayList);

	sceGuClearColor(0xFF000000);
	sceGuClear(GU_COLOR_BUFFER_BIT);

	sceGuClutMode(GU_PSM_5551, 0, 0xFF, 0);
	sceGuClutLoad(32, clut256); // upload 32*8 entries (256)
	sceGuTexMode(GU_PSM_T8, 0, 0, 0); // 8-bit image
	if (_screenWidth == 320)
		sceGuTexImage(0, 512, 256, _screenWidth, _offscreen);
	else
		sceGuTexImage(0, 512, 512, _screenWidth, _offscreen);
	sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
	sceGuTexFilter(GU_LINEAR, GU_LINEAR);
	sceGuTexOffset(0,0);
	sceGuAmbientColor(0xFFFFFFFF);
	sceGuColor(0xFFFFFFFF);

	Vertex *vertices = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
	vertices[0].u = 0.5f;
	vertices[0].v = 0.5f;
	vertices[1].u = _screenWidth - 0.5f;
	vertices[1].v = _screenHeight - 0.5f;

	switch (_graphicMode) {
		case CENTERED_320X200:
			vertices[0].x = (PSP_SCREEN_WIDTH - 320) / 2;
			vertices[0].y = (PSP_SCREEN_HEIGHT - 200) / 2;
			vertices[0].z = 0;
			vertices[1].x = PSP_SCREEN_WIDTH - (PSP_SCREEN_WIDTH - 320) / 2;
			vertices[1].y = PSP_SCREEN_HEIGHT - (PSP_SCREEN_HEIGHT - 200) / 2;
			vertices[1].z = 0;
		break;
		case CENTERED_435X272:
			vertices[0].x = (PSP_SCREEN_WIDTH - 435) / 2;
			vertices[0].y = 0; vertices[0].z = 0;
			vertices[1].x = PSP_SCREEN_WIDTH - (PSP_SCREEN_WIDTH - 435) / 2;
			vertices[1].y = PSP_SCREEN_HEIGHT;
			vertices[1].z = 0;
		break;
		case STRETCHED_480X272:
			vertices[0].x = 0;
			vertices[0].y = 0;
			vertices[0].z = 0;
			vertices[1].x = PSP_SCREEN_WIDTH;
			vertices[1].y = PSP_SCREEN_HEIGHT;
			vertices[1].z = 0;
		break;
		case CENTERED_362X272:
			vertices[0].x = (PSP_SCREEN_WIDTH - 362) / 2;
			vertices[0].y = 0;
			vertices[0].z = 0;
			vertices[1].x = PSP_SCREEN_WIDTH - (PSP_SCREEN_WIDTH - 362) / 2;
			vertices[1].y = PSP_SCREEN_HEIGHT;
			vertices[1].z = 0;
		break;
	}

	if (_shakePos) {
		vertices[0].y += _shakePos;
		vertices[1].y += _shakePos;
	}

	sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF|GU_VERTEX_32BITF|GU_TRANSFORM_2D, 2, 0, vertices);
	if (_screenWidth == 640) {
		// 2nd draw
		Vertex *vertices2 = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
		sceGuTexImage(0, 512, 512, _screenWidth, _offscreen+512);
		vertices2[0].u = 512 + 0.5f;
		vertices2[0].v = vertices[0].v;
		vertices2[1].u = vertices[1].u;
		vertices2[1].v = _screenHeight - 0.5f;
		vertices2[0].x = vertices[0].x + (vertices[1].x - vertices[0].x) * 511 / 640;
		vertices2[0].y = 0;
		vertices2[0].z = 0;
		vertices2[1].x = vertices[1].x;
		vertices2[1].y = vertices[1].y;
		vertices2[1].z = 0;
		sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF|GU_VERTEX_32BITF|GU_TRANSFORM_2D, 2, 0, vertices2);
	}


	// draw overlay
	if (_overlayVisible) {
		Vertex *vertOverlay = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
		vertOverlay[0].x = 0;
		vertOverlay[0].y = 0;
		vertOverlay[0].z = 0;
		vertOverlay[1].x = PSP_SCREEN_WIDTH;
		vertOverlay[1].y = PSP_SCREEN_HEIGHT;
		vertOverlay[1].z = 0;
		vertOverlay[0].u = 0.5f;
		vertOverlay[0].v = 0.5f;
		vertOverlay[1].u = _overlayWidth - 0.5f;
		vertOverlay[1].v = _overlayHeight - 0.5f;
		sceGuTexMode(GU_PSM_4444, 0, 0, 0); // 16-bit image
		sceGuDisable(GU_ALPHA_TEST);
		sceGuEnable(GU_BLEND);

		//sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);
		sceGuBlendFunc(GU_ADD, GU_FIX, GU_ONE_MINUS_SRC_ALPHA, 0xFFFFFFFF, 0);

		if (_overlayWidth > 320)
			sceGuTexImage(0, 512, 512, _overlayWidth, _overlayBuffer);
		else
			sceGuTexImage(0, 512, 256, _overlayWidth, _overlayBuffer);

		sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGBA);
		sceGuDrawArray(GU_SPRITES,GU_TEXTURE_32BITF|GU_VERTEX_32BITF|GU_TRANSFORM_2D,2,0,vertOverlay);
		// need to render twice for textures > 512
		if ( _overlayWidth > 512) {
			Vertex *vertOverlay2 = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
			sceGuTexImage(0, 512, 512, _overlayWidth, _overlayBuffer + 512);
			vertOverlay2[0].u = 512 + 0.5f;
			vertOverlay2[0].v = vertOverlay[0].v;
			vertOverlay2[1].u = vertOverlay[1].u;
			vertOverlay2[1].v = _overlayHeight - 0.5f;
			vertOverlay2[0].x = PSP_SCREEN_WIDTH * 512 / 640;
			vertOverlay2[0].y = 0;
			vertOverlay2[0].z = 0;
			vertOverlay2[1].x = PSP_SCREEN_WIDTH;
			vertOverlay2[1].y = PSP_SCREEN_HEIGHT;
			vertOverlay2[1].z = 0;
			sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF|GU_VERTEX_32BITF|GU_TRANSFORM_2D, 2, 0, vertOverlay2);
		}
		sceGuDisable(GU_BLEND);
	}

	// draw mouse
	if (_mouseVisible) {
		sceGuTexMode(GU_PSM_T8, 0, 0, 0); // 8-bit image
		sceGuClutMode(GU_PSM_5551, 0, 0xFF, 0);
		sceGuClutLoad(32, _cursorPaletteDisabled ? mouseClut : cursorPalette); // upload 32*8 entries (256)
		sceGuAlphaFunc(GU_GREATER, 0, 0xFF);
		sceGuEnable(GU_ALPHA_TEST);
		sceGuTexImage(0, MOUSE_SIZE, MOUSE_SIZE, MOUSE_SIZE, _mouseBuf);
		sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA);

		Vertex *vertMouse = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));
		vertMouse[0].u = 0.5f;
		vertMouse[0].v = 0.5f;
		vertMouse[1].u = _mouseWidth - 0.5f;
		vertMouse[1].v = _mouseHeight - 0.5f;

		//adjust cursor position
		int mX = _mouseX - _mouseHotspotX;
		int mY = _mouseY - _mouseHotspotY;

		if (_overlayVisible) {
			float scalex, scaley;

			scalex = (float)PSP_SCREEN_WIDTH /_overlayWidth;
			scaley = (float)PSP_SCREEN_HEIGHT /_overlayHeight;

			vertMouse[0].x = mX * scalex;
			vertMouse[0].y = mY * scaley;
			vertMouse[0].z = 0;
			vertMouse[1].x = vertMouse[0].x + _mouseWidth * scalex;
			vertMouse[1].y = vertMouse[0].y + _mouseHeight * scaley;
			vertMouse[1].z = 0;
		} else
			switch (_graphicMode) {
			case CENTERED_320X200:
				vertMouse[0].x = (PSP_SCREEN_WIDTH - 320) / 2 + mX;
				vertMouse[0].y = (PSP_SCREEN_HEIGHT - 200) / 2 + mY;
				vertMouse[0].z = 0;
				vertMouse[1].x = vertMouse[0].x + _mouseWidth;
				vertMouse[1].y = vertMouse[0].y + _mouseHeight;
				vertMouse[1].z = 0;
			break;
			case CENTERED_435X272:
			{
				float scalex, scaley;

				scalex = 435.0f / _screenWidth;
				scaley = 272.0f / _screenHeight;

				vertMouse[0].x = (PSP_SCREEN_WIDTH - 435) / 2 + mX * scalex;
				vertMouse[0].y = mY * scaley;
				vertMouse[0].z = 0;
				vertMouse[1].x = vertMouse[0].x + _mouseWidth * scalex;
				vertMouse[1].y = vertMouse[0].y + _mouseHeight * scaley;
				vertMouse[1].z = 0;
			}
			break;
			case CENTERED_362X272:
			{
				float scalex, scaley;

				scalex = 362.0f / _screenWidth;
				scaley = 272.0f / _screenHeight;

				vertMouse[0].x = (PSP_SCREEN_WIDTH - 362) / 2 + mX * scalex;
				vertMouse[0].y = mY * scaley;
				vertMouse[0].z = 0;
				vertMouse[1].x = vertMouse[0].x + _mouseWidth * scalex;
				vertMouse[1].y = vertMouse[0].y + _mouseHeight * scaley;
				vertMouse[1].z = 0;
			}
			break;
			case STRETCHED_480X272:
			{
				float scalex, scaley;

				scalex = (float)PSP_SCREEN_WIDTH / _screenWidth;
				scaley = (float)PSP_SCREEN_HEIGHT / _screenHeight;

				vertMouse[0].x = mX * scalex;
				vertMouse[0].y = mY * scaley;
				vertMouse[0].z = 0;
				vertMouse[1].x = vertMouse[0].x + _mouseWidth * scalex;
				vertMouse[1].y = vertMouse[0].y + _mouseHeight * scaley;
				vertMouse[1].z = 0;
			}
			break;
		}
		sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF|GU_VERTEX_32BITF|GU_TRANSFORM_2D, 2, 0, vertMouse);
	}

	if (_keyboard->isVisible()) {
		_keyboard->render();
	}

	sceGuFinish();
	sceGuSync(0,0);

	sceDisplayWaitVblankStart();
	sceGuSwapBuffers();
}

void OSystem_PSP::setShakePos(int shakeOffset) {
	_shakePos = shakeOffset;
}

void OSystem_PSP::showOverlay() {
	_overlayVisible = true;
}

void OSystem_PSP::hideOverlay() {
	PSPDebugTrace("OSystem_PSP::hideOverlay called\n");
	_overlayVisible = false;
}

void OSystem_PSP::clearOverlay() {
	PSPDebugTrace("clearOverlay\n");

	bzero(_overlayBuffer, _overlayWidth * _overlayHeight * sizeof(OverlayColor));
	sceKernelDcacheWritebackAll();
}

void OSystem_PSP::grabOverlay(OverlayColor *buf, int pitch) {
	int h = _overlayHeight;
	OverlayColor *src = _overlayBuffer;

	do {
		memcpy(buf, src, _overlayWidth * sizeof(OverlayColor));
		src += _overlayWidth;
		buf += pitch;
	} while (--h);
}

void OSystem_PSP::copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h) {
	PSPDebugTrace("copyRectToOverlay\n");

	//Clip the coordinates
	if (x < 0) {
		w += x;
		buf -= x;
		x = 0;
	}

	if (y < 0) {
		h += y;
		buf -= y * pitch;
		y = 0;
	}

	if (w > _overlayWidth - x) {
		w = _overlayWidth - x;
	}

	if (h > _overlayHeight - y) {
		h = _overlayHeight - y;
	}

	if (w <= 0 || h <= 0)
		return;


	OverlayColor *dst = _overlayBuffer + (y * _overlayWidth + x);

	if (_overlayWidth == pitch && pitch == w) {
		memcpy(dst, buf, h * w * sizeof(OverlayColor));
	} else {
		do {
			memcpy(dst, buf, w * sizeof(OverlayColor));
			buf += pitch;
			dst += _overlayWidth;
		} while (--h);
	}
	sceKernelDcacheWritebackAll();
}

int16 OSystem_PSP::getOverlayWidth() {
	return _overlayWidth;
}

int16 OSystem_PSP::getOverlayHeight() {
	return _overlayHeight;
}


void OSystem_PSP::grabPalette(byte *colors, uint start, uint num) {
	uint i;
	uint16 color;

	for (i = start; i < start + num; i++) {
		color = _palette[i];
		*colors++ = ((color & 0x1F) << 3);
		*colors++ = (((color >> 5) & 0x1F) << 3);
		*colors++ = (((color >> 10) & 0x1F) << 3);
		*colors++ = (color & 0x8000 ? 255 : 0);
	}
}

bool OSystem_PSP::showMouse(bool visible) {
	bool last = _mouseVisible;
	_mouseVisible = visible;
	return last;
}

void OSystem_PSP::warpMouse(int x, int y) {
	//assert(x > 0 && x < _screenWidth);
	//assert(y > 0 && y < _screenHeight);
	_mouseX = x;
	_mouseY = y;
}

void OSystem_PSP::setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format) {
	//TODO: handle cursorTargetScale
	_mouseWidth = w;
	_mouseHeight = h;

	_mouseHotspotX = hotspotX;
	_mouseHotspotY = hotspotY;

	_mouseKeyColour = keycolor & 0xFF;

	memcpy(mouseClut, _palette, 256 * sizeof(unsigned short));
	mouseClut[_mouseKeyColour] = 0;

	for (unsigned int i = 0; i < h; i++)
		memcpy(_mouseBuf + i * MOUSE_SIZE, buf + i * w, w);

	sceKernelDcacheWritebackAll();
}

#define PAD_CHECK_TIME	40
#define PAD_DIR_MASK	(PSP_CTRL_UP | PSP_CTRL_DOWN | PSP_CTRL_LEFT | PSP_CTRL_RIGHT)

bool OSystem_PSP::processInput(Common::Event &event) {
	s8 analogStepAmountX = 0;
	s8 analogStepAmountY = 0;

	sceCtrlSetSamplingCycle(0);
	sceCtrlSetSamplingMode(1);
	sceCtrlReadBufferPositive(&pad, 1);

	bool usedInput, haveEvent;

	haveEvent = _keyboard->processInput(event, pad, usedInput);

	if (usedInput) 	// Check if the keyboard used up the input
		return haveEvent;

	uint32 buttonsChanged = pad.Buttons ^ _prevButtons;

	if (buttonsChanged & (PSP_CTRL_CROSS | PSP_CTRL_CIRCLE | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER | PSP_CTRL_START | PSP_CTRL_SELECT | PSP_CTRL_SQUARE | PSP_CTRL_TRIANGLE)) {
		if (buttonsChanged & PSP_CTRL_CROSS) {
			event.type = (pad.Buttons & PSP_CTRL_CROSS) ? Common::EVENT_LBUTTONDOWN : Common::EVENT_LBUTTONUP;
		} else if (buttonsChanged & PSP_CTRL_CIRCLE) {
			event.type = (pad.Buttons & PSP_CTRL_CIRCLE) ? Common::EVENT_RBUTTONDOWN : Common::EVENT_RBUTTONUP;
		} else {
			//any of the other buttons.
			event.type = buttonsChanged & pad.Buttons ? Common::EVENT_KEYDOWN : Common::EVENT_KEYUP;
			event.kbd.ascii = 0;
			event.kbd.flags = 0;

			if (buttonsChanged & PSP_CTRL_LTRIGGER) {
				event.kbd.keycode = Common::KEYCODE_ESCAPE;
				event.kbd.ascii = 27;
			} else if (buttonsChanged & PSP_CTRL_START) {
				event.kbd.keycode = Common::KEYCODE_F5;
				event.kbd.ascii = Common::ASCII_F5;
				if (pad.Buttons & PSP_CTRL_RTRIGGER) {
					event.kbd.flags = Common::KBD_CTRL;	// Main menu to allow RTL
				}
/*			} else if (buttonsChanged & PSP_CTRL_SELECT) {
				event.kbd.keycode = Common::KEYCODE_0;
				event.kbd.ascii = '0';
*/			} else if (buttonsChanged & PSP_CTRL_SQUARE) {
				event.kbd.keycode = Common::KEYCODE_PERIOD;
				event.kbd.ascii = '.';
			} else if (buttonsChanged & PSP_CTRL_TRIANGLE) {
				event.kbd.keycode = Common::KEYCODE_RETURN;
				event.kbd.ascii = 13;
			} else if (pad.Buttons & PSP_CTRL_RTRIGGER) {
				event.kbd.flags |= Common::KBD_SHIFT;
			}

		}

		event.mouse.x = _mouseX;
		event.mouse.y = _mouseY;
		_prevButtons = pad.Buttons;
		return true;
	}

	uint32 time = getMillis();
	if (time - _lastPadCheck > PAD_CHECK_TIME) {
		_lastPadCheck = time;
		int16 newX = _mouseX;
		int16 newY = _mouseY;

		if (pad.Lx < 100) {
			analogStepAmountX = pad.Lx - 100;
		} else if (pad.Lx > 155) {
			analogStepAmountX = pad.Lx - 155;
		}

		if (pad.Ly < 100) {
			analogStepAmountY = pad.Ly - 100;
		} else if (pad.Ly > 155) {
			analogStepAmountY = pad.Ly - 155;
		}

		if (pad.Buttons & PAD_DIR_MASK ||
		    analogStepAmountX != 0 || analogStepAmountY != 0) {
			if (_prevButtons & PAD_DIR_MASK) {
				if (_padAccel < 16)
					_padAccel++;
			} else
				_padAccel = 0;

			_prevButtons = pad.Buttons;

			if (pad.Buttons & PSP_CTRL_LEFT)
				newX -= _padAccel >> 2;
			if (pad.Buttons & PSP_CTRL_RIGHT)
				newX += _padAccel >> 2;
			if (pad.Buttons & PSP_CTRL_UP)
				newY -= _padAccel >> 2;
			if (pad.Buttons & PSP_CTRL_DOWN)
				newY += _padAccel >> 2;

			// If no movement then this has no effect
			if (pad.Buttons & PSP_CTRL_RTRIGGER) {
				// Fine control mode for analog
					if (analogStepAmountX != 0) {
						if (analogStepAmountX > 0)
							newX += analogStepAmountX - (analogStepAmountX - 1);
						else
							newX -= -analogStepAmountX - (-analogStepAmountX - 1);
					}

					if (analogStepAmountY != 0) {
						if (analogStepAmountY > 0)
							newY += analogStepAmountY - (analogStepAmountY - 1);
						else
							newY -= -analogStepAmountY - (-analogStepAmountY - 1);
					}
			} else {
				newX += analogStepAmountX >> ((_screenWidth == 640) ? 2 : 3);
				newY += analogStepAmountY >> ((_screenWidth == 640) ? 2 : 3);
			}

			if (newX < 0)
				newX = 0;
			if (newY < 0)
				newY = 0;
			if (_overlayVisible) {
				if (newX >= _overlayWidth)
					newX = _overlayWidth - 1;
				if (newY >= _overlayHeight)
					newY = _overlayHeight - 1;
			} else {
				if (newX >= _screenWidth)
					newX = _screenWidth - 1;
				if (newY >= _screenHeight)
					newY = _screenHeight - 1;
			}

			if ((_mouseX != newX) || (_mouseY != newY)) {
				event.type = Common::EVENT_MOUSEMOVE;
				event.mouse.x = _mouseX = newX;
				event.mouse.y = _mouseY = newY;
				return true;
			}
		} else {
			//reset pad acceleration
			_padAccel = 0;
		}
	}

	return false;
}

bool OSystem_PSP::pollEvent(Common::Event &event) {

	// If we're polling for events, we should check for pausing the engine
	// Pausing the engine is a necessary fix for games that use the timer for music synchronization
	// 	recovering many hours later causes the game to crash. We're polling without mutexes since it's not critical to
	//  get it right now.

	PowerMan.pollPauseEngine();

	return processInput(event);

}


uint32 OSystem_PSP::getMillis() {
	return SDL_GetTicks();
}

void OSystem_PSP::delayMillis(uint msecs) {
	SDL_Delay(msecs);
}

void OSystem_PSP::setTimerCallback(TimerProc callback, int interval) {
	SDL_SetTimer(interval, (SDL_TimerCallback)callback);
}

OSystem::MutexRef OSystem_PSP::createMutex(void) {
	return (MutexRef)SDL_CreateMutex();
}

void OSystem_PSP::lockMutex(MutexRef mutex) {
	SDL_mutexP((SDL_mutex *)mutex);
}

void OSystem_PSP::unlockMutex(MutexRef mutex) {
	SDL_mutexV((SDL_mutex *)mutex);
}

void OSystem_PSP::deleteMutex(MutexRef mutex) {
	SDL_DestroyMutex((SDL_mutex *)mutex);
}

void OSystem_PSP::mixCallback(void *sys, byte *samples, int len) {
	OSystem_PSP *this_ = (OSystem_PSP *)sys;
	assert(this_);

	if (this_->_mixer)
		this_->_mixer->mixCallback(samples, len);
}

void OSystem_PSP::setupMixer(void) {
	SDL_AudioSpec desired;
	SDL_AudioSpec obtained;

	memset(&desired, 0, sizeof(desired));

	if (ConfMan.hasKey("output_rate"))
		_samplesPerSec = ConfMan.getInt("output_rate");
	else
		_samplesPerSec = SAMPLES_PER_SEC;

	// Originally, we always used 2048 samples. This loop will produce the
	// same result at 22050 Hz, and should hopefully produce something
	// sensible for other frequencies. Note that it must be a power of two.

	uint16 samples = 0x8000;

	for (;;) {
		if (samples / (_samplesPerSec / 1000) < 100)
			break;
		samples >>= 1;
	}

	desired.freq = _samplesPerSec;
	desired.format = AUDIO_S16SYS;
	desired.channels = 2;
	desired.samples = samples;
	desired.callback = mixCallback;
	desired.userdata = this;

	assert(!_mixer);
	_mixer = new Audio::MixerImpl(this);
	assert(_mixer);

	if (SDL_OpenAudio(&desired, &obtained) != 0) {
		warning("Could not open audio: %s", SDL_GetError());
		_samplesPerSec = 0;
		_mixer->setReady(false);
	} else {
		// Note: This should be the obtained output rate, but it seems that at
		// least on some platforms SDL will lie and claim it did get the rate
		// even if it didn't. Probably only happens for "weird" rates, though.
		_samplesPerSec = obtained.freq;

		// Tell the mixer that we are ready and start the sound processing
		_mixer->setOutputRate(_samplesPerSec);
		_mixer->setReady(true);

		SDL_PauseAudio(0);
	}
}

void OSystem_PSP::quit() {
	SDL_CloseAudio();
	SDL_Quit();
	sceGuTerm();
	sceKernelExitGame();
}

void OSystem_PSP::getTimeAndDate(TimeDate &td) const {
	time_t curTime = time(0);
	struct tm t = *localtime(&curTime);
	td.tm_sec = t.tm_sec;
	td.tm_min = t.tm_min;
	td.tm_hour = t.tm_hour;
	td.tm_mday = t.tm_mday;
	td.tm_mon = t.tm_mon;
	td.tm_year = t.tm_year;
}

#define PSP_CONFIG_FILE "ms0:/scummvm.ini"

Common::SeekableReadStream *OSystem_PSP::createConfigReadStream() {
	Common::FSNode file(PSP_CONFIG_FILE);
	return file.createReadStream();
}

Common::WriteStream *OSystem_PSP::createConfigWriteStream() {
	Common::FSNode file(PSP_CONFIG_FILE);
	return file.createWriteStream();
}