/* 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */


#include "common/scummsys.h"
#include "common/system.h"
#include "system.h"

#include "common/util.h"
#include "common/rect.h"
#include "common/savefile.h"

#include "osystem_ds.h"
#include "nds.h"
#include "dsmain.h"
#include "nds/registers_alt.h"
#include "config-manager.h"
#include "common/str.h"
#include "cdaudio.h"
#include "graphics/surface.h"
#include "touchkeyboard.h"

OSystem_DS* OSystem_DS::_instance = NULL;

OSystem_DS::OSystem_DS()
	: eventNum(0), lastPenFrame(0), queuePos(0), _mixer(NULL), _timer(NULL), _frameBufferExists(false)
{
//	eventNum = 0;
//	lastPenFrame = 0;
//	queuePos = 0;
	_instance = this;
//	_mixer = NULL;
  //  _timer = NULL;
	//_frameBufferExists = false;
}

OSystem_DS::~OSystem_DS() {
	delete _mixer;
	delete _timer;
}

int OSystem_DS::timerHandler(int t)
{
	DSTimerManager *tm = (DSTimerManager *)g_system->getTimerManager();
	tm->handler();
	return t;
}

void OSystem_DS::initBackend() {
	ConfMan.setInt("autosave_period", 0);
	ConfMan.setBool("FM_medium_quality", true);

	_mixer = new DSAudioMixer;
	_timer = new DSTimerManager;
	DS::setSoundProc(Audio::Mixer::mixCallback, _mixer);
    DS::setTimerCallback(&OSystem_DS::timerHandler, 10);

	OSystem::initBackend();
}

bool OSystem_DS::hasFeature(Feature f) {
//	consolePrintf("hasfeature\n");
	return (f == kFeatureVirtualKeyboard);
}

void OSystem_DS::setFeatureState(Feature f, bool enable) {
//	consolePrintf("setfeature f=%d e=%d\n", f, enable);
	if (f == kFeatureVirtualKeyboard) DS::setKeyboardIcon(enable);
}

bool OSystem_DS::getFeatureState(Feature f) {
//	consolePrintf("getfeat\n");
	if (f == kFeatureVirtualKeyboard) return DS::getKeyboardIcon();
	return false;
}

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


int OSystem_DS::getDefaultGraphicsMode() const {
	return 0;
}

bool OSystem_DS::setGraphicsMode(int mode) {
	return true;
}

bool OSystem_DS::setGraphicsMode(const char *name) {
//	consolePrintf("Set gfx mode %s\n", name);
	return true;
}

int OSystem_DS::getGraphicsMode() const {
	return -1;
}

void OSystem_DS::initSize(uint width, uint height) {
//	consolePrintf("Set gfx mode %d x %d\n", width, height);
	DS::setGameSize(width, height);
}

int16 OSystem_DS::getHeight() {
	return 200;
}

int16 OSystem_DS::getWidth() {
	return 320;
}

void OSystem_DS::setPalette(const byte *colors, uint start, uint num) {
//	consolePrintf("Set palette %d, %d colours\n", start, num);
//return;
	if (!DS::getIsDisplayMode8Bit()) return;
	for (unsigned int r = start; r < start + num; r++) {
		int red = *colors;
		int green = *(colors + 1);
		int blue = *(colors + 2);

		red >>= 3;
		green >>= 3;
		blue >>= 3;

//		if (r != 255)
		{
			BG_PALETTE[r] = red | (green << 5) | (blue << 10);
			if (!DS::getKeyboardEnable()) {
				BG_PALETTE_SUB[r] = red | (green << 5) | (blue << 10);
			}
		}
//		if (num == 16) consolePrintf("pal:%d r:%d g:%d b:%d\n", r, red, green, blue);

		colors += 4;
	}
}

bool OSystem_DS::grabRawScreen(Graphics::Surface* surf) {
	surf->create(DS::getGameWidth(), DS::getGameHeight(), 1);

	// Ensure we copy using 16 bit quantities due to limitation of VRAM addressing


	u16* image = (u16 *) DS::get8BitBackBuffer();
	for (int y = 0; y <  DS::getGameHeight(); y++)
	{
		DC_FlushRange(image + (y << 8), DS::getGameWidth());
		for (int x = 0; x < DS::getGameWidth() >> 1; x++)
		{
			*(((u16 *) (surf->pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[y << 8 + x];
		}
	}

	return true;
}

void OSystem_DS::grabPalette(unsigned char *colors, uint start, uint num) {
//	consolePrintf("Grabpalette");

	for (unsigned int r = start; r < start + num; r++) {
		*colors++ = (BG_PALETTE[r] & 0x001F) << 3;
		*colors++ = (BG_PALETTE[r] & 0x03E0) >> 5 << 3;
		*colors++ = (BG_PALETTE[r] & 0x7C00) >> 10 << 3;
	}
}


void OSystem_DS::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h)
{
//	consolePrintf("Copy rect %d, %d   %d, %d ", x, y, w, h);

	if (w <= 1) return;
	if (h < 0) return;
	if (!DS::getIsDisplayMode8Bit()) return;

	u16* bgSub = (u16 *) BG_GFX_SUB;
	u16* bg = (u16 *) DS::get8BitBackBuffer();
	u16* src = (u16 *) buf;

	if (DS::getKeyboardEnable()) {

		for (int dy = y; dy < y + h; dy++) {
			u16* dest = bg + (dy << 8) + (x >> 1);

			DC_FlushRange(src, w << 1);
			DC_FlushRange(dest, w << 1);
			dmaCopyHalfWords(3, src, dest, w);

			src += pitch >> 1;
		}

	} else {
		for (int dy = y; dy < y + h; dy++) {
			u16* dest1 = bg + (dy << 8) + (x >> 1);
			u16* dest2 = bgSub + (dy << 8) + (x >> 1);

			DC_FlushRange(src, w << 1);
			DC_FlushRange(dest1, w << 1);
			DC_FlushRange(dest2, w << 1);

			dmaCopyHalfWords(3, src, dest1, w);
			dmaCopyHalfWords(3, src, dest2, w);

			src += pitch >> 1;
		}
	}

//	consolePrintf("Done\n");



}

void OSystem_DS::updateScreen()
{

	if (_frameBufferExists)
	{
		// Copy temp framebuffer back to screen
		copyRectToScreen((byte *)_framebuffer.pixels, _framebuffer.pitch, 0, 0, _framebuffer.w, _framebuffer.h);

		// Free memory
		_framebuffer.free();

		_frameBufferExists = false;
	}

	DS::displayMode16BitFlipBuffer();
	DS::doSoundCallback();
//	DS::doTimerCallback();
	DS::addEventsToQueue();
}

void OSystem_DS::setShakePos(int shakeOffset) {
	DS::setShakePos(shakeOffset);
}

void OSystem_DS::showOverlay ()
{
//	consolePrintf("showovl\n");
	DS::displayMode16Bit();
}

void OSystem_DS::hideOverlay ()
{
	DS::displayMode8Bit();
}

void OSystem_DS::clearOverlay ()
{
	memset((u16 *) DS::get16BitBackBuffer(), 0, 512 * 256 * 2);
//	consolePrintf("clearovl\n");
}

void OSystem_DS::grabOverlay (OverlayColor *buf, int pitch)
{
//	consolePrintf("grabovl\n");
}

void OSystem_DS::copyRectToOverlay (const OverlayColor *buf, int pitch, int x, int y, int w, int h)
{
	u16* bg = (u16 *) DS::get16BitBackBuffer();
	u16* src = (u16 *) buf;

//	if (x + w > 256) w = 256 - x;
	//if (x + h > 256) h = 256 - y;

//	consolePrintf("Copy rect ovl %d, %d   %d, %d  %d\n", x, y, w, h, pitch);



	for (int dy = y; dy < y + h; dy++) {


		// Slow but save copy:
		for (int dx = x; dx < x + w; dx++) {

			*(bg + (dy * 512) + dx) = *src;
			//if ((*src) != 0) consolePrintf("%d,%d: %d   ", dx, dy, *src);
			//consolePrintf("%d,", *src);
			src++;
		}
		src += (pitch - w);

		// Fast but broken copy: (why?)
		/*
		REG_IME = 0;
		dmaCopy(src, bg + (dy << 9) + x, w * 2);
		REG_IME = 1;

		src += pitch;*/
	}

//	consolePrintf("Copy rect ovl done");

}

int16 OSystem_DS::getOverlayHeight()
{
//	consolePrintf("getovlheight\n");
	return getHeight();
}

int16 OSystem_DS::getOverlayWidth()
{
//	consolePrintf("getovlwid\n");
	return getWidth();
}


bool OSystem_DS::showMouse(bool visible)
{
	DS::setShowCursor(visible);
	return true;
}

void OSystem_DS::warpMouse(int x, int y)
{
}

void OSystem_DS::setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, byte keycolor, int targetCursorScale) {
	DS::setCursorIcon(buf, w, h, keycolor, hotspotX, hotspotY);
}

void OSystem_DS::addEvent(Common::Event& e) {
	eventQueue[queuePos++] = e;
}

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

	if (lastPenFrame != DS::getMillis()) {

		if (eventNum == queuePos) {
			eventNum = 0;
			queuePos = 0;
			// Bodge - this last event seems to be processed sometimes and not others.
			// So we make it something harmless which won't cause any adverse effects.
			event.type = Common::EVENT_KEYUP;
			event.kbd.ascii = 0;
			event.kbd.keycode = Common::KEYCODE_INVALID;
			event.kbd.flags = 0;
//			consolePrintf("type: %d\n", event.type);
			return false;
		} else {
			event = eventQueue[eventNum++];
//			consolePrintf("type: %d\n", event.type);
			return true;
		}
	}

	return false;

/*	if (lastPenFrame != DS::getMillis()) {
		if ((eventNum == 0)) {
			event.type = Common::EVENT_MOUSEMOVE;
			event.mouse = Common::Point(DS::getPenX(), DS::getPenY());
			eventNum = 1;
			return true;
		}
		if (eventNum == 1) {
			eventNum = 0;
			lastPenFrame = DS::getMillis();
			if (DS::getPenDown()) {
				event.type = Common::EVENT_LBUTTONDOWN;
				event.mouse = Common::Point(DS::getPenX(), DS::getPenY());
				consolePrintf("Down %d, %d  ", event.mouse.x, event.mouse.y);
				return true;
			} else if (DS::getPenReleased()) {
				event.type = Common::EVENT_LBUTTONUP;
				event.mouse = Common::Point(DS::getPenX(), DS::getPenY());
				consolePrintf("Up %d, %d ", event.mouse.x, event.mouse.y);
				return true;
			} else {
				return false;
			}
		}
	}*/

	return false;
}

uint32 OSystem_DS::getMillis()
{
	return DS::getMillis();
}

void OSystem_DS::delayMillis(uint msecs)
{
	int st = getMillis();
	DS::addEventsToQueue();
	DS::CD::update();

	DS::doSoundCallback();
	while (st + msecs >= getMillis()) {
		DS::doSoundCallback();
	}

	DS::doTimerCallback();
	DS::checkSleepMode();
	DS::addEventsToQueue();
}

OSystem::MutexRef OSystem_DS::createMutex(void)
{
	return NULL;
}

void OSystem_DS::lockMutex(MutexRef mutex)
{
}

void OSystem_DS::unlockMutex(MutexRef mutex)
{
}

void OSystem_DS::deleteMutex(MutexRef mutex)
{
}

void OSystem_DS::clearSoundCallback()
{
	consolePrintf("Clearing sound callback");
//	DS::setSoundProc(NULL, NULL);
}

int OSystem_DS::getOutputSampleRate() const
{
	return DS::getSoundFrequency();
}

bool OSystem_DS::openCD(int drive)
{
	return DS::CD::checkCD();
}

bool OSystem_DS::pollCD()
{
	return DS::CD::isPlaying();
}

void OSystem_DS::playCD(int track, int num_loops, int start_frame, int duration)
{
	DS::CD::playTrack(track, num_loops, start_frame, duration);
}

void OSystem_DS::stopCD()
{
	DS::CD::stopTrack();
}

void OSystem_DS::updateCD()
{
}

void OSystem_DS::quit()
{
/*	consolePrintf("Soft resetting...");
	IPC->reset = 1;
	REG_IE = 0;

	asm("swi 0x26\n");
	swiSoftReset();*/
}

void OSystem_DS::setWindowCaption(const char *caption)
{
}

void OSystem_DS::displayMessageOnOSD(const char *msg)
{
}

Common::SaveFileManager* OSystem_DS::getSavefileManager()
{
	bool forceSram;

	if (ConfMan.hasKey("forcesramsave", "ds")) {
		forceSram = ConfMan.getBool("forcesramsave", "ds");
	} else {
		forceSram = false;
	}
	if (forceSram) {
		consolePrintf("Using SRAM save method!\n");
	}

	if (DS::isGBAMPAvailable() && (!forceSram)) {
		return &mpSaveManager;
	} else {
		return &saveManager;
	}
}


Graphics::Surface* OSystem_DS::createTempFrameBuffer() {
	// For now, we create a full temporary screen surface, to which we copy the
	// the screen content. Later unlockScreen will copy everything back.
	// Not very nice nor efficient, but at least works, and is not worse
	// than in the bad old times where we used grabRawScreen + copyRectToScreen.
//	consolePrintf("lockScreen()\n");
	_framebuffer.create(DS::getGameWidth(), DS::getGameHeight(), 1);

	// Ensure we copy using 16 bit quantities due to limitation of VRAM addressing

	size_t imageStrideInBytes = DS::isCpuScalerEnabled()? DS::getGameWidth() : 512;
	size_t imageStrideInWords = imageStrideInBytes / 2;

	u16* image = (u16 *) DS::get8BitBackBuffer();
	for (int y = 0; y <  DS::getGameHeight(); y++) {
		DC_FlushRange(image + (y * imageStrideInWords), DS::getGameWidth());
		for (int x = 0; x < DS::getGameWidth() >> 1; x++) {
			*(((u16 *) (_framebuffer.pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[(y << 8) + x];
//			*(((u16 *) (surf->pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[y * imageStrideInWords + x];
		}
	}
//	consolePrintf("lockScreen() done\n");
	_frameBufferExists = true;

	return &_framebuffer;
}


Graphics::Surface *OSystem_DS::lockScreen() {
	if (!_frameBufferExists) {
		createTempFrameBuffer();
	}

	return &_framebuffer;
}

void OSystem_DS::unlockScreen() {

//	consolePrintf("unlockScreen()\n");

	// Copy temp framebuffer back to screen
//	copyRectToScreen((byte *)_framebuffer.pixels, _framebuffer.pitch, 0, 0, _framebuffer.w, _framebuffer.h);

	// Free memory
//	_framebuffer.free();
//	consolePrintf("unlockScreen() done\n");
}

void OSystem_DS::setFocusRectangle(const Common::Rect& rect) {
	DS::setTalkPos(rect.left + rect.width() / 2, rect.top + rect.height() / 2);
}

void OSystem_DS::clearFocusRectangle() {

}


void OSystem_DS::addAutoComplete(const char *word) {
	DS::addAutoComplete((char *) word);
}

void OSystem_DS::clearAutoComplete() {
	DS::clearAutoComplete();
}

void OSystem_DS::setCharactersEntered(int count) {
	DS::setCharactersEntered(count);
}

OSystem *OSystem_DS_create() {
	return new OSystem_DS();
}