// This file is part of Gopher2600.
//
// Gopher2600 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.
//
// Gopher2600 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 Gopher2600.  If not, see <https://www.gnu.org/licenses/>.

package sdlimgui

import (
	"fmt"
	"strings"
	"time"
	"unicode"

	"github.com/inkyblackness/imgui-go/v4"
	"github.com/jetsetilly/gopher2600/debugger/govern"
	"github.com/jetsetilly/gopher2600/hardware/television/specification"
	"github.com/jetsetilly/gopher2600/logger"
	"github.com/jetsetilly/gopher2600/notifications"
	"github.com/jetsetilly/gopher2600/userinput"
	"github.com/veandco/go-sdl2/sdl"
)

func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
	// notify phantom input system of a backspace. placed before the
	// no-repeat-key-presses test below
	if ev.Keysym.Scancode == sdl.SCANCODE_BACKSPACE && ev.Type == sdl.KEYDOWN {
		img.phantomInput = phantomInputBackSpace
	}

	// most keyboard events want to ignore repeat events. any that don't should
	// be handled before this condition
	if ev.Repeat == 1 {
		return
	}

	ctrl := ev.Keysym.Mod&sdl.KMOD_LCTRL == sdl.KMOD_LCTRL || ev.Keysym.Mod&sdl.KMOD_RCTRL == sdl.KMOD_RCTRL
	alt := ev.Keysym.Mod&sdl.KMOD_LALT == sdl.KMOD_LALT || ev.Keysym.Mod&sdl.KMOD_RALT == sdl.KMOD_RALT
	shift := ev.Keysym.Mod&sdl.KMOD_LSHIFT == sdl.KMOD_LSHIFT || ev.Keysym.Mod&sdl.KMOD_RSHIFT == sdl.KMOD_RSHIFT

	// enable window searching based on keyboard modifiers
	img.wm.searchActive = ctrl && shift

	// force tooltips if shift key is held down
	img.tooltipForce = shift

	if ev.Type == sdl.KEYUP {
		// special handling if window searching is enabled
		if img.wm.searchActive {
			switch ev.Keysym.Scancode {
			case sdl.SCANCODE_BACKSPACE:
				if len(img.wm.searchString) > 0 {
					img.wm.searchString = img.wm.searchString[:len(img.wm.searchString)-1]
				}
			case sdl.SCANCODE_SPACE:
				// dont allow leading space
				if len(img.wm.searchString) > 0 {
					img.wm.searchString = fmt.Sprintf("%s ", img.wm.searchString)
				}
			default:
				key := sdl.GetKeyFromScancode(ev.Keysym.Scancode)
				if unicode.IsPrint(rune(key)) {
					name := sdl.GetScancodeName(ev.Keysym.Scancode)
					img.wm.searchString = fmt.Sprintf("%s%s", img.wm.searchString, strings.ToLower(name))
				}
			}
			return
		}

		handled := true

		if img.isPlaymode() {
			switch ev.Keysym.Scancode {
			case sdl.SCANCODE_ESCAPE:
				if img.isCaptured() {
					img.setCapture(false)
				} else if img.wm.playmodeWindows[winSelectROMID].playmodeIsOpen() {
					img.wm.playmodeWindows[winSelectROMID].playmodeSetOpen(false)
				} else {
					img.quit()
				}

			case sdl.SCANCODE_LEFT:
				if alt {
					img.screen.SetRotation(specification.LeftRotation)
				}
			case sdl.SCANCODE_RIGHT:
				if alt {
					img.screen.SetRotation(specification.RightRotation)
				}
			case sdl.SCANCODE_UP:
				if alt {
					img.screen.SetRotation(specification.NormalRotation)
				}
			case sdl.SCANCODE_DOWN:
				if alt {
					img.screen.SetRotation(specification.FlippedRotation)
				}

			default:
				handled = false
			}
		}

		if !img.modalActive() {
			switch ev.Keysym.Scancode {
			case sdl.SCANCODE_TAB:
				if !img.isPlaymode() && imgui.IsAnyItemActive() {
					// in debugger mode do not handle if an imgui widget is active
					// (see the sdl.KEYDOWN branch below for opposite condition and
					// explanation)
					handled = false
				} else {
					if ctrl {
						img.dbg.ReloadCartridge()
					} else {
						// only open ROM selector if window has been focused for a
						// while. see windowFocusedTime declaration for an explanation
						if time.Since(img.windowFocusedTime) > 500*time.Millisecond {
							img.wm.toggleOpen(winSelectROMID)
						}
					}
				}

			case sdl.SCANCODE_GRAVE:
				if img.isPlaymode() {
					img.dbg.PushSetMode(govern.ModeDebugger)
				} else {
					img.dbg.PushSetMode(govern.ModePlay)
				}

			case sdl.SCANCODE_F7:
				if img.isPlaymode() {
					fps := img.prefs.fpsDetail.Get().(bool)
					img.prefs.fpsDetail.Set(!fps)
				}

			case sdl.SCANCODE_F8:
				w := img.wm.playmodeWindows[winBotID]
				w.playmodeSetOpen(!w.playmodeIsOpen())

			case sdl.SCANCODE_F9:
				img.wm.toggleOpen(winTrackerID)

			case sdl.SCANCODE_F10:
				img.wm.toggleOpen(winPrefsID)

			case sdl.SCANCODE_F11:
				img.prefs.fullScreen.Set(!img.prefs.fullScreen.Get().(bool))

			case sdl.SCANCODE_F12:
				if ctrl && !shift {
					img.screenshot(modeComposite, "")
				} else if shift && !ctrl {
					img.screenshot(modeMotion, "")
				} else {
					img.screenshot(modeSingle, "")
				}
				img.playScr.overlay.set(notifications.NotifyScreenshot)

			case sdl.SCANCODE_F14:
				fallthrough
			case sdl.SCANCODE_SCROLLLOCK:
				img.setCapture(!img.isCaptured())

			case sdl.SCANCODE_F15:
				fallthrough
			case sdl.SCANCODE_PAUSE:
				if img.isPlaymode() {
					if img.dbg.State() == govern.Paused {
						img.dbg.PushSetPause(false)
					} else {
						img.dbg.PushSetPause(true)
					}
				} else {
					if img.dbg.State() == govern.Paused {
						img.term.pushCommand("RUN")
					} else {
						img.setCapturedRunning(false)
					}
				}

			case sdl.SCANCODE_A:
				if ctrl {
					img.wm.arrangeBySize = 1
				} else {
					handled = false
				}

			case sdl.SCANCODE_R:
				if ctrl {
					img.dbg.ReloadCartridge()
				} else {
					handled = false
				}

			case sdl.SCANCODE_M:
				if ctrl {
					img.toggleAudioMute()
				} else {
					handled = false
				}

			default:
				handled = false
			}

			if handled {
				return
			}
		}
	} else if ev.Type == sdl.KEYDOWN {
		// for debugger mode we test for the ESC key press on the down event
		// and not the up event. this is because imgui widgets react to the ESC
		// key on the down event and we only want to perform our special ESC
		// key handling if no widget is active
		//
		// if we perform out special handling on the up stroke then the active
		// widget will be unselected and then the special handling perfomed on
		// every ESC KEY press. we don't want that. we want the active widget
		// to be deselected and for the special handling to require a
		// completely separate key press

		if !img.isPlaymode() && !img.modalActive() {
			switch ev.Keysym.Scancode {
			case sdl.SCANCODE_TAB:
				// in debugger mode do not handle if an imgui widget is not
				// active (see the sdl.KEYUP branch above for opposite
				// condition)
				//
				// this prevents a KEYDOWN being forwarded to imgui and without
				// the corresponding KEYUP if the TAB key was consumed becaue
				// IsAnyItemActive() was true at time of KEYUP. without this
				// check imgui thinks the TAB key is being held down
				if !imgui.IsAnyItemActive() {
					return
				}
			case sdl.SCANCODE_ESCAPE:
				if !imgui.IsAnyItemActive() {
					img.setCapturedRunning(!img.isCapturedRunning())
				}
			}
		}
	}

	// forward keypresses to userinput.Event channel
	if img.isCaptured() || (img.isPlaymode() && !imgui.IsAnyItemActive()) {
		switch ev.Type {
		case sdl.KEYDOWN:
			fallthrough
		case sdl.KEYUP:
			select {
			case img.dbg.UserInput() <- userinput.EventKeyboard{
				Key:  sdl.GetScancodeName(ev.Keysym.Scancode),
				Down: ev.Type == sdl.KEYDOWN,
				Mod:  getKeyMod(),
			}:
			default:
				logger.Log("sdlimgui", "dropped keyboard event")
			}
		}
	}

	// remaining keypresses forwarded to imgui io system
	switch ev.Type {
	case sdl.KEYDOWN:
		imgui.CurrentIO().KeyPress(int(ev.Keysym.Scancode))
		img.updateKeyModifier()
	case sdl.KEYUP:
		imgui.CurrentIO().KeyRelease(int(ev.Keysym.Scancode))
		img.updateKeyModifier()
	}
}

func getKeyMod() userinput.KeyMod {
	if sdl.GetModState()&sdl.KMOD_LALT == sdl.KMOD_LALT ||
		sdl.GetModState()&sdl.KMOD_RALT == sdl.KMOD_RALT {
		return userinput.KeyModAlt
	} else if sdl.GetModState()&sdl.KMOD_LSHIFT == sdl.KMOD_LSHIFT ||
		sdl.GetModState()&sdl.KMOD_RSHIFT == sdl.KMOD_RSHIFT {
		return userinput.KeyModShift
	} else if sdl.GetModState()&sdl.KMOD_LCTRL == sdl.KMOD_LCTRL ||
		sdl.GetModState()&sdl.KMOD_RCTRL == sdl.KMOD_RCTRL {
		return userinput.KeyModCtrl
	}
	return userinput.KeyModNone
}
