package com.limelight.input;

import com.limelight.input.GamepadMapping.Mapping;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket;

import net.java.games.input.Component;
import net.java.games.input.Controller;
import net.java.games.input.Event;
import net.java.games.input.EventQueue;

public class Gamepad {
	private Controller pad;
	private GamepadMapping config;

	private short inputMap = 0x0000;
	private byte leftTrigger = 0x00;
	private byte rightTrigger = 0x00;
	private short rightStickX = 0x0000;
	private short rightStickY = 0x0000;
	private short leftStickX = 0x0000;
	private short leftStickY = 0x0000;

	public Gamepad(Controller pad, GamepadMapping settings) {
		this.config = settings;
		this.pad = pad;

		for (Component comp : pad.getComponents()) {
			initValue(comp);
		}
	}

	public GamepadMapping getConfiguration() {
		return config;
	}

	private void initValue(Component comp) {
		handleComponent(comp, comp.getPollData());
	}

	public boolean poll() {
		return pad.poll();
	}

	private void sendControllerPacket(NvConnection conn) {
		if (conn != null) {
			conn.sendControllerInput(inputMap, leftTrigger, rightTrigger, 
					leftStickX, leftStickY, rightStickX, rightStickY);
		}
	}

	public EventQueue getEvents() {
		return pad.getEventQueue();
	}

	public void handleEvents(NvConnection conn) {
		EventQueue queue = pad.getEventQueue();
		Event event = new Event();

		while(queue.getNextEvent(event)) {

			/* uncommented for debugging */
			//printInfo(pad, event);

			handleEvent(event);

			sendControllerPacket(conn);
		}

	}

	/*
	 * used for debugging, normally unused.
	 */
	@SuppressWarnings("unused")
	private void printInfo(Controller gamepad, Event event) {
		Component comp = event.getComponent();

		StringBuilder builder = new StringBuilder(gamepad.getName());

		builder.append(" at ");
		builder.append(event.getNanos()).append(": ");
		builder.append(comp.getName()).append(" changed to ");


		float value = event.getValue(); 
		if(comp.isAnalog()) {
			builder.append(value);
		} else {
			if(value==1.0f) {
				builder.append("On");
			} else {
				builder.append("Off");
			}
		}

		System.out.println(builder.toString());
	}

	private void handleEvent(Event event) {
		Component comp = event.getComponent();
		float value = event.getValue();

		handleComponent(comp, value);
	}

	private void handleComponent(Component comp, float value) {
		Mapping mapping = config.getMapping(comp);
		if (mapping != null) {
			if (mapping.contComp.isAnalog()) {
				handleAnalog(mapping.contComp, sanitizeValue(mapping, value));
			} else {
				handleButtons(mapping.contComp, sanitizeValue(mapping, value));
			}
		}
	}

	private float sanitizeValue(Mapping mapping, float value) {
		float sanitized = value;
		if (mapping.invert) {
			sanitized = -sanitized;
		}
		if (mapping.trigger) {
			sanitized = (sanitized + 1)/2;
		}
		return sanitized;
	}
	
	private void toggle(short button, boolean press) {
		if (press) {
			inputMap |= button;
		} else {
			inputMap &= ~button;
		}
	}

	private void handleAnalog(ControllerComponent contComp, float value) {
		switch (contComp) {
		case LS_X:
			leftStickX = (short)Math.round(value * 0x7FFF);
			break;
		case LS_Y:
			leftStickY = (short)Math.round(value * 0x7FFF);
			break;
		case RS_X:
			rightStickX = (short)Math.round(value * 0x7FFF);
			break;
		case RS_Y:
			rightStickY = (short)Math.round(value * 0x7FFF);
			break;
		case LT:
			leftTrigger = (byte)Math.round(value * 0xFF);
			break;
		case RT:
			rightTrigger = (byte)Math.round(value * 0xFF);
			break;
		default:
			System.out.println("A mapping error has occured. Ignoring: " + contComp.name());
			break;
		}
	}

	private void handleButtons(ControllerComponent contComp, float value) {
		boolean press = false;

		if (value > 0.5F) {
			press = true;
		}

		switch (contComp) {
		case BTN_A:
			toggle(ControllerPacket.A_FLAG, press);
			break;
		case BTN_X:
			toggle(ControllerPacket.X_FLAG, press);
			break;
		case BTN_Y:
			toggle(ControllerPacket.Y_FLAG, press);
			break;
		case BTN_B:
			toggle(ControllerPacket.B_FLAG, press);
			break;
		case DPAD_UP:
			toggle(ControllerPacket.UP_FLAG, press);
			break;
		case DPAD_DOWN:
			toggle(ControllerPacket.DOWN_FLAG, press);
			break;
		case DPAD_LEFT:
			toggle(ControllerPacket.LEFT_FLAG, press);
			break;
		case DPAD_RIGHT:
			toggle(ControllerPacket.RIGHT_FLAG, press);
			break;
		case LS_THUMB:
			toggle(ControllerPacket.LS_CLK_FLAG, press);
			break;
		case RS_THUMB:
			toggle(ControllerPacket.RS_CLK_FLAG, press);
			break;
		case LB:
			toggle(ControllerPacket.LB_FLAG, press);
			break;
		case RB:
			toggle(ControllerPacket.RB_FLAG, press);
			break;
		case BTN_START:
			toggle(ControllerPacket.PLAY_FLAG, press);
			break;
		case BTN_BACK:
			toggle(ControllerPacket.BACK_FLAG, press);
			break;
		case BTN_SPECIAL:
			toggle(ControllerPacket.SPECIAL_BUTTON_FLAG, press);
			break;
		default:
			System.out.println("A mapping error has occured. Ignoring: " + contComp.name());
			break;
		}
	}
}
