/*
Package gamepad provides game controller logic for the game.

# Controls

The gamepad controls are currently hard-coded for Xbox 360 style controllers, and the
controller mappings vary depending on the "mode" of control.

# N Style and X Style

If the gamepad control is set to "NStyle" then the A/B and X/Y buttons will be swapped
to match the labels of a Nintendo style controller. Since the game has relatively few
inputs needed now, this module maps them to a PrimaryButton and a SecondaryButton,
defined as:

PrimaryButton: A or X button
SecondaryButton: B or Y button

# Mouse Mode

- Left stick moves the mouse cursor (a cursor sprite is drawn on screen)
- Right stick scrolls the level (title screen or level editor)
- PrimaryButton emulates a left click.
- SecondaryButton emulates a right click.
- Left Shoulder emulates a middle click.
- Left Trigger (L2) closes the top-most window in the Editor (Backspace key)
- Right Shoulder toggles between Mouse Mode and other scene-specific mode.

# Gameplay Mode

- Left stick moves the player character (left/right only).
- D-Pad also moves the player character (left/right only).
- PrimaryButton is to "Use"
- SecondaryButton is to "Jump"
- If the player has antigravity, up/down controls on left stick or D-Pad work too.
- Right Shoulder toggles between GameplayMode and MouseMode.
*/
package gamepad

import (
	"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
	"git.kirsle.net/SketchyMaze/doodle/pkg/log"
	"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
	"git.kirsle.net/SketchyMaze/doodle/pkg/sprites"
	"git.kirsle.net/go/render"
	"git.kirsle.net/go/render/event"
	"git.kirsle.net/go/ui"
)

// Global state variables for gamepad support.
var (
	// PlayScene tells us whether antigravity is on, so directional controls work all directions.
	PlayModeAntigravity bool
	SceneName           string // Set by doodle.Goto() so we know what scene name we're on.

	playerOne *int // controller index for Player 1 (main).
	p1style   Style
	p1mode    Mode

	// Mouse cursor
	cursorVisible bool
	cursorSprite  *ui.Image
	cursorLast    render.Point // detect if mouse cursor took over from gamepad

	// MouseMode button state history to emulate mouse-ups.
	leftClickLast   bool
	rightClickLast  bool
	middleClickLast bool
	leftTriggerLast bool

	// MouseMode right stick last position, for arrow key (level scroll) emulation.
	rightStickLast event.Vector

	// Gameplay mode last left-stick position.
	leftStickLast event.Vector
	dpLeftLast    bool // D-Pad last positions.
	dpRightLast   bool
	dpDownLast    bool
	dpUpLast      bool

	// Right Shoulder last state (mode switch)
	rightShoulderLast bool
)

// SetControllerIndex sets which gamepad will be "Player One"
func SetControllerIndex(index int) {
	playerOne = &index
}

// UnsetController detaches the Player One controller.
func UnsetController() {
	playerOne = nil
}

// SetStyle sets the controller button style.
func SetStyle(s Style) {
	p1style = s
}

// SetMode sets the controller mode.
func SetMode(m Mode) {
	p1mode = m
}

// PrimaryButton returns whether the A or X button is pressed.
func PrimaryButton(ctrl event.GameController) bool {
	if p1style == NStyle {
		return ctrl.ButtonB() || ctrl.ButtonY()
	}
	return ctrl.ButtonA() || ctrl.ButtonX()
}

// SecondaryButton returns whether the B or Y button is pressed.
func SecondaryButton(ctrl event.GameController) bool {
	if p1style == NStyle {
		return ctrl.ButtonA() || ctrl.ButtonX()
	}
	return ctrl.ButtonB() || ctrl.ButtonY()
}

// Loop hooks the render events on each game tick.
func Loop(ev *event.State) {
	// If we don't have a controller registered, watch out for one until we do.
	if playerOne == nil {
		if len(ev.Controllers) > 0 {
			for idx, ctrl := range ev.Controllers {
				SetControllerIndex(idx)
				log.Info("Gamepad: using controller #%d (%s) as Player 1", idx, ctrl.Name())
				break
			}
		} else {
			return
		}
	}

	// Get our SDL2 controller.
	ctrl, ok := ev.GetController(*playerOne)
	if !ok {
		log.Error("gamepad: controller #%d has gone away! Detaching as Player 1", playerOne)
		playerOne = nil
		return
	}

	// Right Shoulder = toggle controller mode, handle this first.
	if ctrl.ButtonR1() {
		if !rightShoulderLast {
			if SceneName == "Play" {
				// Toggle between GameplayMode and MouseMode.
				if p1mode == GameplayMode {
					shmem.FlashError("Mouse Mode: Left stick moves the cursor.")
					p1mode = MouseMode
				} else {
					shmem.FlashError("Game Mode: Left stick moves the player.")
					p1mode = GameplayMode
				}

				// Reset all button states.
				ev.Left = false
				ev.Right = false
				ev.Up = false
				ev.Down = false
				ev.Enter = false
				ev.Space = false
			}

			rightShoulderLast = true
			return
		}
	} else if rightShoulderLast {
		rightShoulderLast = false
	}

	// If we are in Play Mode, translate gamepad events into key events.
	if p1mode == GameplayMode {
		// D-Pad controls to move the player character.
		if ctrl.ButtonLeft() {
			ev.Left = true
			dpLeftLast = true
		} else if dpLeftLast {
			ev.Left = false
			dpLeftLast = false
		}

		if ctrl.ButtonRight() {
			ev.Right = true
			dpRightLast = true
		} else if dpRightLast {
			ev.Right = false
			dpRightLast = false
		}

		// Antigravity on? Up/Down arrow emulation.
		if PlayModeAntigravity {
			if ctrl.ButtonUp() {
				ev.Up = true
				dpUpLast = true
			} else if dpUpLast {
				ev.Up = false
				dpUpLast = false
			}

			if ctrl.ButtonDown() {
				ev.Down = true
				dpDownLast = true
			} else if dpDownLast {
				ev.Down = false
				dpDownLast = false
			}
		}

		// "Use" button.
		if PrimaryButton(ctrl) {
			ev.Space = true
			ev.Enter = true // to click thru modals
			leftClickLast = true
		} else if leftClickLast {
			ev.Space = false
			ev.Enter = false
			leftClickLast = false
		}

		// Jump button.
		if SecondaryButton(ctrl) {
			ev.Up = true
			rightClickLast = true
		} else if rightClickLast {
			ev.Up = false
			rightClickLast = false
		}

		// Left control stick to move the player character.
		// TODO: analog movements.
		leftStick := ctrl.LeftStick()
		if leftStick.X != 0 {
			if leftStick.X < -balance.GameControllerScrollMin {
				ev.Left = true
				ev.Right = false
			} else if leftStick.X > balance.GameControllerScrollMin {
				ev.Right = true
				ev.Left = false
			} else {
				ev.Right = false
				ev.Left = false
			}
		} else if leftStickLast.X != 0 {
			ev.Left = false
			ev.Right = false
		}

		// Antigravity on?
		if PlayModeAntigravity {
			if leftStick.Y != 0 {
				if leftStick.Y < -balance.GameControllerScrollMin {
					ev.Up = true
					ev.Down = false
				} else if leftStick.Y > balance.GameControllerScrollMin {
					ev.Down = true
					ev.Up = false
				} else {
					ev.Down = false
					ev.Up = false
				}
			} else if leftStickLast.Y != 0 {
				ev.Up = false
				ev.Down = false
			}
		}

		leftStickLast = leftStick
	}

	// If we are emulating a mouse, handle that now.
	if p1mode == MouseMode {
		// Move the cursor.
		leftStick := ctrl.LeftStick()
		if leftStick.X != 0 || leftStick.Y != 0 {
			cursorVisible = true
		} else if cursorVisible {
			// If the mouse cursor has moved behind our back (e.g., real mouse moved), turn off
			// the MouseMode cursor sprite.
			if cursorLast.X != ev.CursorX || cursorLast.Y != ev.CursorY {
				cursorVisible = false
			}
		}

		ev.CursorX += int(leftStick.X * balance.GameControllerMouseMoveMax)
		ev.CursorY += int(leftStick.Y * balance.GameControllerMouseMoveMax)

		// Constrain the cursor inside window boundaries.
		w, h := shmem.CurrentRenderEngine.WindowSize()
		if ev.CursorX < 0 {
			ev.CursorX = 0
		} else if ev.CursorX > w {
			ev.CursorX = w
		}
		if ev.CursorY < 0 {
			ev.CursorY = 0
		} else if ev.CursorY > h {
			ev.CursorY = h
		}

		// Store last cursor point so we can detect mouse movement outside the gamepad.
		cursorLast = render.NewPoint(ev.CursorX, ev.CursorY)

		// Are we clicking?
		if PrimaryButton(ctrl) {
			ev.Button1 = true // left-click
			leftClickLast = true
		} else if leftClickLast {
			ev.Button1 = false
			leftClickLast = false
		}

		// Right-click
		if SecondaryButton(ctrl) {
			ev.Button3 = true // right-click
			rightClickLast = true
		} else if rightClickLast {
			ev.Button3 = false
			rightClickLast = false
		}

		// Middle-click
		if ctrl.ButtonL1() {
			ev.Button2 = true // middle click
			middleClickLast = true
		} else if middleClickLast {
			ev.Button2 = false
			middleClickLast = false
		}

		// Left Trigger = Backspace (close active window)
		if ctrl.ButtonL2() {
			if !leftTriggerLast {
				ev.SetKeyDown(`\b`, true)
			}
			leftTriggerLast = true
		} else if leftTriggerLast {
			ev.SetKeyDown(`\b`, false)
			leftTriggerLast = false
		}

		// Arrow Key emulation on the right control stick, e.g. for Level Editor.
		rightStick := ctrl.RightStick()
		if rightStick.X != 0 {
			if rightStick.X < -balance.GameControllerScrollMin {
				ev.Left = true
				ev.Right = false
			} else if rightStick.X > balance.GameControllerScrollMin {
				ev.Right = true
				ev.Left = false
			} else {
				ev.Right = false
				ev.Left = false
			}
		} else if rightStickLast.X != 0 {
			ev.Left = false
			ev.Right = false
		}

		if rightStick.Y != 0 {
			if rightStick.Y < -balance.GameControllerScrollMin {
				ev.Up = true
				ev.Down = false
			} else if rightStick.Y > balance.GameControllerScrollMin {
				ev.Down = true
				ev.Up = false
			} else {
				ev.Down = false
				ev.Up = false
			}
		} else if rightStickLast.Y != 0 {
			ev.Up = false
			ev.Down = false
		}

		rightStickLast = rightStick
	}
}

// Draw the cursor on screen if the game controller is emulating a mouse.
func Draw(e render.Engine) {
	if playerOne == nil || p1mode != MouseMode || !cursorVisible {
		return
	}

	if cursorSprite == nil {
		img, err := sprites.LoadImage(e, balance.CursorIcon)
		if err != nil {
			log.Error("gamepad: couldn't load cursor sprite (%s): %s", balance.CursorIcon, err)
			return
		}
		cursorSprite = img
	}

	cursorSprite.Present(e, shmem.Cursor)
}
