// Copyright 2015 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build ignore

// The key name convention follows the Web standard: https://www.w3.org/TR/uievents-code/#code-value-tables

package main

import (
	"bufio"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"text/template"
)

var (
	glfwKeyNameToGLFWKey            map[string]int
	uiKeyNameToGLFWKeyName          map[string]string
	androidKeyToUIKeyName           map[int]string
	iosKeyToUIKeyName               map[int]string
	uiKeyNameToJSCode               map[string]string
	oldEbitengineKeyNameToUIKeyName map[string]string
)

func init() {
	glfwKeyNameToGLFWKey = map[string]int{
		"Unknown":      -1,
		"Space":        32,
		"Apostrophe":   39,
		"Comma":        44,
		"Minus":        45,
		"Period":       46,
		"Slash":        47,
		"Semicolon":    59,
		"Equal":        61,
		"LeftBracket":  91,
		"Backslash":    92,
		"RightBracket": 93,
		"GraveAccent":  96,
		"World1":       161,
		"World2":       162,
		"Escape":       256,
		"Enter":        257,
		"Tab":          258,
		"Backspace":    259,
		"Insert":       260,
		"Delete":       261,
		"Right":        262,
		"Left":         263,
		"Down":         264,
		"Up":           265,
		"PageUp":       266,
		"PageDown":     267,
		"Home":         268,
		"End":          269,
		"CapsLock":     280,
		"ScrollLock":   281,
		"NumLock":      282,
		"PrintScreen":  283,
		"Pause":        284,
		"LeftShift":    340,
		"LeftControl":  341,
		"LeftAlt":      342,
		"LeftSuper":    343,
		"RightShift":   344,
		"RightControl": 345,
		"RightAlt":     346,
		"RightSuper":   347,
		"Menu":         348,
		"KPDecimal":    330,
		"KPDivide":     331,
		"KPMultiply":   332,
		"KPSubtract":   333,
		"KPAdd":        334,
		"KPEnter":      335,
		"KPEqual":      336,
		"Last":         348,
	}

	uiKeyNameToGLFWKeyName = map[string]string{
		"Space":          "Space",
		"Quote":          "Apostrophe",
		"Comma":          "Comma",
		"Minus":          "Minus",
		"Period":         "Period",
		"Slash":          "Slash",
		"Semicolon":      "Semicolon",
		"Equal":          "Equal",
		"BracketLeft":    "LeftBracket",
		"Backslash":      "Backslash",
		"BracketRight":   "RightBracket",
		"Backquote":      "GraveAccent",
		"Escape":         "Escape",
		"Enter":          "Enter",
		"Tab":            "Tab",
		"Backspace":      "Backspace",
		"Insert":         "Insert",
		"Delete":         "Delete",
		"ArrowRight":     "Right",
		"ArrowLeft":      "Left",
		"ArrowDown":      "Down",
		"ArrowUp":        "Up",
		"PageUp":         "PageUp",
		"PageDown":       "PageDown",
		"Home":           "Home",
		"End":            "End",
		"CapsLock":       "CapsLock",
		"ScrollLock":     "ScrollLock",
		"NumLock":        "NumLock",
		"PrintScreen":    "PrintScreen",
		"Pause":          "Pause",
		"ShiftLeft":      "LeftShift",
		"ControlLeft":    "LeftControl",
		"AltLeft":        "LeftAlt",
		"MetaLeft":       "LeftSuper",
		"ShiftRight":     "RightShift",
		"ControlRight":   "RightControl",
		"AltRight":       "RightAlt",
		"MetaRight":      "RightSuper",
		"ContextMenu":    "Menu",
		"NumpadAdd":      "KPAdd",
		"NumpadDecimal":  "KPDecimal",
		"NumpadDivide":   "KPDivide",
		"NumpadMultiply": "KPMultiply",
		"NumpadSubtract": "KPSubtract",
		"NumpadEnter":    "KPEnter",
		"NumpadEqual":    "KPEqual",
		"IntlBackslash":  "World1",
	}

	// https://developer.android.com/reference/android/view/KeyEvent
	//
	// Android doesn't distinguish these keys:
	// - a US backslash key (HID: 0x31),
	// - an international pound/tilde key (HID: 0x32), and
	// - an international backslash key (HID: 0x64).
	// These are mapped to the same key code KEYCODE_BACKSLASH (73).
	// See https://source.android.com/docs/core/interaction/input/keyboard-devices
	androidKeyToUIKeyName = map[int]string{
		55:  "Comma",
		56:  "Period",
		57:  "AltLeft",
		58:  "AltRight",
		115: "CapsLock",
		113: "ControlLeft",
		114: "ControlRight",
		59:  "ShiftLeft",
		60:  "ShiftRight",
		66:  "Enter",
		62:  "Space",
		61:  "Tab",
		112: "Delete", // KEYCODE_FORWARD_DEL
		123: "End",
		122: "Home",
		124: "Insert",
		93:  "PageDown",
		92:  "PageUp",
		20:  "ArrowDown",
		21:  "ArrowLeft",
		22:  "ArrowRight",
		19:  "ArrowUp",
		111: "Escape",
		67:  "Backspace", // KEYCODE_DEL
		75:  "Quote",
		69:  "Minus",
		76:  "Slash",
		74:  "Semicolon",
		70:  "Equal",
		71:  "BracketLeft",
		73:  "Backslash",
		72:  "BracketRight",
		68:  "Backquote",
		143: "NumLock",
		121: "Pause",       // KEYCODE_BREAK
		120: "PrintScreen", // KEYCODE_SYSRQ
		116: "ScrollLock",
		82:  "ContextMenu",
		157: "NumpadAdd",
		158: "NumpadDecimal",
		154: "NumpadDivide",
		155: "NumpadMultiply",
		156: "NumpadSubtract",
		160: "NumpadEnter",
		161: "NumpadEqual",
		117: "MetaLeft",
		118: "MetaRight",
	}

	// https://developer.apple.com/documentation/uikit/uikeyboardhidusage?language=objc
	iosKeyToUIKeyName = map[int]string{
		0xE2: "AltLeft",
		0xE6: "AltRight",
		0x51: "ArrowDown",
		0x50: "ArrowLeft",
		0x4F: "ArrowRight",
		0x52: "ArrowUp",
		0x35: "Backquote",

		// These three keys are:
		// - US backslash-pipe key, and
		// - non-US hashmark key (bottom left of return; on German layout, this is the #' key).
		// On US layout configurations, they all map to the same characters - the backslash.
		//
		// See also: https://www.w3.org/TR/uievents-code/#keyboard-102
		0x31: "Backslash", // UIKeyboardHIDUsageKeyboardBackslash
		0x32: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSPound

		0x64: "IntlBackslash", // UIKeyboardHIDUsageKeyboardNonUSBackslash

		0x2A: "Backspace",
		0x2F: "BracketLeft",
		0x30: "BracketRight",

		// Caps Lock can either be a normal key or a hardware toggle.
		0x39: "CapsLock", // UIKeyboardHIDUsageKeyboardCapsLock
		0x82: "CapsLock", // UIKeyboardHIDUsageKeyboardLockingCapsLock

		0x36: "Comma",
		0xE0: "ControlLeft",
		0xE4: "ControlRight",
		0x4C: "Delete",
		0x4D: "End",
		0x28: "Enter",
		0x2E: "Equal",
		0x29: "Escape",
		0x4A: "Home",
		0x49: "Insert",
		0x76: "ContextMenu",
		0xE3: "MetaLeft",
		0xE7: "MetaRight",
		0x2D: "Minus",

		// Num Lock can either be a normal key or a hardware toggle.
		0x53: "NumLock", // UIKeyboardHIDUsageKeyboardNumLock
		0x83: "NumLock", // UIKeyboardHIDUsageKeyboardLockingNumLock

		0x57: "NumpadAdd",

		// Some keyboard layouts have a comma, some a period on the numeric pad.
		// They are the same key, though.
		0x63: "NumpadDecimal", // UIKeyboardHIDUsageKeypadPeriod
		0x85: "NumpadDecimal", // UIKeyboardHIDUsageKeypadComma

		0x54: "NumpadDivide",
		0x58: "NumpadEnter",

		// Some numeric keypads also have an equals sign.
		// There appear to be two separate keycodes for that.
		0x67: "NumpadEqual", // UIKeyboardHIDUsageKeypadEqualSign
		0x86: "NumpadEqual", // UIKeyboardHIDUsageKeypadEqualSignAS400

		0x55: "NumpadMultiply",
		0x56: "NumpadSubtract",
		0x4E: "PageDown",
		0x4B: "PageUp",
		0x48: "Pause",
		0x37: "Period",
		0x46: "PrintScreen",
		0x34: "Quote",

		// Scroll Lock can either be a normal key or a hardware toggle.
		0x47: "ScrollLock", // UIKeyboardHIDUsageKeyboardScrollLock
		0x84: "ScrollLock", // UIKeyboardHIDUsageKeyboardLockingScrollLock

		0x33: "Semicolon",
		0xE1: "ShiftLeft",
		0xE5: "ShiftRight",
		0x38: "Slash",
		0x2C: "Space",
		0x2B: "Tab",
	}

	// The UI key and JS key are almost same but very slightly different (e.g., 'A' vs 'KeyA').
	uiKeyNameToJSCode = map[string]string{
		"Comma":          "Comma",
		"Period":         "Period",
		"AltLeft":        "AltLeft",
		"AltRight":       "AltRight",
		"CapsLock":       "CapsLock",
		"ControlLeft":    "ControlLeft",
		"ControlRight":   "ControlRight",
		"ShiftLeft":      "ShiftLeft",
		"ShiftRight":     "ShiftRight",
		"Enter":          "Enter",
		"Space":          "Space",
		"Tab":            "Tab",
		"Delete":         "Delete",
		"End":            "End",
		"Home":           "Home",
		"Insert":         "Insert",
		"PageDown":       "PageDown",
		"PageUp":         "PageUp",
		"ArrowDown":      "ArrowDown",
		"ArrowLeft":      "ArrowLeft",
		"ArrowRight":     "ArrowRight",
		"ArrowUp":        "ArrowUp",
		"Escape":         "Escape",
		"Backspace":      "Backspace",
		"Quote":          "Quote",
		"Minus":          "Minus",
		"Slash":          "Slash",
		"Semicolon":      "Semicolon",
		"Equal":          "Equal",
		"BracketLeft":    "BracketLeft",
		"Backslash":      "Backslash",
		"BracketRight":   "BracketRight",
		"Backquote":      "Backquote",
		"NumLock":        "NumLock",
		"Pause":          "Pause",
		"PrintScreen":    "PrintScreen",
		"ScrollLock":     "ScrollLock",
		"ContextMenu":    "ContextMenu",
		"NumpadAdd":      "NumpadAdd",
		"NumpadDecimal":  "NumpadDecimal",
		"NumpadDivide":   "NumpadDivide",
		"NumpadMultiply": "NumpadMultiply",
		"NumpadSubtract": "NumpadSubtract",
		"NumpadEnter":    "NumpadEnter",
		"NumpadEqual":    "NumpadEqual",
		"MetaLeft":       "MetaLeft",
		"MetaRight":      "MetaRight",
		"IntlBackslash":  "IntlBackslash",
	}

	const (
		glfwKey0   = 48
		glfwKeyA   = 65
		glfwKeyF1  = 290
		glfwKeyKP0 = 320
	)

	// ASCII: 0 - 9
	for c := '0'; c <= '9'; c++ {
		glfwKeyNameToGLFWKey[string(c)] = int(glfwKey0 + c - '0')
		name := "Digit" + string(c)
		uiKeyNameToGLFWKeyName[name] = string(c)
		androidKeyToUIKeyName[7+int(c)-'0'] = name
		// Gomobile's key code (= USB HID key codes) has successive key codes for 1, 2, ..., 9, 0
		// in this order. Same for iOS.
		if c == '0' {
			iosKeyToUIKeyName[0x27] = name
		} else {
			iosKeyToUIKeyName[0x1E+int(c)-'1'] = name
		}
		uiKeyNameToJSCode[name] = name

	}
	// ASCII: A - Z
	for c := 'A'; c <= 'Z'; c++ {
		glfwKeyNameToGLFWKey[string(c)] = int(glfwKeyA + c - 'A')
		uiKeyNameToGLFWKeyName[string(c)] = string(c)
		androidKeyToUIKeyName[29+int(c)-'A'] = string(c)
		iosKeyToUIKeyName[0x04+int(c)-'A'] = string(c)
		uiKeyNameToJSCode[string(c)] = "Key" + string(c)
	}
	// Function keys
	for i := 1; i <= 24; i++ {
		name := "F" + strconv.Itoa(i)
		glfwKeyNameToGLFWKey[name] = glfwKeyF1 + i - 1
		uiKeyNameToGLFWKeyName[name] = name
		// Android doesn't support F13 and more as constants of KeyEvent:
		// https://developer.android.com/reference/android/view/KeyEvent
		//
		// Note that F13 might be avilable if HID devices are available directly:
		// https://source.android.com/docs/core/interaction/input/keyboard-devices
		if i <= 12 {
			androidKeyToUIKeyName[131+i-1] = name
		}
		if i <= 12 {
			iosKeyToUIKeyName[0x3A+i-1] = name
		} else {
			iosKeyToUIKeyName[0x68+i-13] = name
		}
		uiKeyNameToJSCode[name] = name
	}
	// Numpad
	// https://www.w3.org/TR/uievents-code/#key-numpad-section
	for c := '0'; c <= '9'; c++ {
		name := "Numpad" + string(c)
		glfwKeyNameToGLFWKey["KP"+string(c)] = int(glfwKeyKP0 + c - '0')
		uiKeyNameToGLFWKeyName[name] = "KP" + string(c)
		androidKeyToUIKeyName[144+int(c)-'0'] = name
		// Gomobile's key code (= USB HID key codes) has successive key codes for 1, 2, ..., 9, 0
		// in this order. Same for iOS.
		if c == '0' {
			iosKeyToUIKeyName[0x62] = name
		} else {
			iosKeyToUIKeyName[0x59+int(c)-'1'] = name
		}
		uiKeyNameToJSCode[name] = name
	}

	// Keys for backward compatibility
	oldEbitengineKeyNameToUIKeyName = map[string]string{
		"0":            "Digit0",
		"1":            "Digit1",
		"2":            "Digit2",
		"3":            "Digit3",
		"4":            "Digit4",
		"5":            "Digit5",
		"6":            "Digit6",
		"7":            "Digit7",
		"8":            "Digit8",
		"9":            "Digit9",
		"Apostrophe":   "Quote",
		"Down":         "ArrowDown",
		"GraveAccent":  "Backquote",
		"KP0":          "Numpad0",
		"KP1":          "Numpad1",
		"KP2":          "Numpad2",
		"KP3":          "Numpad3",
		"KP4":          "Numpad4",
		"KP5":          "Numpad5",
		"KP6":          "Numpad6",
		"KP7":          "Numpad7",
		"KP8":          "Numpad8",
		"KP9":          "Numpad9",
		"KPAdd":        "NumpadAdd",
		"KPDecimal":    "NumpadDecimal",
		"KPDivide":     "NumpadDivide",
		"KPMultiply":   "NumpadMultiply",
		"KPSubtract":   "NumpadSubtract",
		"KPEnter":      "NumpadEnter",
		"KPEqual":      "NumpadEqual",
		"Left":         "ArrowLeft",
		"LeftBracket":  "BracketLeft",
		"Menu":         "ContextMenu",
		"Right":        "ArrowRight",
		"RightBracket": "BracketRight",
		"Up":           "ArrowUp",
	}
}

const ebitengineKeysTmpl = `{{.License}}

{{.DoNotEdit}}

package ebiten

import (
	"fmt"
	"strings"

	"github.com/hajimehoshi/ebiten/v2/internal/ui"
)

// A Key represents a keyboard key.
// These keys represent physical keys of US keyboard.
// For example, KeyQ represents Q key on US keyboards and ' (quote) key on Dvorak keyboards.
type Key int

// Keys.
const (
{{range $index, $name := .EbitengineKeyNamesWithoutMods}}Key{{$name}} Key = Key(ui.Key{{$name}})
{{end}}	KeyAlt     Key = Key(ui.KeyReserved0)
	KeyControl Key = Key(ui.KeyReserved1)
	KeyShift   Key = Key(ui.KeyReserved2)
	KeyMeta    Key = Key(ui.KeyReserved3)
	KeyMax     Key = KeyMeta

	// Keys for backward compatibility.
	// Deprecated: as of v2.1.
{{range $old, $new := .OldEbitengineKeyNameToUIKeyName}}Key{{$old}} Key = Key(ui.Key{{$new}})
{{end}}
)

func (k Key) isValid() bool {
	switch k {
	{{range $name := .EbitengineKeyNamesWithoutOld}}case Key{{$name}}:
		return true
	{{end}}
	default:
		return false
	}
}

// String returns a string representing the key.
//
// If k is an undefined key, String returns an empty string.
func (k Key) String() string {
	switch k {
	{{range $name := .EbitengineKeyNamesWithoutOld}}case Key{{$name}}:
		return {{$name | printf "%q"}}
	{{end}}}
	return ""
}

func keyNameToKeyCode(name string) (Key, bool) {
	switch strings.ToLower(name) {
	{{range $name := .EbitengineKeyNames}}case {{$name | printf "%q" | ToLower}}:
		return Key{{$name}}, true
	{{end}}}
	return 0, false
}

// MarshalText implements encoding.TextMarshaler.
func (k Key) MarshalText() ([]byte, error) {
	return []byte(k.String()), nil
}

// UnmarshalText implements encoding.TextUnmarshaler
func (k *Key) UnmarshalText(text []byte) error {
	key, ok := keyNameToKeyCode(string(text))
	if !ok {
		return fmt.Errorf("ebiten: unexpected key name: %s", string(text))
	}
	*k = key
	return nil
}
`

const uiKeysTmpl = `{{.License}}

{{.DoNotEdit}}

package ui

import (
	"fmt"
)

type Key int

const (
{{range $index, $name := .UIKeyNames}}Key{{$name}}{{if eq $index 0}} Key = iota{{end}}
{{end}}	KeyReserved0
	KeyReserved1
	KeyReserved2
	KeyReserved3
	KeyMax = KeyReserved3
)

func (k Key) String() string {
	switch k {
	{{range $index, $name := .UIKeyNames}}case Key{{$name}}:
		return {{$name | printf "Key%s" | printf "%q"}}
	{{end}}}
	return fmt.Sprintf("Key(%d)", k)
}
`

const eventKeysTmpl = `{{.License}}

{{.DoNotEdit}}

package event

import (
	"github.com/hajimehoshi/ebiten/v2/internal/ui"
)

type Key = ui.Key

const (
{{range $index, $name := .UIKeyNames}}Key{{$name}} = ui.Key{{$name}}
{{end}}
)
`

const uiGLFWKeysTmpl = `{{.License}}

{{.DoNotEdit}}

{{.BuildConstraints}}

package ui

import (
	"github.com/hajimehoshi/ebiten/v2/internal/glfw"
)

var uiKeyToGLFWKey = map[Key]glfw.Key{
{{range $dname, $gname := .UIKeyNameToGLFWKeyName}}Key{{$dname}}: glfw.Key{{$gname}},
{{end}}
}
`

const uiJSKeysTmpl = `{{.License}}

{{.DoNotEdit}}

{{.BuildConstraints}}

package ui

import (
	"syscall/js"
)

var uiKeyToJSCode = map[Key]js.Value{
{{range $name, $code := .UIKeyNameToJSCode}}Key{{$name}}: js.ValueOf({{$code | printf "%q"}}),
{{end}}
}
`

const glfwKeysTmpl = `{{.License}}

{{.DoNotEdit}}

{{.BuildConstraints}}

package glfw

const (
{{range $name, $key := .GLFWKeyNameToGLFWKey}}Key{{$name}} = Key({{$key}})
{{end}}
)
`

const mobileAndroidKeysTmpl = `{{.License}}

{{.DoNotEdit}}

{{.BuildConstraints}}

package ebitenmobileview

import (
	"github.com/hajimehoshi/ebiten/v2/internal/ui"
)

var androidKeyToUIKey = map[int]ui.Key{
{{range $key, $name := .AndroidKeyToUIKeyName}}{{$key}}: ui.Key{{$name}},
{{end}}
}
`

const mobileIOSKeysTmpl = `{{.License}}

{{.DoNotEdit}}

{{.BuildConstraints}}

package ebitenmobileview

import (
	"github.com/hajimehoshi/ebiten/v2/internal/ui"
)

var iosKeyToUIKey = map[int]ui.Key{
{{range $key, $name := .IOSKeyToUIKeyName}}{{$key}}: ui.Key{{$name}},
{{end}}
}
`

func digitKey(name string) int {
	if len(name) != 1 {
		return -1
	}
	c := name[0]
	if c < '0' || '9' < c {
		return -1
	}
	return int(c - '0')
}

func alphabetKey(name string) rune {
	if len(name) != 1 {
		return -1
	}
	c := rune(name[0])
	if c < 'A' || 'Z' < c {
		return -1
	}
	return c
}

func functionKey(name string) int {
	if len(name) < 2 {
		return -1
	}
	if name[0] != 'F' {
		return -1
	}
	i, err := strconv.Atoi(name[1:])
	if err != nil {
		return -1
	}
	return i
}

func keyNamesLess(k []string) func(i, j int) bool {
	return func(i, j int) bool {
		k0, k1 := k[i], k[j]
		d0, d1 := digitKey(k0), digitKey(k1)
		a0, a1 := alphabetKey(k0), alphabetKey(k1)
		f0, f1 := functionKey(k0), functionKey(k1)
		if d0 != -1 {
			if d1 != -1 {
				return d0 < d1
			}
			return true
		}
		if a0 != -1 {
			if d1 != -1 {
				return false
			}
			if a1 != -1 {
				return a0 < a1
			}
			return true
		}
		if d1 != -1 {
			return false
		}
		if a1 != -1 {
			return false
		}
		if f0 != -1 && f1 != -1 {
			return f0 < f1
		}
		return k0 < k1
	}
}

const license = `// Copyright 2013 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
`

func main() {
	// Follow the standard comment rule (https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source).
	doNotEdit := "// Code generated by genkeys.go using 'go generate'. DO NOT EDIT."

	ebitengineKeyNames := []string{}
	ebitengineKeyNamesWithoutOld := []string{}
	ebitengineKeyNamesWithoutMods := []string{}
	uiKeyNames := []string{}

	for name := range uiKeyNameToJSCode {
		uiKeyNames = append(uiKeyNames, name)
		ebitengineKeyNames = append(ebitengineKeyNames, name)
		ebitengineKeyNamesWithoutOld = append(ebitengineKeyNamesWithoutOld, name)
		ebitengineKeyNamesWithoutMods = append(ebitengineKeyNamesWithoutMods, name)
	}
	for old := range oldEbitengineKeyNameToUIKeyName {
		ebitengineKeyNames = append(ebitengineKeyNames, old)
	}
	// Keys for modifiers
	ebitengineKeyNames = append(ebitengineKeyNames, "Alt", "Control", "Shift", "Meta")
	ebitengineKeyNamesWithoutOld = append(ebitengineKeyNamesWithoutOld, "Alt", "Control", "Shift", "Meta")

	sort.Slice(ebitengineKeyNames, keyNamesLess(ebitengineKeyNames))
	sort.Slice(ebitengineKeyNamesWithoutOld, keyNamesLess(ebitengineKeyNamesWithoutOld))
	sort.Slice(ebitengineKeyNamesWithoutMods, keyNamesLess(ebitengineKeyNamesWithoutMods))
	sort.Slice(uiKeyNames, keyNamesLess(uiKeyNames))

	// TODO: Add this line for event package (#926).
	//
	//     filepath.Join("event", "keys.go"):                              eventKeysTmpl,

	for path, tmpl := range map[string]string{
		filepath.Join("internal", "glfw", "keys.go"):                   glfwKeysTmpl,
		filepath.Join("internal", "ui", "keys.go"):                     uiKeysTmpl,
		filepath.Join("internal", "ui", "keys_glfw.go"):                uiGLFWKeysTmpl,
		filepath.Join("internal", "ui", "keys_js.go"):                  uiJSKeysTmpl,
		filepath.Join("keys.go"):                                       ebitengineKeysTmpl,
		filepath.Join("mobile", "ebitenmobileview", "keys_android.go"): mobileAndroidKeysTmpl,
		filepath.Join("mobile", "ebitenmobileview", "keys_ios.go"):     mobileIOSKeysTmpl,
	} {
		f, err := os.Create(path)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()

		funcs := template.FuncMap{
			"ToLower": strings.ToLower,
		}
		tmpl, err := template.New(path).Funcs(funcs).Parse(tmpl)
		if err != nil {
			log.Fatal(err)
		}

		// The build tag can't be included in the templates because of `go vet`.
		// Pass the build tag and extract this in the template to make `go vet` happy.
		buildConstraints := ""
		switch path {
		case filepath.Join("internal", "glfw", "keys.go"):
			buildConstraints = "//go:build darwin || freebsd || linux || netbsd || openbsd || windows"
		case filepath.Join("internal", "ui", "keys_mobile.go"):
			buildConstraints = "//go:build android || ios"
		case filepath.Join("internal", "ui", "keys_glfw.go"):
			buildConstraints = "//go:build !android && !ios && !js && !nintendosdk && !playstation5"
		}
		// NOTE: According to godoc, maps are automatically sorted by key.
		w := bufio.NewWriter(f)
		if err := tmpl.Execute(w, struct {
			License                         string
			DoNotEdit                       string
			BuildConstraints                string
			UIKeyNameToJSCode               map[string]string
			EbitengineKeyNames              []string
			EbitengineKeyNamesWithoutOld    []string
			EbitengineKeyNamesWithoutMods   []string
			GLFWKeyNameToGLFWKey            map[string]int
			UIKeyNames                      []string
			UIKeyNameToGLFWKeyName          map[string]string
			AndroidKeyToUIKeyName           map[int]string
			IOSKeyToUIKeyName               map[int]string
			OldEbitengineKeyNameToUIKeyName map[string]string
		}{
			License:                         license,
			DoNotEdit:                       doNotEdit,
			BuildConstraints:                buildConstraints,
			UIKeyNameToJSCode:               uiKeyNameToJSCode,
			EbitengineKeyNames:              ebitengineKeyNames,
			EbitengineKeyNamesWithoutOld:    ebitengineKeyNamesWithoutOld,
			EbitengineKeyNamesWithoutMods:   ebitengineKeyNamesWithoutMods,
			GLFWKeyNameToGLFWKey:            glfwKeyNameToGLFWKey,
			UIKeyNames:                      uiKeyNames,
			UIKeyNameToGLFWKeyName:          uiKeyNameToGLFWKeyName,
			AndroidKeyToUIKeyName:           androidKeyToUIKeyName,
			IOSKeyToUIKeyName:               iosKeyToUIKeyName,
			OldEbitengineKeyNameToUIKeyName: oldEbitengineKeyNameToUIKeyName,
		}); err != nil {
			log.Fatal(err)
		}

		if err := w.Flush(); err != nil {
			log.Fatal(err)
		}
	}
}
