/*	gc_n64_usb : Gamecube or N64 controller to USB firmware
	Copyright (C) 2007-2015  Raphael Assenat <raph@raphnet.net>

	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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include "gamepads.h"
#include "n64.h"
#include "gcn64_protocol.h"

#undef BUTTON_A_RUMBLE_TEST

/*********** prototypes *************/
static void n64Init(void);
static char n64Update(void);
static char n64Changed(void);
static void n64GetReport(gamepad_data *dst);
static void n64SetVibration(char enable);

static char must_rumble = 0;
#ifdef BUTTON_A_RUMBLE_TEST
static char force_rumble = 0;
#endif

static void n64Init(void)
{
	n64Update();
}

#define RSTATE_INIT			0
#define RSTATE_OFF			1
#define RSTATE_TURNON		2
#define RSTATE_ON			3
#define RSTATE_TURNOFF		4
#define RSTATE_UNAVAILABLE	5
static unsigned char n64_rumble_state = RSTATE_UNAVAILABLE;
unsigned char tmpdata[40];

static char initRumble(void)
{
	int count;
	unsigned char data[4];

	tmpdata[0] = N64_EXPANSION_WRITE;
	tmpdata[1] = 0x80;
	tmpdata[2] = 0x01;
	memset(tmpdata+3, 0x80, 32);

	count = gcn64_transaction(tmpdata, 35, data, sizeof(data));
	if (count == 1)
		return 0;

	return -1;
}

static char controlRumble(char enable)
{
	int count;
	unsigned char data[4];

	tmpdata[0] = N64_EXPANSION_WRITE;
	tmpdata[1] = 0xc0;
	tmpdata[2] = 0x1b;
	memset(tmpdata+3, enable ? 0x01 : 0x00, 32);
	count = gcn64_transaction(tmpdata, 35, data, sizeof(data));
	if (count == 1)
		return 0;

	return -1;
}

static char n64Update(void)
{
	unsigned char count;
	unsigned char x,y;
	unsigned char btns1, btns2;
	unsigned char caps[N64_CAPS_REPLY_LENGTH];
	unsigned char status[N64_GET_STATUS_REPLY_LENGTH];

	/* Pad answer to N64_GET_CAPABILITIES
	 *
	 * 0x050000 : 0000 0101 0000 0000 0000 0000 : No expansion pack
	 * 0x050001 : 0000 0101 0000 0000 0000 0001 : With expansion pack
	 * 0x050002 : 0000 0101 0000 0000 0000 0010 : Expansion pack removed
	 *
	 * Bit 0 tells us if there is something connected to the expansion port.
	 * Bit 1 tells is if there was something connected that has been removed.
	 */
	tmpdata[0] = N64_GET_CAPABILITIES;
	count = gcn64_transaction(tmpdata, 1, caps, sizeof(caps));
	if (count != N64_CAPS_REPLY_LENGTH) {
		// a failed read could mean the pack or controller was gone. Init
		// will be necessary next time we detect a pack is present.
		n64_rumble_state = RSTATE_INIT;
		return -1;
	}

	/* Detect when a pack becomes present and schedule initialisation when it happens. */
	if ((caps[2] & 0x01) && (n64_rumble_state == RSTATE_UNAVAILABLE)) {
		n64_rumble_state = RSTATE_INIT;
	}

	/* Detect when a pack is removed. */
	if (!(caps[2] & 0x01) || (caps[2] & 0x02) ) {
		n64_rumble_state = RSTATE_UNAVAILABLE;
	}
#ifdef BUTTON_A_RUMBLE_TEST
	must_rumble = force_rumble;
	//printf("Caps: %02x %02x %02x\r\n", caps[0], caps[1], caps[2]);
#endif


	switch (n64_rumble_state)
	{
		case RSTATE_INIT:
			/* Retry until the controller answers with a full byte. */
			if (initRumble() != 0) {
				if (initRumble() != 0) {
					n64_rumble_state = RSTATE_UNAVAILABLE;
				}
				break;
			}

			if (must_rumble) {
				controlRumble(1);
				n64_rumble_state = RSTATE_ON;
			} else {
				controlRumble(0);
				n64_rumble_state = RSTATE_OFF;
			}
			break;

		case RSTATE_TURNON:
			if (0 == controlRumble(1)) {
				n64_rumble_state = RSTATE_ON;
			}
			break;

		case RSTATE_TURNOFF:
			if (0 == controlRumble(0)) {
				n64_rumble_state = RSTATE_OFF;
			}
			break;

		case RSTATE_ON:
			if (!must_rumble) {
				 controlRumble(0);
				 n64_rumble_state = RSTATE_OFF;
			}
			break;

		case RSTATE_OFF:
			if (must_rumble) {
				 controlRumble(1);
				n64_rumble_state = RSTATE_ON;
			}
			break;
	}

	tmpdata[0] = N64_GET_STATUS;
	count = gcn64_transaction(tmpdata, 1, status, sizeof(status));
	if (count != N64_GET_STATUS_REPLY_LENGTH) {
		return -1;
	}

/*
	Bit	Function
	0	A
	1	B
	2	Z
	3	Start
	4	Directional Up
	5	Directional Down
	6	Directional Left
	7	Directional Right
	8	unknown (always 0)
	9	unknown (always 0)
	10	L
	11	R
	12	C Up
	13	C Down
	14	C Left
	15	C Right
	16-23: analog X axis
	24-31: analog Y axis
 */

	btns1 = status[0];
	btns2 = status[1];
	x = status[2];
	y = status[3];

#ifdef BUTTON_A_RUMBLE_TEST
	if (btns1 & 0x80) {
		force_rumble = 1;
	} else {
		force_rumble = 0;
	}
#endif

	last_built_report.pad_type = PAD_TYPE_N64;
	last_built_report.n64.buttons = (btns1 << 8) | btns2;
	last_built_report.n64.x = x;
	last_built_report.n64.y = y;

	/* Copy all the data as-is for the raw field */
	last_built_report.n64.raw_data[0] = btns1;
	last_built_report.n64.raw_data[1] = btns2;
	last_built_report.n64.raw_data[2] = x;
	last_built_report.n64.raw_data[3] = y;

	/* Some cheap non-official controllers
	 * use the full 8 bit range instead of the
	 * normal +-80 observed on official controllers. In
	 * particular, some units (but not all!) produced
	 * by TTX. The symptom is usually "The joystick
	 * left direction does not work".
	 *
	 * So I limit values to the -127 to +127 range,
	 * otherwise it causes problem later
	 * when the sign is inverted. Using 16 bit
	 * signed numbers instead of 8 bit would solve
	 * this, but this is only for cheap, not
	 * even worth using controllers so I don't
	 * care.
	 *
	 * The joystick will now "work" as bad as it would
	 * on a N64, or maybe a little better. This should
	 * help people realise they got what the paid for
	 * instead of suspecting the adapter. */
    if (last_built_report.n64.x == -128)
        last_built_report.n64.x = -127;
    if (last_built_report.n64.y == -128)
        last_built_report.n64.y = -127;

	return 0;
}

static char n64Probe(void)
{
	int count;
	char i;
	unsigned char tmp;
	unsigned char data[4];

	/* Pad answer to N64_GET_CAPABILITIES
	 *
	 * 0x050000 : 0000 0101 0000 0000 0000 0000 : No expansion pack
	 * 0x050001 : 0000 0101 0000 0000 0000 0001 : With expansion pack
	 * 0x050002 : 0000 0101 0000 0000 0000 0010 : Expansion pack removed
	 *
	 * Bit 0 tells us if there is something connected to the expansion port.
	 * Bit 1 tells is if there was something connected that has been removed.
	 */

	n64_rumble_state = RSTATE_UNAVAILABLE;

	for (i=0; i<15; i++)
	{
		_delay_ms(30);

		tmp = N64_GET_CAPABILITIES;
		count = gcn64_transaction(&tmp, 1, data, sizeof(data));

		if (count == N64_CAPS_REPLY_LENGTH) {
			return 1;
		}
	}
	return 0;
}

static char n64Changed(void)
{
	return memcmp(&last_built_report, &last_sent_report, sizeof(gamepad_data));
}

static void n64GetReport(gamepad_data *dst)
{
	if (dst)
		memcpy(dst, &last_built_report, sizeof(gamepad_data));

	memcpy(&last_sent_report, &last_built_report, sizeof(gamepad_data));
}

static void n64SetVibration(char enable)
{
	must_rumble = enable;
}

static Gamepad N64Gamepad = {
	.init					= n64Init,
	.update					= n64Update,
	.changed				= n64Changed,
	.getReport				= n64GetReport,
	.probe					= n64Probe,
	.setVibration			= n64SetVibration,
};

Gamepad *n64GetGamepad(void)
{
	return &N64Gamepad;
}