package d2input

import (
	"sort"

	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
	ebiten_input "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input/ebiten"
)

type inputManager struct {
	inputService d2interface.InputService
	cursorX      int
	cursorY      int

	buttonMod d2enum.MouseButtonMod
	keyMod    d2enum.KeyMod

	entries handlerEntryList
}

func New() d2interface.InputManager {
	return &inputManager{
		inputService: ebiten_input.InputService{},
	}
}

// Advance advances the inputManager
func (im *inputManager) Advance(_, _ float64) error {
	im.updateKeyMod()
	im.updateButtonMod()

	cursorX, cursorY := im.inputService.CursorPosition()
	eventBase := HandlerEvent{
		im.keyMod,
		im.buttonMod,
		cursorX,
		cursorY,
	}

	for key := d2enum.KeyMin; key <= d2enum.KeyMax; key++ {
		im.updateJustPressedKey(key, eventBase)
		im.updateJustReleasedKey(key, eventBase)
		im.updatePressedKey(key, eventBase)
	}

	im.updateInputChars(eventBase)

	for button := d2enum.MouseButtonMin; button <= d2enum.MouseButtonMax; button++ {
		im.updateJustPressedButton(button, eventBase)
		im.updateJustReleasedButton(button, eventBase)
		im.updatePressedButton(button, eventBase)
	}

	im.updateCursor(cursorX, cursorY, eventBase)

	return nil
}

func (im *inputManager) updateKeyMod() {
	im.keyMod = 0
	if im.inputService.IsKeyPressed(d2enum.KeyAlt) {
		im.keyMod |= d2enum.KeyModAlt
	}

	if im.inputService.IsKeyPressed(d2enum.KeyControl) {
		im.keyMod |= d2enum.KeyModControl
	}

	if im.inputService.IsKeyPressed(d2enum.KeyShift) {
		im.keyMod |= d2enum.KeyModShift
	}
}

func (im *inputManager) updateButtonMod() {
	im.buttonMod = 0
	if im.inputService.IsMouseButtonPressed(d2enum.MouseButtonLeft) {
		im.buttonMod |= d2enum.MouseButtonModLeft
	}

	if im.inputService.IsMouseButtonPressed(d2enum.MouseButtonMiddle) {
		im.buttonMod |= d2enum.MouseButtonModMiddle
	}

	if im.inputService.IsMouseButtonPressed(d2enum.MouseButtonRight) {
		im.buttonMod |= d2enum.MouseButtonModRight
	}
}

func (im *inputManager) updateJustPressedKey(k d2enum.Key, e HandlerEvent) {
	if im.inputService.IsKeyJustPressed(k) {
		event := KeyEvent{HandlerEvent: e, key: k}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.KeyDownHandler); ok {
				return l.OnKeyDown(&event)
			}

			return false
		}

		im.propagate(fn)
	}
}

func (im *inputManager) updateJustReleasedKey(k d2enum.Key, e HandlerEvent) {
	if im.inputService.IsKeyJustReleased(k) {
		event := KeyEvent{HandlerEvent: e, key: k}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.KeyUpHandler); ok {
				return l.OnKeyUp(&event)
			}

			return false
		}
		im.propagate(fn)
	}
}

func (im *inputManager) updatePressedKey(k d2enum.Key, e HandlerEvent) {
	if im.inputService.IsKeyPressed(k) {
		event := KeyEvent{
			HandlerEvent: e,
			key:          k,
			duration:     im.inputService.KeyPressDuration(k),
		}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.KeyRepeatHandler); ok {
				return l.OnKeyRepeat(&event)
			}

			return false
		}
		im.propagate(fn)
	}
}

func (im *inputManager) updateInputChars(eventBase HandlerEvent) {
	if chars := im.inputService.InputChars(); len(chars) > 0 {
		event := KeyCharsEvent{eventBase, chars}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.KeyCharsHandler); ok {
				l.OnKeyChars(&event)
			}

			return false
		}
		im.propagate(fn)
	}
}

func (im *inputManager) updateJustPressedButton(b d2enum.MouseButton, e HandlerEvent) {
	if im.inputService.IsMouseButtonJustPressed(b) {
		event := MouseEvent{e, b}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.MouseButtonDownHandler); ok {
				return l.OnMouseButtonDown(&event)
			}

			return false
		}
		im.propagate(fn)
	}
}

func (im *inputManager) updateJustReleasedButton(b d2enum.MouseButton, e HandlerEvent) {
	if im.inputService.IsMouseButtonJustReleased(b) {
		event := MouseEvent{e, b}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.MouseButtonUpHandler); ok {
				return l.OnMouseButtonUp(&event)
			}

			return false
		}
		im.propagate(fn)
	}
}

func (im *inputManager) updatePressedButton(b d2enum.MouseButton, e HandlerEvent) {
	if im.inputService.IsMouseButtonPressed(b) {
		event := MouseEvent{e, b}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.MouseButtonRepeatHandler); ok {
				return l.OnMouseButtonRepeat(&event)
			}

			return false
		}
		im.propagate(fn)
	}
}

func (im *inputManager) updateCursor(cursorX, cursorY int, e HandlerEvent) {
	if im.cursorX != cursorX || im.cursorY != cursorY {
		event := MouseMoveEvent{e}

		fn := func(handler d2interface.InputEventHandler) bool {
			if l, ok := handler.(d2interface.MouseMoveHandler); ok {
				return l.OnMouseMove(&event)
			}

			return false
		}
		im.propagate(fn)

		im.cursorX, im.cursorY = cursorX, cursorY
	}
}

// BindHandlerWithPriority adds an event handler with a specific call priority
func (im *inputManager) BindHandlerWithPriority(
	h d2interface.InputEventHandler,
	p d2enum.Priority) error {
	return im.bindHandler(h, p)
}

// BindHandler adds an event handler
func (im *inputManager) BindHandler(h d2interface.InputEventHandler) error {
	return im.bindHandler(h, d2enum.PriorityDefault)
}

// BindHandler adds an event handler
func (im *inputManager) bindHandler(h d2interface.InputEventHandler, p d2enum.Priority) error {
	for _, entry := range im.entries {
		if entry.handler == h {
			return ErrHasReg
		}
	}

	entry := handlerEntry{h, p}
	im.entries = append(im.entries, entry)
	sort.Sort(im.entries)

	return nil
}

// UnbindHandler removes a previously bound event handler
func (im *inputManager) UnbindHandler(handler d2interface.InputEventHandler) error {
	for i, entry := range im.entries {
		if entry.handler == handler {
			copy(im.entries[i:], im.entries[i+1:])
			im.entries = im.entries[:len(im.entries)-1]

			return nil
		}
	}

	return ErrNotReg
}

func (im *inputManager) propagate(callback func(d2interface.InputEventHandler) bool) {
	var priority d2enum.Priority

	var handled bool

	for _, entry := range im.entries {
		if priority > entry.priority && handled {
			break
		}

		if callback(entry.handler) {
			handled = true
		}

		priority = entry.priority
	}
}

type handlerEntry struct {
	handler  d2interface.InputEventHandler
	priority d2enum.Priority
}

type handlerEntryList []handlerEntry

func (lel handlerEntryList) Len() int {
	return len(lel)
}

func (lel handlerEntryList) Swap(i, j int) {
	lel[i], lel[j] = lel[j], lel[i]
}

func (lel handlerEntryList) Less(i, j int) bool {
	return lel[i].priority > lel[j].priority
}
