package com.gpl.rpg.AndorsTrail.controller;

import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;

import com.gpl.rpg.AndorsTrail.activity.HeroinfoActivity;
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.L;

public final class InputController implements OnClickListener, OnLongClickListener{
	private final ControllerContext controllers;
	private final WorldContext world;

	private final Coord lastTouchPosition_tileCoords = new Coord();
	private int lastTouchPosition_dx = 0;
	private int lastTouchPosition_dy = 0;
	private long lastTouchEventTime = 0;
	private boolean isDpadActive = false;

	private int keyState_dx = 0;
	private int keyState_dy = 0;
	private boolean keyState_attack = false;
	private boolean keyState_flee = false;
	private boolean keyState_endturn = false;
	
	final private int KEY_UNHANDLED = 0; // Default, for unhandled keycodes
	final private int KEY_MOVE_UP = 1;
	final private int KEY_MOVE_DOWN = 2;
	final private int KEY_MOVE_LEFT = 3;
	final private int KEY_MOVE_RIGHT = 4;
	final private int KEY_MOVE_UP_LEFT = 5;
	final private int KEY_MOVE_UP_RIGHT = 6;
	final private int KEY_MOVE_DOWN_LEFT = 7;
	final private int KEY_MOVE_DOWN_RIGHT = 8;
	final private int KEY_ATTACK = 9;
	final private int KEY_FLEE = 10;
	final private int KEY_END_TURN = 11;
	final private int KEY_HERO_INFO = 12;
	final private int KEY_TOOLBOX = 13;
	
	private SparseIntArray keyMap = new SparseIntArray();  // Android keycode to internal key event mapping.  TODO: Configure via preferences

	public InputController(ControllerContext controllers, WorldContext world) {
		this.controllers = controllers;
		this.world = world;
		initializeKeyMap();
		
	}
	
/*	New keyboard handler.  Both Key Down and Key Up events handled here to allow conditional behaviours.
	On 4-way dpad controllers, cursor keys, and WASD, diagonals are generated by chording two keys.
	Single-key diagonals are supported on numeric keypad and 8-way dpads (not seen/tested in the wild).

    Because two-key combos initially generate a ordinal movement (one key comes in first), which can
    be dangerous in tight spaces, modifiers are provided to "lock" the input until both keys are down.
    TODO: Use delay timer to enable chorded diagonals on first move without locking kludge?
 */

    // Map key codes to spectic internal actions  
	// TODO: Move mapping out of code to JSON/XML file, or maybe player prefs
	private void initializeKeyMap() {
		int key;

		// Keys mapping to UP
		key = KEY_MOVE_UP;
		keyMap.put(KeyEvent.KEYCODE_DPAD_UP, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_8, key);
		keyMap.put(KeyEvent.KEYCODE_8, key);
		keyMap.put(KeyEvent.KEYCODE_W, key);

		// Keys mapping to DOWN
		key = KEY_MOVE_DOWN;
		keyMap.put(KeyEvent.KEYCODE_DPAD_DOWN, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_2, key);
		keyMap.put(KeyEvent.KEYCODE_2, key);
		keyMap.put(KeyEvent.KEYCODE_S, key);

		// Keys mapping to LEFT
		key = KEY_MOVE_LEFT;
		keyMap.put(KeyEvent.KEYCODE_DPAD_LEFT, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_4, key);
		keyMap.put(KeyEvent.KEYCODE_4, key);
		keyMap.put(KeyEvent.KEYCODE_A, key);

		// Keys mapping to RIGHT
		key = KEY_MOVE_RIGHT;
		keyMap.put(KeyEvent.KEYCODE_DPAD_RIGHT, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_6, key);
		keyMap.put(KeyEvent.KEYCODE_6, key);
		keyMap.put(KeyEvent.KEYCODE_D, key);

		// Keys mapping to UP_LEFT
		key = KEY_MOVE_UP_LEFT;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
			keyMap.put(KeyEvent.KEYCODE_DPAD_UP_LEFT, key);
		}
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_7, key);
		keyMap.put(KeyEvent.KEYCODE_7, key);
		keyMap.put(KeyEvent.KEYCODE_MOVE_HOME, key);

		// Keys mapping to UP_RIGHT		
		key = KEY_MOVE_UP_RIGHT;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
			keyMap.put(KeyEvent.KEYCODE_DPAD_UP_RIGHT, key);
		}
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_9, key);
		keyMap.put(KeyEvent.KEYCODE_9, key);
		keyMap.put(KeyEvent.KEYCODE_PAGE_UP, key);

		// Keys mapping to DOWN_LEFT
		key = KEY_MOVE_DOWN_LEFT;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
			keyMap.put(KeyEvent.KEYCODE_DPAD_DOWN_LEFT, key);
		}
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_1, key);
		keyMap.put(KeyEvent.KEYCODE_1, key);
		keyMap.put(KeyEvent.KEYCODE_MOVE_END, key);

		// Keys mapping to DOWN_RIGHT		
		key = KEY_MOVE_DOWN_RIGHT;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
			keyMap.put(KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, key);
		}
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_3, key);
		keyMap.put(KeyEvent.KEYCODE_3, key);
		keyMap.put(KeyEvent.KEYCODE_PAGE_DOWN, key);

		// Keys mapping to ATTACK
		key = KEY_ATTACK;
		keyMap.put(KeyEvent.KEYCODE_DPAD_CENTER, key);
		keyMap.put(KeyEvent.KEYCODE_BUTTON_A, key);
		keyMap.put(KeyEvent.KEYCODE_SPACE, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_5, key);

		// Keys mapping to FLEE		
		key = KEY_FLEE;
		keyMap.put(KeyEvent.KEYCODE_BUTTON_X, key);
		keyMap.put(KeyEvent.KEYCODE_F, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_ENTER, key);
		keyMap.put(KeyEvent.KEYCODE_ENTER, key);

		// Keys mapping to END_TURN
		key = KEY_END_TURN;
		keyMap.put(KeyEvent.KEYCODE_BUTTON_Y, key);
		keyMap.put(KeyEvent.KEYCODE_E, key);
		keyMap.put(KeyEvent.KEYCODE_FORWARD_DEL, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_DOT, key);

		// Keys mapping to HERO_INFO 		
		key = KEY_HERO_INFO;
		//keyMap.put(KeyEvent.KEYCODE_BUTTON_SELECT, key);
		keyMap.put(KeyEvent.KEYCODE_BUTTON_L1, key);
		keyMap.put(KeyEvent.KEYCODE_NUM_LOCK, key);
		keyMap.put(KeyEvent.KEYCODE_C, key);

		// Keys mapping to TOOLBOX 		
		key = KEY_TOOLBOX;
		keyMap.put(KeyEvent.KEYCODE_BUTTON_R1, key);
		keyMap.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE, key);
		keyMap.put(KeyEvent.KEYCODE_B, key);
	}

	// Generate game actions based on mapped keys
	public boolean onKeyboardAction(Context context, KeyEvent event, boolean acceptInput) {
		//L.log("onKeyboardAction(): Processing action " + event.getAction() + " for keyCode " + event.getKeyCode());

		if (event.getAction() != KeyEvent.ACTION_DOWN && event.getAction() != KeyEvent.ACTION_UP) return false; // don't handle other actions
		boolean keydown = (event.getAction() == KeyEvent.ACTION_DOWN);
		boolean inihbit =  (keyState_attack || keyState_flee); // used to inhibit movement if an action key is held down

		switch (keyMap.get(event.getKeyCode())) {

			// Ordinal directional keys - only modify one direction register; registers are combined when
			// keys used simultaneously to create synthetic diagonals
			case KEY_MOVE_UP:
				keyState_dy = keydown ? -1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;
			case KEY_MOVE_DOWN:
				keyState_dy = keydown ? 1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;
			case KEY_MOVE_LEFT:
				keyState_dx = keydown ? -1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;
			case KEY_MOVE_RIGHT:
				keyState_dx = keydown ? 1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;

			// Diagonal directional keys.  Modify both direction registers, can't be combined
			// TODO: store individual key position to allow combinations.  May not be worth the trouble.
			case KEY_MOVE_UP_LEFT:
				keyState_dx = keydown ? -1 : 0;
				keyState_dy = keydown ? -1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;
			case KEY_MOVE_UP_RIGHT:
				keyState_dx = keydown ? 1 : 0;
				keyState_dy = keydown ? -1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;
			case KEY_MOVE_DOWN_LEFT:
				keyState_dx = keydown ? -1 : 0;
				keyState_dy = keydown ? 1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;
			case KEY_MOVE_DOWN_RIGHT:
				keyState_dx = keydown ? 1 : 0;
				keyState_dy = keydown ? 1 : 0;
				if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy);
				break;

			// Special key handling below - some combat/movement stuff done here because it's too 
			// specific for logic in onRelativeMovement
			
			// "Attack" shortcut - freeze movement to allow chorded direction when key is released.
			// if in combat, executes an attack on key release
			case KEY_ATTACK:
				if (keydown && !keyState_attack) { // key pressed - pause any movement
					if(!world.model.uiSelections.isInCombat) controllers.movementController.stopMovement();
				} else if (!keydown && keyState_attack) { // key released - execute attack / move in direction
					if (acceptInput) onRelativeMovement(keyState_dx, keyState_dy);
				}
				keyState_attack = keydown;
				break;

			// "Flee" shortcut.  Intitiates flee when pressed.  If a direction is held, moves in chosen direction when released
			case KEY_FLEE:
				if (world.model.uiSelections.isInCombat) {
					if (keydown && !keyState_flee) { // button pressed - set flee; movement locked while pressed
						if(acceptInput) controllers.combatController.startFlee();
					} else if (!keydown && keyState_flee) { // button released - move flee direction, if held
						// We need to do a special call because the movement key may already be down, and if the device
						// doesn't generate repeat keystrokes, this handler won't get another event
						if ((keyState_dx != 0 || keyState_dy != 0) && allowInputInterval()) {
							if(acceptInput) controllers.combatController.executeMoveAttack(keyState_dx, keyState_dy);
						}
					}
				}
				keyState_flee = keydown;
				break;

			// "End Turn" shortcut.  Flag prevents repeated end turn if key is held down.
			case KEY_END_TURN:
				if (acceptInput && keydown && !keyState_endturn) {
					if (world.model.uiSelections.isInCombat) controllers.combatController.endPlayerTurn();
				}
				keyState_endturn = keydown;
				break;
			
			// "Hero Info" screen shortcut.  New activity takes focus, so we don't need to worry about repeats.
			case KEY_HERO_INFO:
				if (acceptInput && keydown) context.startActivity(new Intent(context, HeroinfoActivity.class));
				break;
			
			case KEY_TOOLBOX:
//			???	ToolboxView toolboxview =  context.getApplicationContext(). findViewById(R.id.main_toolboxview);
				
				break;
			
			case KEY_UNHANDLED: // Unhandled keycode
				return false;
			
			default: // unhandled keymap code entry (should not happen)
				L.log("onKeyboardAction(): Unhandled keyMap code constant " + keyMap.get(event.getKeyCode()) + " for keyCode " + event.getKeyCode());
				return false;
		}

		return true;
	}

	public void onRelativeMovement(int dx, int dy) {
		//L.log("onRelativeMovement(): dx=" + dx + " dy=" + dy + " combat: " + world.model.uiSelections.isInCombat);
		if (world.model.uiSelections.isInCombat) {
			if (allowInputInterval()) controllers.combatController.executeMoveAttack(dx, dy);
		} else if (dx == 0 && dy == 0) {
			controllers.movementController.stopMovement();
		} else {
			controllers.movementController.startMovement(dx, dy, null);
		}
	}

	public void onKeyboardCancel() {
		controllers.movementController.stopMovement();
	}

	@Override
	public void onClick(View arg0) {
		if (!world.model.uiSelections.isInCombat) return;
		onRelativeMovement(lastTouchPosition_dx, lastTouchPosition_dy);
	}

	@Override
	public boolean onLongClick(View arg0) {
		if (world.model.uiSelections.isInCombat) {
			//TODO: Should be able to mark positions far away (mapwalk / ranged combat)
			if (lastTouchPosition_dx == 0 && lastTouchPosition_dy == 0) return false;
			if (Math.abs(lastTouchPosition_dx) > 1) return false;
			if (Math.abs(lastTouchPosition_dy) > 1) return false;

			controllers.combatController.setCombatSelection(lastTouchPosition_tileCoords);
			return true;
		}
		return false;
	}

	private boolean allowInputInterval() {
		final long now = System.currentTimeMillis();
		if ((now - lastTouchEventTime) < Constants.MINIMUM_INPUT_INTERVAL) return false;
		lastTouchEventTime = now;
		return true;
	}

	public void setDpadActive(boolean isDpadActive) {
		this.isDpadActive = isDpadActive;
	}

	public void onTouchCancel() {
		controllers.movementController.stopMovement();
	}

	public boolean onTouchedTile(int tile_x, int tile_y) {
		lastTouchPosition_tileCoords.set(tile_x, tile_y);
		lastTouchPosition_dx = tile_x - world.model.player.position.x;
		lastTouchPosition_dy = tile_y - world.model.player.position.y;

		if (world.model.uiSelections.isInCombat || isDpadActive) return false;

		controllers.movementController.startMovement(lastTouchPosition_dx, lastTouchPosition_dy, lastTouchPosition_tileCoords);
		return true;
	}
}
