//go:build ignore

package main

import (
	"bufio"
	"bytes"
	"errors"
	"flag"
	"fmt"
	"go/format"
	"io"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"unicode/utf8"
)

func main() {
	out := flag.String("out", "bind.go", "out")
	dump := flag.Bool("dump", false, "dump")
	flag.Parse()
	if err := run(*out, *dump); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
}

func run(out string, dump bool) error {
	buf := new(bytes.Buffer)
	if _, err := buf.Write([]byte(hdr)); err != nil {
		return err
	}
	if err := loadVars(buf); err != nil {
		return err
	}
	if err := loadKeymaps(buf); err != nil {
		return err
	}
	if dump {
		return os.WriteFile(out, buf.Bytes(), 0o644)
	}
	b, err := format.Source(buf.Bytes())
	if err != nil {
		return err
	}
	return os.WriteFile(out, b, 0o644)
}

func loadVars(w io.Writer) error {
	fmt.Fprintln(w, "// DefaultVars are the default readline vars.")
	fmt.Fprintln(w, "//")
	fmt.Fprintln(w, "// see: INPUTRC=/dev/null bash -c 'bind -v'")
	fmt.Fprintln(w, "func DefaultVars() map[string]interface{} {")
	fmt.Fprintln(w, "\treturn map[string]interface{}{")
	s, err := load("bash", "-c", "bind -v")
	if err != nil {
		return err
	}
	for s.Scan() {
		v := strings.SplitN(strings.TrimSpace(s.Text()), " ", 3)
		var val interface{} = v[2]
		typ := "q"
		switch v[2] {
		case "on":
			typ, val = "t", true
		case "off":
			typ, val = "t", false
		default:
			if i, err := strconv.Atoi(v[2]); err == nil {
				typ, val = "d", i
			}
		}
		fmt.Fprintf(w, "\t\t%q: %"+typ+",\n", v[1], val)
	}
	fmt.Fprintln(w, "\t}")
	fmt.Fprintln(w, "}")
	if err := s.Err(); err != nil && !errors.Is(err, io.EOF) {
		return err
	}
	return nil
}

func loadKeymaps(w io.Writer) error {
	fmt.Fprintln(w, "// DefaultBinds are the default readline bind keymaps.")
	fmt.Fprintln(w, "//")
	fmt.Fprintln(w, "// see: INPUTRC=/dev/null bash -c 'bind -pm <keymap>'")
	fmt.Fprintln(w, "func DefaultBinds() map[string]map[string]Bind {")
	fmt.Fprintln(w, "\treturn map[string]map[string]Bind {")
	for _, keymap := range []string{
		"emacs", "emacs-standard", "emacs-meta", "emacs-ctlx",
		"vi", "vi-move", "vi-command", "vi-insert",
	} {
		fmt.Fprintf(w, "\t\t%q: map[string]Bind {\n", keymap)
		s, err := load("bash", "-c", "bind -pm "+keymap)
		if err != nil {
			return fmt.Errorf("unable to load keymap %s: %w", keymap, err)
		}
		m := make(map[string]bool)
		for s.Scan() {
			line := strings.TrimSpace(s.Text())
			if strings.HasPrefix(line, "# ") {
				fmt.Fprintln(w, "\t\t\t// "+strings.TrimPrefix(line, "# "))
				continue
			}
			v := strings.SplitN(line, `": `, 2)
			z := strings.TrimPrefix(v[0], `"`)
			switch b := []byte(z); {
			case !utf8.Valid(b):
				z = `string([]byte{` + encode(b) + `})`
			case z == "`":
				z = "\"`\""
			default:
				z = "`" + z + "`"
			}
			if !m[z] {
				fmt.Fprintf(w, "\t\t\tUnescape(%s): Bind{%q, false},\n", z, v[1])
			} else {
				fmt.Fprintf(w, "\t\t\t// Unescape(%s): Bind{%q, false}, // DUPLICATE KEY\n", z, v[1])
			}
			m[z] = true
		}
		if err := s.Err(); err != nil && !errors.Is(err, io.EOF) {
			return fmt.Errorf("unable to read keymap %s: %w", keymap, err)
		}
		fmt.Fprintln(w, "\t\t},")
	}
	fmt.Fprintln(w, "\t}")
	fmt.Fprintln(w, "}")
	return nil
}

func load(name string, params ...string) (*bufio.Scanner, error) {
	buf := new(bytes.Buffer)
	cmd := exec.Command(name, params...)
	cmd.Env = []string{"INPUTRC=/dev/null"}
	cmd.Stdout = buf
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return nil, err
	}
	return bufio.NewScanner(bytes.NewReader(bytes.TrimSpace(buf.Bytes()))), nil
}

func encode(b []byte) string {
	var s []string
	for _, c := range b {
		s = append(s, fmt.Sprintf("0x%x", c))
	}
	return strings.Join(s, ", ")
}

const hdr = `package inputrc

// Generated by gen.go. DO NOT EDIT.

`
