/* 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.
 *
 */

//////////////////////////////////////////////////////////////////////
// Simple ARM7 stub (sends RTC, TSC, and X/Y data to the ARM 9)
// -- joat
// -- modified by Darkain and others
//////////////////////////////////////////////////////////////////////

// #define USE_LIBCARTRESET

#include <nds.h>

#include <bios.h>
#include <arm7/touch.h>
#include <arm7/clock.h>
#include <arm7/audio.h>
#include <system.h>
#include <stdlib.h>
#include <string.h>
#include <registers_alt.h> // Needed for SOUND_CR
#include <NDS/scummvm_ipc.h>
//////////////////////////////////////////////////////////////////////
#ifdef USE_DEBUGGER
#include <dswifi7.h>
#endif

#include "cartreset_nolibfat.h"

#define TOUCH_CAL_X1 (*(vs16 *)0x027FFCD8)
#define TOUCH_CAL_Y1 (*(vs16 *)0x027FFCDA)
#define TOUCH_CAL_X2 (*(vs16 *)0x027FFCDE)
#define TOUCH_CAL_Y2 (*(vs16 *)0x027FFCE0)
#define SCREEN_WIDTH    256
#define SCREEN_HEIGHT   192
s32 TOUCH_WIDTH  = TOUCH_CAL_X2 - TOUCH_CAL_X1;
s32 TOUCH_HEIGHT = TOUCH_CAL_Y2 - TOUCH_CAL_Y1;
s32 TOUCH_OFFSET_X = ( ((SCREEN_WIDTH  - 60) * TOUCH_CAL_X1) / TOUCH_WIDTH  ) - 28;
s32 TOUCH_OFFSET_Y = ( ((SCREEN_HEIGHT - 60) * TOUCH_CAL_Y1) / TOUCH_HEIGHT ) - 28;

vu8 *soundData;

vu8 *soundBuffer;
vu8 *arm9Buffer;
bool soundFilled[4];

int playingSection;

bool needSleep = false;
int temp;

int adpcmBufferNum = 0;

// those are pixel positions of the two points you click when calibrating
#define TOUCH_CNTRL_X1 (*(vu8 *)0x027FFCDC)
#define TOUCH_CNTRL_Y1 (*(vu8 *)0x027FFCDD)
#define TOUCH_CNTRL_X2 (*(vu8 *)0x027FFCE2)
#define TOUCH_CNTRL_Y2 (*(vu8 *)0x027FFCE3)

/*
void startSound(int sampleRate, const void *data, uint32 bytes, u8 channel = 0, u8 vol = 0x7F, u8 pan = 63, u8 format = 0) {
	SCHANNEL_TIMER(channel)  = SOUND_FREQ(sampleRate);
	SCHANNEL_SOURCE(channel) = (uint32)data;
	SCHANNEL_LENGTH(channel) = bytes;
	SCHANNEL_CR(channel)     = SOUND_ENABLE | SOUND_ONE_SHOT | SOUND_VOL(vol) | SOUND_PAN(pan) | (format==1?SOUND_8BIT:SOUND_16BIT);
}

s8 getFreeSoundChannel() {
	for (int i = 0; i < 16; i++) {
		if ( (SCHANNEL_CR(i) & SOUND_ENABLE) == 0 )
			return i;
	}
	return -1;
}
*/

s8 getFreeSoundChannel() {
	// return 0;
	for (int i = 0; i < 16; i++) {
		if ( (SCHANNEL_CR(i) & SCHANNEL_ENABLE) == 0 )
			return i;
	}
	return -1;
}

void startSound(int sampleRate, const void *data, uint32 bytes, u8 channel = 0, u8 vol = 0x7F, u8 pan = 63, u8 format = 0) {
	// REG_IME = IME_DISABLE;

	channel = getFreeSoundChannel();
	/*
	if (format == 2) {
		channel = 1;
	} else {
		channel = 0;
	}
	*/

	if (channel > 1)
		channel = 1;

	bytes &= ~7; // Multiple of 4 bytes!
	// bytes += 4;

	SCHANNEL_CR(channel) = 0;
	SCHANNEL_TIMER(channel)  = SOUND_FREQ(sampleRate);
	SCHANNEL_SOURCE(channel) = (uint32)data;
	SCHANNEL_LENGTH(channel) = (bytes & 0x7FFFFFFF) >> 2;
	SCHANNEL_REPEAT_POINT(channel) = 0;

	SCHANNEL_CR(channel + 2) = 0;
	SCHANNEL_TIMER(channel + 2)  = SOUND_FREQ(sampleRate);
	SCHANNEL_SOURCE(channel + 2) = (uint32)data;
	SCHANNEL_LENGTH(channel + 2) = (bytes & 0x7FFFFFFF) >> 2;
	SCHANNEL_REPEAT_POINT(channel + 2) = 0;

	uint32 flags = SCHANNEL_ENABLE | SOUND_VOL(vol) | SOUND_PAN(pan);

	switch (format) {
	case 1: {
		flags |= SOUND_FORMAT_8BIT;
		flags |= SOUND_REPEAT; // | (1 << 15);
		break;
	}

	case 0: {
		flags |= SOUND_FORMAT_16BIT;
		flags |= SOUND_REPEAT; // | (1 << 15);
		break;
	}

	case 2: {
		flags |= SOUND_FORMAT_ADPCM;
		flags |= SOUND_ONE_SHOT; // | (1 << 15);

		SCHANNEL_SOURCE(channel) = (unsigned int)IPC->adpcm.buffer[0];
		// bytes += 32;
		SCHANNEL_LENGTH(channel) = ((bytes + 4) & 0x7FFFFFFF) >> 2;

		SCHANNEL_CR(channel + 1) = 0;
		SCHANNEL_SOURCE(channel + 1) = (unsigned int)IPC->adpcm.buffer[0];
		SCHANNEL_LENGTH(channel + 1) = ((bytes + 4) & 0x7FFFFFFF) >> 2;
		SCHANNEL_TIMER(channel + 1)  = SOUND_FREQ(sampleRate);
		SCHANNEL_REPEAT_POINT(channel + 1) = 0;
		SCHANNEL_CR(channel + 1) = flags;
		temp = bytes;
		adpcmBufferNum = 0;
		break;
	}
	}

	/*
	if (bytes & 0x80000000) {
		flags |= SOUND_REPEAT;
	} else {
	}
	*/

	soundData = (vu8 *)data;

	SCHANNEL_CR(channel)     = flags;
	SCHANNEL_CR(channel + 2) = flags;

	if (channel == 0) {
		for (volatile int i = 0; i < 16384 * 2; i++) {
			// Delay loop - this makes everything stay in sync!
		}

		TIMER0_CR = 0;
		TIMER0_DATA = SOUND_FREQ(sampleRate) * 2;
		TIMER0_CR = TIMER_ENABLE | TIMER_DIV_1;

		TIMER1_CR = 0;
		TIMER1_DATA = 65536 - ((bytes & 0x7FFFFFFF) >> 3); // Trigger four times during the length of the buffer
		TIMER1_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_CASCADE;

		playingSection = 0;
	} else {
		for (volatile int i = 0; i < 16384 * 2; i++) {
			// Delay loop - this makes everything stay in sync!
		}

		TIMER2_CR = 0;
		TIMER2_DATA = SOUND_FREQ(sampleRate) * 2;
		TIMER2_CR = TIMER_ENABLE | TIMER_DIV_1;

		TIMER3_CR = 0;
		TIMER3_DATA = 65536 - ((bytes & 0x7FFFFFFF) >> 3); // Trigger four times during the length of the buffer
		TIMER3_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_CASCADE;

		for (int r = 0; r < 4; r++) {
			// IPC->streamFillNeeded[r] = true;
		}

		IPC->streamPlayingSection = 0;
	}

	// IPC->fillSoundFirstHalf = true;
	// IPC->fillSoundSecondHalf = true;
	// soundFirstHalf = true;

	// REG_IME = IME_ENABLE;
}

void stopSound(int chan) {
	SCHANNEL_CR(chan) = 0;
}

void DummyHandler() {
	REG_IF = REG_IF;
}

void powerManagerWrite(uint32 command, u32 data, bool enable) {
	uint16 result;
	SerialWaitBusy();

	// Write the command and wait for it to complete
	REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz | (1 << 11);
	REG_SPIDATA = command | 0x80;
	SerialWaitBusy();

	// Write the second command and clock in the data
	REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz;
	REG_SPIDATA = 0;
	SerialWaitBusy();

	result = REG_SPIDATA & 0xFF;

	// Write the command and wait for it to complete
	REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz | (1 << 11);
	REG_SPIDATA = command;
	SerialWaitBusy();

	// Write the second command and clock in the data
	REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz;
	REG_SPIDATA = enable ? (result | data) : (result & ~data);
	SerialWaitBusy();
}

/*
void performSleep() {
	powerManagerWrite(0, 0x30, true);

	// Here, I set up a dummy interrupt handler, then trigger all interrupts.
	// These are just aknowledged by the handler without doing anything else.
	// Why?  Because without it the sleep mode will only happen once, and then
	// never again.  I got the idea from reading the MoonShell source.
	IME = 0;
	u32 irq = (u32)IRQ_HANDLER;
	IRQ_HANDLER = DummyHandler;
	IF = ~0;
	IME = 1;

	// Now save which interrupts are enabled, then set only the screens unfolding
	// interrupt to be enabled, so that the first interrupt that happens is the
	// one I want.
	int saveInts = IE;

	IE = IRQ_TIMER0; // Screens unfolding interrupt

	// Now call the sleep function in the bios
	bool b;
	do {
		TIMER0_CR = 0;
		TIMER0_DATA = TIMER_FREQ(20);
		TIMER0_CR = TIMER_ENABLE | TIMER_DIV_64;

		swiDelay(100);

		swiSleep();

		swiDelay(100);

		powerManagerWrite(0, 0x30, b = !b);
	} while (!(TIMER0_CR & TIMER_ENABLE));

	TIMER0_CR = 0;

	// We're back from sleep, now restore the interrupt state and IRQ handler
	IRQ_HANDLER = (void (*)())irq;
	IE = saveInts;
	IF = ~0;
	IME = 1;

	powerManagerWrite(0, 0x30, false);
}
*/

void performSleep() {
	powerManagerWrite(0, 0x30, true);

	IPC->performArm9SleepMode = true; // Tell ARM9 to sleep

	// u32 irq = (u32)IRQ_HANDLER;
	// IRQ_HANDLER = DummyHandler;
	// POWER_CR &= ~POWER_SOUND;

	// int saveInts = REG_IE;
	// REG_IE = (1 << 22) | IRQ_VBLANK; // Lid open
	// *((u32 *)(0x0380FFF8)) = *((u32 *)(0x0380FFF8)) | (REG_IE & REG_IF);
	// VBLANK_INTR_WAIT_FLAGS = IRQ_VBLANK;

	int r = 0;
	while ((REG_KEYXY & (1 << 7))) { // Wait for lid to open
		swiDelay(1000000);
		r++;
	}

	// IRQ_HANDLER = (void (*)())irq;
	IPC->performArm9SleepMode = false; // Tell ARM9 to wake up
	// REG_IE = saveInts;

	// POWER_CR |= POWER_SOUND;

	powerManagerWrite(0, 0x30, false);
}

void powerOff() {
	powerManagerWrite(0, 0x40, true);
}

void InterruptTimer1() {
	IPC->fillNeeded[playingSection] = true;
	soundFilled[playingSection] = false;

	if (playingSection == 3) {
		// IME = IME_DISABLED;

		// while (SCHANNEL_CR(0) & SCHANNEL_ENABLE) {
		// }
		// SCHANNEL_CR(0) &= ~SCHANNEL_ENABLE;

		// SCHANNEL_CR(0) |= SCHANNEL_ENABLE;
		// TIMER1_CR = 0;
		// TIMER1_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_CASCADE;

		playingSection = 0;

		// IME = IME_ENABLED;
	} else {
		playingSection++;
	}

	IPC->playingSection = playingSection;

/*	for (int r = 0; r < 4; r++) {
		//if ((!soundFilled[r]) && (!IPC->fillNeeded[playingSection])) {
			memcpy((void *) (soundBuffer + (r * 1024)), (void *) (arm9Buffer + (r * 1024)), 1024);

			vu16 *p = (vu16 *) (soundBuffer);
			//for (int t = 0; t < 2048; t++) {
		//		*(p + t) = (t & 1)? 0xF000: 0x0000;
			//}
			soundFilled[r] = true;
		//}
	}*/
}

void InterruptTimer3() {
	while (IPC->adpcm.semaphore); // Wait for buffer to become free if needed
	IPC->adpcm.semaphore = true; // Lock the buffer structure to prevent clashing with the ARM7

	IPC->streamFillNeeded[IPC->streamPlayingSection] = true;

	if (IPC->streamPlayingSection == 3) {
		IPC->streamPlayingSection = 0;
	} else {
		IPC->streamPlayingSection++;
	}

	IPC->adpcm.semaphore = false;
}

// IPC->performArm9SleepMode = false;

// precalculate some values
// static int16 TOUCH_WIDTH  = TOUCH_CAL_X2 - TOUCH_CAL_X1;
// static int16 TOUCH_HEIGHT = TOUCH_CAL_Y2 - TOUCH_CAL_Y1;
// static int16 CNTRL_WIDTH  = TOUCH_CNTRL_X2 - (TOUCH_CNTRL_X1 - 8);
// static int16 CNTRL_HEIGHT = TOUCH_CNTRL_Y2 - (TOUCH_CNTRL_Y1 - 8);

 void InterruptVBlank() {
	uint16 but = 0, x = 0, y = 0, xpx = 0, ypx = 0, z1 = 0, z2 = 0, batt = 0, aux = 0;
	int t1 = 0, t2 = 0;
	uint32 temp = 0;
	uint8 ct[sizeof(IPC->curtime)];
	static int heartbeat = 0;
	// Update the heartbeat
	heartbeat++;

	// Read the X/Y buttons and the /PENIRQ line
	but = REG_KEYXY;
	if (!(but & 0x40)) {
		// Read the touch screen
		touchPosition p;
		touchReadXY(&p);

		// x = touchRead(TSC_MEASURE_X);
		// y = touchRead(TSC_MEASURE_Y);

		x = p.rawx;
		y = p.rawy;

		// xpx = p.px;
		// ypx = p.py;

		xpx = ( ((SCREEN_WIDTH  - 60) * x) / TOUCH_WIDTH  ) - TOUCH_OFFSET_X;
		ypx = ( ((SCREEN_HEIGHT - 60) * y) / TOUCH_HEIGHT ) - TOUCH_OFFSET_Y;

		// xpx = (IPC->touchX - (int16) TOUCH_CAL_X1) * CNTRL_WIDTH  / TOUCH_WIDTH  + (int16) (TOUCH_CNTRL_X1 - 8);
		// ypx = (IPC->touchY - (int16) TOUCH_CAL_Y1) * CNTRL_HEIGHT / TOUCH_HEIGHT + (int16) (TOUCH_CNTRL_Y1 - 8);

		z1 = touchRead(TSC_MEASURE_Z1);
		z2 = touchRead(TSC_MEASURE_Z2);
	}

	// Check if screen is folded
	if (but & (1 << 7)) {
		needSleep = true;
	}

	batt = touchRead(TSC_MEASURE_BATTERY);
	aux  = touchRead(TSC_MEASURE_AUX);

	// Read the time
	rtcGetTime((uint8 *)ct);
	BCDToInteger((uint8 *)&(ct[1]), 7);

	// Read the temperature
	temp = touchReadTemperature(&t1, &t2);

	// Update the IPC struct
	IPC->heartbeat = heartbeat;
	IPC->buttons   = but;
	IPC->touchX    = x;
	IPC->touchY    = y;
	IPC->touchXpx  = xpx;
	IPC->touchYpx  = ypx;
	IPC->touchZ1   = z1;
	IPC->touchZ2   = z2;
	IPC->battery   = batt;
	IPC->aux       = aux;

	for (u32 i = 0; i < sizeof(ct); i++) {
		IPC->curtime[i] = ct[i];
	}

	IPC->temperature = temp;
	IPC->tdiode1 = t1;
	IPC->tdiode2 = t2;

	// sound code  :)
	TransferSound *snd = IPC->soundData;
	IPC->soundData = 0;
	if (snd) {
		for (int i = 0; i < snd->count; i++) {
			s8 chan = getFreeSoundChannel();
			if (snd->data[i].rate > 0) {
				if (chan >= 0) {
					startSound(snd->data[i].rate, snd->data[i].data, snd->data[i].len, chan, snd->data[i].vol, snd->data[i].pan, snd->data[i].format);
				}
			} else {
				stopSound(-snd->data[i].rate);
			}
		}
	}

#ifdef USE_DEBUGGER
	Wifi_Update(); // update wireless in vblank
#endif
}

#ifdef USE_DEBUGGER
// callback to allow wifi library to notify arm9
void arm7_synctoarm9() { // send fifo message
	REG_IPC_FIFO_TX = 0x87654321;
}

// interrupt handler to allow incoming notifications from arm9
void arm7_fifo() { // check incoming fifo messages
	u32 msg = REG_IPC_FIFO_RX;
	if (msg == 0x87654321)
		Wifi_Sync();
}

void initDebugger() {
	// set up the wifi irq
	irqSet(IRQ_WIFI, Wifi_Interrupt); // set up wifi interrupt
	irqEnable(IRQ_WIFI);

	// get them talking together

	// sync with arm9 and init wifi
	u32 fifo_temp;

	while (1) { // wait for magic number
		while (REG_IPC_FIFO_CR & IPC_FIFO_RECV_EMPTY)
			swiWaitForVBlank();

		fifo_temp = REG_IPC_FIFO_RX;

		if (fifo_temp == 0x12345678)
			break;
	}

	while (REG_IPC_FIFO_CR & IPC_FIFO_RECV_EMPTY)
		swiWaitForVBlank();

	fifo_temp = REG_IPC_FIFO_RX; // give next value to wifi_init
	Wifi_Init(fifo_temp);

	irqSet(IRQ_FIFO_NOT_EMPTY,arm7_fifo); // set up fifo irq
	irqEnable(IRQ_FIFO_NOT_EMPTY);
	REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_RECV_IRQ;

	Wifi_SetSyncHandler(arm7_synctoarm9); // allow wifi lib to notify arm9
	// arm7 wifi init complete
}
#endif

#ifdef USE_LIBCARTRESET
void reboot() {
	cartExecute();
}
#endif

int main(int argc, char ** argv) {
#ifdef USE_DEBUGGER
	REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
#endif

	// Reset the clock if needed
	rtcReset();

	// enable sound
	// powerOn(POWER_SOUND);
	SOUND_CR = SOUND_ENABLE | SOUND_VOL(0x7F);
	IPC->soundData = 0;
	IPC->reset = false;

	// fifoInit();

	for (int r = 0; r < 8; r++) {
		IPC->adpcm.arm7Buffer[r] = (u8 *)malloc(512);
	}

	for (int r = 0; r < 4; r++) {
		soundFilled[r] = false;
	}

	// Set up the interrupt handler

	irqInit();

	irqSet(IRQ_VBLANK, InterruptVBlank);
	irqEnable(IRQ_VBLANK);

	irqSet(IRQ_TIMER1, InterruptTimer1);
	irqEnable(IRQ_TIMER1);

	irqSet(IRQ_TIMER3, InterruptTimer3);
	irqEnable(IRQ_TIMER3);

	/*
	REG_IME = 0;
	IRQ_HANDLER = &InterruptHandler;
	REG_IE = IRQ_VBLANK | IRQ_TIMER1 | IRQ_TIMER3;
	REG_IF = ~0;
	DISP_SR = DISP_VBLANK_IRQ;
	REG_IME = 1;
	*/

#ifdef USE_DEBUGGER
	initDebugger();
#endif

	// Keep the ARM7 out of main RAM
	while ((1)) {
		if (needSleep) {
			performSleep();
			needSleep = false;
		}

#ifdef USE_LIBCARTRESET
		if (passmeloopQuery()) {
			reboot();
		}
#endif

		if (IPC->reset) {
			powerOff();
		}

		swiWaitForVBlank();
	}

	return 0;
}