/*
 *	wiiuse
 *
 *	Written By:
 *		Michael Laforest	< para >
 *		Email: < thepara (--AT--) g m a i l [--DOT--] com >
 *
 *	Copyright 2006-2007
 *
 *	This file is part of wiiuse.
 *
 *	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/>.
 *
 *	$Header$
 *
 */

/**
 *	@file
 *	@brief Handles wiimote events.
 *
 *	The file includes functions that handle the events
 *	that are sent from the wiimote to us.
 */

#include "wiiuse_internal.h"
#include "events.h"

#include "classic.h"                    /* for classic_ctrl_disconnected, etc */
#include "dynamics.h"                   /* for calculate_gforce, etc */
#include "guitar_hero_3.h"              /* for guitar_hero_3_disconnected, etc */
#include "ir.h"                         /* for calculate_basic_ir, etc */
#include "nunchuk.h"                    /* for nunchuk_disconnected, etc */
#include "wiiboard.h"                   /* for wii_board_disconnected, etc */
#include "motion_plus.h"                /* for motion_plus_disconnected, etc */

#include "os.h"							/* for wiiuse_os_poll */

#include <stdio.h>                      /* for printf, perror */
#include <stdlib.h>                     /* for free, malloc */
#include <string.h>                     /* for memcpy, memset */

static void event_data_read(struct wiimote_t* wm, byte* msg);
static void event_data_write(struct wiimote_t *wm, byte *msg);
static void event_status(struct wiimote_t* wm, byte* msg);
static void handle_expansion(struct wiimote_t* wm, byte* msg);

static void save_state(struct wiimote_t* wm);
static int state_changed(struct wiimote_t* wm);

/**
 *	@brief Poll the wiimotes for any events.
 *
 *	@param wm		An array of pointers to wiimote_t structures.
 *	@param wiimotes	The number of wiimote_t structures in the \a wm array.
 *
 *	@return Returns number of wiimotes that an event has occurred on.
 *
 *	It is necessary to poll the wiimote devices for events
 *	that occur.  If an event occurs on a particular wiimote,
 *	the event variable will be set.
 */
int wiiuse_poll(struct wiimote_t** wm, int wiimotes) {
	return wiiuse_os_poll(wm, wiimotes);
}

int wiiuse_update(struct wiimote_t** wiimotes, int nwiimotes, wiiuse_update_cb callback) {
	int evnt = 0;
	if (wiiuse_poll(wiimotes, nwiimotes)) {
		static struct wiimote_callback_data_t s;
		int i = 0;
		for (; i < nwiimotes; ++i) {
			switch (wiimotes[i]->event) {
				case WIIUSE_NONE:
					break;
				default:
					/* this could be:  WIIUSE_EVENT, WIIUSE_STATUS, WIIUSE_CONNECT, etc.. */
					s.uid = wiimotes[i]->unid;
					s.leds = wiimotes[i]->leds;
					s.battery_level = wiimotes[i]->battery_level;
					s.accel = wiimotes[i]->accel;
					s.orient = wiimotes[i]->orient;
					s.gforce = wiimotes[i]->gforce;
					s.ir = wiimotes[i]->ir;
					s.buttons = wiimotes[i]->btns;
					s.buttons_held = wiimotes[i]->btns_held;
					s.buttons_released = wiimotes[i]->btns_released;
					s.event = wiimotes[i]->event;
					s.state = wiimotes[i]->state;
					s.expansion = wiimotes[i]->exp;
					callback(&s);
					evnt++;
					break;
			}
		}
	}
	return evnt;
}

/**
 *	@brief Called on a cycle where no significant change occurs.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 */
void idle_cycle(struct wiimote_t* wm) {
	/*
	 *	Smooth the angles.
	 *
	 *	This is done to make sure that on every cycle the orientation
	 *	angles are smoothed.  Normally when an event occurs the angles
	 *	are updated and smoothed, but if no packet comes in then the
	 *	angles remain the same.  This means the angle wiiuse reports
	 *	is still an old value.  Smoothing needs to be applied in this
	 *	case in order for the angle it reports to converge to the true
	 *	angle of the device.
	 */
	if (WIIUSE_USING_ACC(wm) && WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING)) {
		apply_smoothing(&wm->accel_calib, &wm->orient, SMOOTH_ROLL);
		apply_smoothing(&wm->accel_calib, &wm->orient, SMOOTH_PITCH);
	}

	/* clear out any old read requests */
	clear_dirty_reads(wm);
}


/**
 *	@brief Clear out all old 'dirty' read requests.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 */
void clear_dirty_reads(struct wiimote_t* wm) {
	struct read_req_t* req = wm->read_req;

	while (req && req->dirty) {
		WIIUSE_DEBUG("Cleared old read request for address: %x", req->addr);

		wm->read_req = req->next;
		free(req);
		req = wm->read_req;
	}
}

/**
 *	@brief Handle accel data in a wiimote message.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 *	@param msg		The message specified in the event packet.
 */
static void handle_wm_accel(struct wiimote_t* wm, byte* msg) {
	wm->accel.x = msg[2];
	wm->accel.y = msg[3];
	wm->accel.z = msg[4];

	/* calculate the remote orientation */
	calculate_orientation(&wm->accel_calib, &wm->accel, &wm->orient, WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING));

	/* calculate the gforces on each axis */
	calculate_gforce(&wm->accel_calib, &wm->accel, &wm->gforce);
}


/**
 *	@brief Analyze the event that occurred on a wiimote.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 *	@param event	The event that occurred.
 *	@param msg		The message specified in the event packet.
 *
 *	Pass the event to the registered event callback.
 */
void propagate_event(struct wiimote_t* wm, byte event, byte* msg) {
	save_state(wm);

	switch (event) {
		case WM_RPT_BTN: {
				/* button */
				wiiuse_pressed_buttons(wm, msg);
				break;
			}
		case WM_RPT_BTN_ACC: {
				/* button - motion */
				wiiuse_pressed_buttons(wm, msg);

				handle_wm_accel(wm, msg);

				break;
			}
		case WM_RPT_READ: {
				/* data read */
				event_data_read(wm, msg);

				/* yeah buttons may be pressed, but this wasn't an "event" */
				return;
			}
		case WM_RPT_CTRL_STATUS: {
				/* controller status */
				event_status(wm, msg);

				/* don't execute the event callback */
				return;
			}
		case WM_RPT_BTN_EXP: {
				/* button - expansion */
				wiiuse_pressed_buttons(wm, msg);
				handle_expansion(wm, msg + 2);

				break;
			}
		case WM_RPT_BTN_ACC_EXP: {
				/* button - motion - expansion */
				wiiuse_pressed_buttons(wm, msg);

				handle_wm_accel(wm, msg);

				handle_expansion(wm, msg + 5);

				break;
			}
		case WM_RPT_BTN_ACC_IR: {
				/* button - motion - ir */
				wiiuse_pressed_buttons(wm, msg);

				handle_wm_accel(wm, msg);

				/* ir */
				calculate_extended_ir(wm, msg + 5);

				break;
			}
		case WM_RPT_BTN_IR_EXP: {
				/* button - ir - expansion */
				wiiuse_pressed_buttons(wm, msg);
				handle_expansion(wm, msg + 12);

				/* ir */
				calculate_basic_ir(wm, msg + 2);

				break;
			}
		case WM_RPT_BTN_ACC_IR_EXP: {
				/* button - motion - ir - expansion */
				wiiuse_pressed_buttons(wm, msg);

				handle_wm_accel(wm, msg);

				handle_expansion(wm, msg + 15);

				/* ir */
				calculate_basic_ir(wm, msg + 5);

				break;
			}
		case WM_RPT_WRITE: {
				event_data_write(wm, msg);
				break;
			}
		default: {
				WIIUSE_WARNING("Unknown event, can not handle it [Code 0x%x].", event);
				return;
			}
	}

	/* was there an event? */
	if (state_changed(wm)) {
		wm->event = WIIUSE_EVENT;
	}
}


/**
 *	@brief Find what buttons are pressed.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 *	@param msg		The message specified in the event packet.
 */
void wiiuse_pressed_buttons(struct wiimote_t* wm, byte* msg) {
	int16_t now;

	/* convert from big endian */
	now = from_big_endian_uint16_t(msg) & WIIMOTE_BUTTON_ALL;

	/* pressed now & were pressed, then held */
	wm->btns_held = (now & wm->btns);

	/* were pressed or were held & not pressed now, then released */
	wm->btns_released = ((wm->btns | wm->btns_held) & ~now);

	/* buttons pressed now */
	wm->btns = now;
}


/**
 *	@brief Received a data packet from a read request.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 *	@param msg		The message specified in the event packet.
 *
 *	Data from the wiimote comes in packets.  If the requested
 *	data segment size is bigger than one packet can hold then
 *	several packets will be received.  These packets are first
 *	reassembled into one, then the registered callback function
 *	that handles data reads is invoked.
 */
static void event_data_read(struct wiimote_t* wm, byte* msg) {
	/* we must always assume the packet received is from the most recent request */
	byte err;
	byte len;
	uint16_t offset;
	struct read_req_t* req = wm->read_req;

	wiiuse_pressed_buttons(wm, msg);

	/* find the next non-dirty request */
	while (req && req->dirty) {
		req = req->next;
	}

	/* if we don't have a request out then we didn't ask for this packet */
	if (!req) {
		WIIUSE_WARNING("Received data packet when no request was made.");
		return;
	}

	err = msg[2] & 0x0F;

	if (err == 0x08) {
		WIIUSE_WARNING("Unable to read data - address does not exist.");
	} else if (err == 0x07) {
		WIIUSE_WARNING("Unable to read data - address is for write-only registers.");
	} else if (err) {
		WIIUSE_WARNING("Unable to read data - unknown error code %x.", err);
	}

	if (err) {
		/* this request errored out, so skip it and go to the next one */

		/* delete this request */
		wm->read_req = req->next;
		free(req);

		/* if another request exists send it to the wiimote */
		if (wm->read_req) {
			wiiuse_send_next_pending_read_request(wm);
		}

		return;
	}

	len = ((msg[2] & 0xF0) >> 4) + 1;
	offset = from_big_endian_uint16_t(msg + 3);
	req->addr = (req->addr & 0xFFFF);

	req->wait -= len;
	if (req->wait >= req->size)
		/* this should never happen */
	{
		req->wait = 0;
	}

	WIIUSE_DEBUG("Received read packet:");
	WIIUSE_DEBUG("    Packet read offset:   %i bytes", offset);
	WIIUSE_DEBUG("    Request read offset:  %i bytes", req->addr);
	WIIUSE_DEBUG("    Read offset into buf: %i bytes", offset - req->addr);
	WIIUSE_DEBUG("    Read data size:       %i bytes", len);
	WIIUSE_DEBUG("    Still need:           %i bytes", req->wait);

	/* reconstruct this part of the data */
	memcpy((req->buf + offset - req->addr), (msg + 5), len);

#ifdef WITH_WIIUSE_DEBUG
	{
		int i = 0;
		printf("Read: ");
		for (; i < req->size - req->wait; ++i) {
			printf("%x ", req->buf[i]);
		}
		printf("\n");
	}
#endif

	/* if all data has been received, execute the read event callback or generate event */
	if (!req->wait) {
		if (req->cb) {
			/* this was a callback, so invoke it now */
			req->cb(wm, req->buf, req->size);

			/* delete this request */
			wm->read_req = req->next;
			free(req);
		} else {
			/*
			 *	This should generate an event.
			 *	We need to leave the event in the array so the client
			 *	can access it still.  We'll flag is as being 'dirty'
			 *	and give the client one cycle to use it.  Next event
			 *	we will remove it from the list.
			 */
			wm->event = WIIUSE_READ_DATA;
			req->dirty = 1;
		}

		/* if another request exists send it to the wiimote */
		if (wm->read_req) {
			wiiuse_send_next_pending_read_request(wm);
		}
	}
}


static void event_data_write(struct wiimote_t *wm, byte *msg) {

	struct data_req_t* req = wm->data_req;

	wiiuse_pressed_buttons(wm, msg);

	/* if we don't have a request out then we didn't ask for this packet */
	if (!req) {
		WIIUSE_WARNING("Transmitting data packet when no request was made.");
		return;
	}
	if (!(req->state == REQ_SENT)) {
		WIIUSE_WARNING("Transmission is not necessary");
		/* delete this request */
		wm->data_req = req->next;
		free(req);
		return;
	}


	req->state = REQ_DONE;

	if (req->cb) {
		/* this was a callback, so invoke it now */
		req->cb(wm, NULL, 0);
		/* delete this request */
		wm->data_req = req->next;
		free(req);
	} else {
		/*
		 *  This should generate an event.
		 *  We need to leave the event in the array so the client
		 *  can access it still.  We'll flag is as being 'REQ_DONE'
		 *  and give the client one cycle to use it.  Next event
		 *  we will remove it from the list.
		 */
		wm->event = WIIUSE_WRITE_DATA;

	}
	/* if another request exists send it to the wiimote */
	if (wm->data_req)   {
		wiiuse_send_next_pending_write_request(wm);
	}
}

/**
 *	@brief Read the controller status.
 *
 *	@param wm		Pointer to a wiimote_t structure.
 *	@param msg		The message specified in the event packet.
 *
 *	Read the controller status and execute the registered status callback.
 */
static void event_status(struct wiimote_t* wm, byte* msg) {
	int led[4] = {0, 0, 0, 0};
	int attachment = 0;
	int ir = 0;
	int exp_changed = 0;
	struct data_req_t* req = wm->data_req;

	/* initial handshake is not finished yet, ignore this */
	if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_HANDSHAKE)) {
		return;
	}

	/*
	 *	An event occurred.
	 *	This event can be overwritten by a more specific
	 *	event type during a handshake or expansion removal.
	 */
	wm->event = WIIUSE_STATUS;

	wiiuse_pressed_buttons(wm, msg);

	/* find what LEDs are lit */
	if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_1)	{
		led[0] = 1;
	}
	if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_2)	{
		led[1] = 1;
	}
	if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_3)	{
		led[2] = 1;
	}
	if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_4)	{
		led[3] = 1;
	}

	/* probe for Motion+ */
	if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_MPLUS_PRESENT)) {
		wiiuse_probe_motion_plus(wm);
	}

	/* is an attachment connected to the expansion port? */
	if ((msg[2] & WM_CTRL_STATUS_BYTE1_ATTACHMENT) == WM_CTRL_STATUS_BYTE1_ATTACHMENT) {
		WIIUSE_DEBUG("Attachment detected!");
		attachment = 1;
	}

	/* is the speaker enabled? */
	if ((msg[2] & WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED) == WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED) {
		WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_SPEAKER);
	}

	/* is IR sensing enabled? */
	if ((msg[2] & WM_CTRL_STATUS_BYTE1_IR_ENABLED) == WM_CTRL_STATUS_BYTE1_IR_ENABLED) {
		ir = 1;
	}

	/* find the battery level and normalize between 0 and 1 */
	wm->battery_level = (msg[5] / (float)WM_MAX_BATTERY_CODE);

	/* expansion port */
	if (attachment && !WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP) && !WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP_HANDSHAKE)) {
		/* send the initialization code for the attachment */
		handshake_expansion(wm, NULL, 0);
		exp_changed = 1;
	} else if (!attachment && WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
		/* attachment removed */
		disable_expansion(wm);
		exp_changed = 1;
	}

#ifdef WIIUSE_WIN32
	if (!attachment) {
		WIIUSE_DEBUG("Setting timeout to normal %i ms.", wm->normal_timeout);
		wm->timeout = wm->normal_timeout;
	}
#endif

	/*
	 *	From now on the remote will only send status packets.
	 *	We need to send a WIIMOTE_CMD_REPORT_TYPE packet to
	 *	reenable other incoming reports.
	 */
	if (exp_changed && WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR)) {
		/*
		 *  Since the expansion status changed IR needs to
		 *  be reset for the new IR report mode.
		 */
		WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_IR);
		wiiuse_set_ir(wm, 1);
	} else {
		wiiuse_set_report_type(wm);
		return;
	}

	/* handling new Tx for changed exp */
	if (!req) {
		return;
	}
	if (!(req->state == REQ_SENT)) {
		return;
	}
	wm->data_req = req->next;
	req->state = REQ_DONE;
	/* if(req->cb!=NULL) req->cb(wm,msg,6); */
	free(req);
}


/**
 *	@brief Handle data from the expansion.
 *
 *	@param wm		A pointer to a wiimote_t structure.
 *	@param msg		The message specified in the event packet for the expansion.
 */
static void handle_expansion(struct wiimote_t* wm, byte* msg) {
	switch (wm->exp.type) {
		case EXP_NUNCHUK:
			nunchuk_event(&wm->exp.nunchuk, msg);
			break;
		case EXP_CLASSIC:
			classic_ctrl_event(&wm->exp.classic, msg);
			break;
		case EXP_GUITAR_HERO_3:
			guitar_hero_3_event(&wm->exp.gh3, msg);
			break;
		case EXP_WII_BOARD:
			wii_board_event(&wm->exp.wb, msg);
			break;
		case EXP_MOTION_PLUS:
		case EXP_MOTION_PLUS_CLASSIC:
		case EXP_MOTION_PLUS_NUNCHUK:
			motion_plus_event(&wm->exp.mp, wm->exp.type, msg);
			break;
		default:
			break;
	}
}


/**
 *	@brief Handle the handshake data from the expansion device.
 *
 *	@param wm		A pointer to a wiimote_t structure.
 *	@param data		The data read in from the device.
 *	@param len		The length of the data block, in bytes.
 *
 *	Tries to determine what kind of expansion was attached
 *	and invoke the correct handshake function.
 *
 *	If the data is NULL then this function will try to start
 *	a handshake with the expansion.
 */
void handshake_expansion(struct wiimote_t* wm, byte* data, uint16_t len) {
	int id;
	byte val = 0;
	byte buf = 0x00;
	byte* handshake_buf;
	int gotIt = 0;
	WIIUSE_DEBUG("handshake_expansion with state %d", wm->expansion_state);

	switch (wm->expansion_state) {
			/* These two initialization writes disable the encryption */
		case 0:
			wm->expansion_state = 1;
			/* increase the timeout until the handshake completes */
#ifdef WIIUSE_WIN32
			WIIUSE_DEBUG("write 0x55 - Setting timeout to expansion %i ms.", wm->exp_timeout);
			wm->timeout = wm->exp_timeout;
#endif
			buf = 0x55;
			wiiuse_write_data_cb(wm, WM_EXP_MEM_ENABLE1, &buf, 1, handshake_expansion);
			break;
		case 1:
			wm->expansion_state = 2;
			/* increase the timeout until the handshake completes */
#ifdef WIIUSE_WIN32
			WIIUSE_DEBUG("write 0x00 - Setting timeout to expansion %i ms.", wm->exp_timeout);
			wm->timeout = wm->exp_timeout;
#endif
			val = 0x00;
			wiiuse_write_data_cb(wm, WM_EXP_MEM_ENABLE2, &buf, 1, handshake_expansion);
			break;
		case 2:
			wm->expansion_state = 3;
			/* get the calibration data */
			if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
				disable_expansion(wm);
			}
			handshake_buf = malloc(EXP_HANDSHAKE_LEN * sizeof(byte));
			/* tell the wiimote to send expansion data */
			WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_EXP);
			wiiuse_read_data_cb(wm, handshake_expansion, handshake_buf, WM_EXP_MEM_CALIBR, EXP_HANDSHAKE_LEN);
			break;
		case 3:
			if (!data || !len) {
				WIIUSE_DEBUG("no handshake data received from expansion");
				disable_expansion(wm);
				return;
			}
			wm->expansion_state = 0;
			id = from_big_endian_uint32_t(data + 220);
			switch (id) {
				case EXP_ID_CODE_NUNCHUK:
					if (nunchuk_handshake(wm, &wm->exp.nunchuk, data, len)) {
						wm->event = WIIUSE_NUNCHUK_INSERTED;
						gotIt = 1;
					}
					break;

				case EXP_ID_CODE_CLASSIC_CONTROLLER:
					if (classic_ctrl_handshake(wm, &wm->exp.classic, data, len)) {
						wm->event = WIIUSE_CLASSIC_CTRL_INSERTED;
						gotIt = 1;
					}
					break;

				case EXP_ID_CODE_GUITAR:
					if (guitar_hero_3_handshake(wm, &wm->exp.gh3, data, len)) {
						wm->event = WIIUSE_GUITAR_HERO_3_CTRL_INSERTED;
						gotIt = 1;
					}
					break;

				case EXP_ID_CODE_MOTION_PLUS:
				case EXP_ID_CODE_MOTION_PLUS_CLASSIC:
				case EXP_ID_CODE_MOTION_PLUS_NUNCHUK:
					/* wiiuse_motion_plus_handshake(wm, data, len); */
					wm->event = WIIUSE_MOTION_PLUS_ACTIVATED;
					gotIt = 1;
					break;

				case EXP_ID_CODE_WII_BOARD:
					if (wii_board_handshake(wm, &wm->exp.wb, data, len)) {
						wm->event = WIIUSE_WII_BOARD_CTRL_INSERTED;
						gotIt = 1;
					}
					break;

				default:
					WIIUSE_WARNING("Unknown expansion type. Code: 0x%x", id);
					break;
			}
			free(data);
			if (gotIt) {
				WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_EXP_HANDSHAKE);
				WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_EXP);
			} else {
				WIIUSE_WARNING("Could not handshake with expansion id: 0x%x", id);
			}
			wiiuse_set_ir_mode(wm);
			wiiuse_status(wm);
			break;
	}
}



/**
 *	@brief Disable the expansion device if it was enabled.
 *
 *	@param wm		A pointer to a wiimote_t structure.
 *	@param data		The data read in from the device.
 *	@param len		The length of the data block, in bytes.
 *
 *	If the data is NULL then this function will try to start
 *	a handshake with the expansion.
 */
void disable_expansion(struct wiimote_t* wm) {
	WIIUSE_DEBUG("Disabling expansion");
	if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
		return;
	}

	/* tell the associated module the expansion was removed */
	switch (wm->exp.type) {
		case EXP_NUNCHUK:
			nunchuk_disconnected(&wm->exp.nunchuk);
			wm->event = WIIUSE_NUNCHUK_REMOVED;
			break;
		case EXP_CLASSIC:
			classic_ctrl_disconnected(&wm->exp.classic);
			wm->event = WIIUSE_CLASSIC_CTRL_REMOVED;
			break;
		case EXP_GUITAR_HERO_3:
			guitar_hero_3_disconnected(&wm->exp.gh3);
			wm->event = WIIUSE_GUITAR_HERO_3_CTRL_REMOVED;
			break;
		case EXP_WII_BOARD:
			wii_board_disconnected(&wm->exp.wb);
			wm->event = WIIUSE_WII_BOARD_CTRL_REMOVED;
			break;
		case EXP_MOTION_PLUS:
		case EXP_MOTION_PLUS_CLASSIC:
		case EXP_MOTION_PLUS_NUNCHUK:
			motion_plus_disconnected(&wm->exp.mp);
			wm->event = WIIUSE_MOTION_PLUS_REMOVED;
			break;
		default:
			break;
	}

	WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_EXP);
	wm->exp.type = EXP_NONE;
	wm->expansion_state = 0;
}


/**
 *	@brief Save important state data.
 *	@param wm	A pointer to a wiimote_t structure.
 */
static void save_state(struct wiimote_t* wm) {
	/* wiimote */
	wm->lstate.btns = wm->btns;
	wm->lstate.accel = wm->accel;

	/* ir */
	if (WIIUSE_USING_IR(wm)) {
		wm->lstate.ir_ax = wm->ir.ax;
		wm->lstate.ir_ay = wm->ir.ay;
		wm->lstate.ir_distance = wm->ir.distance;
	}

	/* expansion */
	switch (wm->exp.type) {
		case EXP_NUNCHUK:
			wm->lstate.exp_ljs_ang = wm->exp.nunchuk.js.ang;
			wm->lstate.exp_ljs_mag = wm->exp.nunchuk.js.mag;
			wm->lstate.exp_btns = wm->exp.nunchuk.btns;
			wm->lstate.exp_accel = wm->exp.nunchuk.accel;
			break;

		case EXP_CLASSIC:
			wm->lstate.exp_ljs_ang = wm->exp.classic.ljs.ang;
			wm->lstate.exp_ljs_mag = wm->exp.classic.ljs.mag;
			wm->lstate.exp_rjs_ang = wm->exp.classic.rjs.ang;
			wm->lstate.exp_rjs_mag = wm->exp.classic.rjs.mag;
			wm->lstate.exp_r_shoulder = wm->exp.classic.r_shoulder;
			wm->lstate.exp_l_shoulder = wm->exp.classic.l_shoulder;
			wm->lstate.exp_btns = wm->exp.classic.btns;
			break;

		case EXP_GUITAR_HERO_3:
			wm->lstate.exp_ljs_ang = wm->exp.gh3.js.ang;
			wm->lstate.exp_ljs_mag = wm->exp.gh3.js.mag;
			wm->lstate.exp_r_shoulder = wm->exp.gh3.whammy_bar;
			wm->lstate.exp_btns = wm->exp.gh3.btns;
			break;

		case EXP_WII_BOARD:
			wm->lstate.exp_wb_rtr = wm->exp.wb.rtr;
			wm->lstate.exp_wb_rtl = wm->exp.wb.rtl;
			wm->lstate.exp_wb_rbr = wm->exp.wb.rbr;
			wm->lstate.exp_wb_rbl = wm->exp.wb.rbl;
			break;

		case EXP_MOTION_PLUS:
		case EXP_MOTION_PLUS_CLASSIC:
		case EXP_MOTION_PLUS_NUNCHUK: {
				wm->lstate.drx = wm->exp.mp.raw_gyro.pitch;
				wm->lstate.dry = wm->exp.mp.raw_gyro.roll;
				wm->lstate.drz = wm->exp.mp.raw_gyro.yaw;

				if (wm->exp.type == EXP_MOTION_PLUS_CLASSIC) {
					wm->lstate.exp_ljs_ang = wm->exp.classic.ljs.ang;
					wm->lstate.exp_ljs_mag = wm->exp.classic.ljs.mag;
					wm->lstate.exp_rjs_ang = wm->exp.classic.rjs.ang;
					wm->lstate.exp_rjs_mag = wm->exp.classic.rjs.mag;
					wm->lstate.exp_r_shoulder = wm->exp.classic.r_shoulder;
					wm->lstate.exp_l_shoulder = wm->exp.classic.l_shoulder;
					wm->lstate.exp_btns = wm->exp.classic.btns;
				} else {
					wm->lstate.exp_ljs_ang = wm->exp.nunchuk.js.ang;
					wm->lstate.exp_ljs_mag = wm->exp.nunchuk.js.mag;
					wm->lstate.exp_btns = wm->exp.nunchuk.btns;
					wm->lstate.exp_accel = wm->exp.nunchuk.accel;
				}

				break;
			}

		case EXP_NONE:
			break;
	}
}


/**
 *	@brief Determine if the current state differs significantly from the previous.
 *	@param wm	A pointer to a wiimote_t structure.
 *	@return	1 if a significant change occurred, 0 if not.
 */
static int state_changed(struct wiimote_t* wm) {
#define STATE_CHANGED(a, b)		if (a != b)				return 1

#define CROSS_THRESH(last, now, thresh)										\
				do {															\
					if (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_ORIENT_THRESH)) {		\
						if ((diff_f(last.roll, now.roll) >= thresh) ||			\
							(diff_f(last.pitch, now.pitch) >= thresh) ||		\
							(diff_f(last.yaw, now.yaw) >= thresh))				\
						{														\
							last = now;											\
							return 1;											\
						}														\
					} else {													\
						if (last.roll != now.roll)		return 1;				\
						if (last.pitch != now.pitch)	return 1;				\
						if (last.yaw != now.yaw)		return 1;				\
					}															\
				} while (0)

#define CROSS_THRESH_XYZ(last, now, thresh)									\
				do {															\
					if (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_ORIENT_THRESH)) {		\
						if ((diff_f(last.x, now.x) >= thresh) ||				\
							(diff_f(last.y, now.y) >= thresh) ||				\
							(diff_f(last.z, now.z) >= thresh))					\
						{														\
							last = now;											\
							return 1;											\
						}														\
					} else {													\
						if (last.x != now.x)		return 1;					\
						if (last.y != now.y)		return 1;					\
						if (last.z != now.z)		return 1;					\
					}															\
				} while (0)

	/* ir */
	if (WIIUSE_USING_IR(wm)) {
		STATE_CHANGED(wm->lstate.ir_ax, wm->ir.ax);
		STATE_CHANGED(wm->lstate.ir_ay, wm->ir.ay);
		STATE_CHANGED(wm->lstate.ir_distance, wm->ir.distance);
	}

	/* accelerometer */
	if (WIIUSE_USING_ACC(wm)) {
		/* raw accelerometer */
		CROSS_THRESH_XYZ(wm->lstate.accel, wm->accel, wm->accel_threshold);

		/* orientation */
		CROSS_THRESH(wm->lstate.orient, wm->orient, wm->orient_threshold);
	}

	/* expansion */
	switch (wm->exp.type) {
		case EXP_NUNCHUK: {
				STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.nunchuk.js.ang);
				STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.nunchuk.js.mag);
				STATE_CHANGED(wm->lstate.exp_btns, wm->exp.nunchuk.btns);

				CROSS_THRESH(wm->lstate.exp_orient, wm->exp.nunchuk.orient, wm->exp.nunchuk.orient_threshold);
				CROSS_THRESH_XYZ(wm->lstate.exp_accel, wm->exp.nunchuk.accel, wm->exp.nunchuk.accel_threshold);
				break;
			}
		case EXP_CLASSIC: {
				STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.classic.ljs.ang);
				STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.classic.ljs.mag);
				STATE_CHANGED(wm->lstate.exp_rjs_ang, wm->exp.classic.rjs.ang);
				STATE_CHANGED(wm->lstate.exp_rjs_mag, wm->exp.classic.rjs.mag);
				STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.classic.r_shoulder);
				STATE_CHANGED(wm->lstate.exp_l_shoulder, wm->exp.classic.l_shoulder);
				STATE_CHANGED(wm->lstate.exp_btns, wm->exp.classic.btns);
				break;
			}
		case EXP_GUITAR_HERO_3: {
				STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.gh3.js.ang);
				STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.gh3.js.mag);
				STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.gh3.whammy_bar);
				STATE_CHANGED(wm->lstate.exp_btns, wm->exp.gh3.btns);
				break;
			}
		case EXP_WII_BOARD: {
				STATE_CHANGED(wm->lstate.exp_wb_rtr, wm->exp.wb.tr);
				STATE_CHANGED(wm->lstate.exp_wb_rtl, wm->exp.wb.tl);
				STATE_CHANGED(wm->lstate.exp_wb_rbr, wm->exp.wb.br);
				STATE_CHANGED(wm->lstate.exp_wb_rbl, wm->exp.wb.bl);
				break;
			}

		case EXP_MOTION_PLUS:
		case EXP_MOTION_PLUS_CLASSIC:
		case EXP_MOTION_PLUS_NUNCHUK: {
				STATE_CHANGED(wm->lstate.drx, wm->exp.mp.raw_gyro.pitch);
				STATE_CHANGED(wm->lstate.dry, wm->exp.mp.raw_gyro.roll);
				STATE_CHANGED(wm->lstate.drz, wm->exp.mp.raw_gyro.yaw);

				if (wm->exp.type == EXP_MOTION_PLUS_CLASSIC) {
					STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.classic.ljs.ang);
					STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.classic.ljs.mag);
					STATE_CHANGED(wm->lstate.exp_rjs_ang, wm->exp.classic.rjs.ang);
					STATE_CHANGED(wm->lstate.exp_rjs_mag, wm->exp.classic.rjs.mag);
					STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.classic.r_shoulder);
					STATE_CHANGED(wm->lstate.exp_l_shoulder, wm->exp.classic.l_shoulder);
					STATE_CHANGED(wm->lstate.exp_btns, wm->exp.classic.btns);
				} else {
					STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.nunchuk.js.ang);
					STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.nunchuk.js.mag);
					STATE_CHANGED(wm->lstate.exp_btns, wm->exp.nunchuk.btns);

					CROSS_THRESH(wm->lstate.exp_orient, wm->exp.nunchuk.orient, wm->exp.nunchuk.orient_threshold);
					CROSS_THRESH_XYZ(wm->lstate.exp_accel, wm->exp.nunchuk.accel, wm->exp.nunchuk.accel_threshold);
				}

				break;
			}
		case EXP_NONE: {
				break;
			}
	}

	STATE_CHANGED(wm->lstate.btns, wm->btns);

	return 0;
}
