package main

import (
	"fmt"
	"os/exec"
	"reflect"
	"strings"
	"time"

	evdev "github.com/gvalkov/golang-evdev"
)

// Mapper receives events from the Shuttle devices, and maps (through
// configuration) to the Virtual Keyboard events.
type Mapper struct {
	inputDevice *evdev.InputDevice
	state       buttonsState
	watcher     *watcher
}

type buttonsState struct {
	jog         int
	shuttle     int
	buttonsHeld map[int]bool
	lastJog     time.Time
}

func NewMapper(inputDevice *evdev.InputDevice) *Mapper {
	m := &Mapper{
		inputDevice: inputDevice,
	}
	m.state.buttonsHeld = make(map[int]bool)
	m.state.jog = -1
	return m
}

func (m *Mapper) Process() error {
	evs, err := m.inputDevice.Read()
	if err != nil {
		return err
	}

	m.dispatch(evs)

	return nil
}

func (m *Mapper) dispatch(evs []evdev.InputEvent) {
	newJogVal := jogVal(evs)
	if m.state.jog != newJogVal {
		if m.state.jog != -1 {
			if m.state.lastJog.IsZero() {
				m.state.lastJog = time.Now()
			}

			slow := ""
			if time.Since(m.state.lastJog) > slowJogTiming() {
				slow = "Slow"
			}
			// Trigger JL or JR if we're advancing or not..
			delta := newJogVal - m.state.jog
			if (delta > 0 || delta < -200) && (delta < 200) {
				if err := m.EmitOther(slow + "JogR"); err != nil {
					fmt.Println("Jog right:", err)
				}
			} else {
				if err := m.EmitOther(slow + "JogL"); err != nil {
					fmt.Println("Jog left:", err)
				}
			}

			m.state.lastJog = time.Now()
		}
		m.state.jog = newJogVal
	}

	newShuttleVal := shuttleVal(evs)
	if m.state.shuttle != newShuttleVal {
		keyName := fmt.Sprintf("S%d", newShuttleVal)
		fmt.Println("SHUTTLE", keyName)
		if err := m.EmitOther(keyName); err != nil {
			fmt.Println("Shuttle movement %q: %s\n", keyName, err)
		}
		m.state.shuttle = newShuttleVal
	}

	for _, ev := range evs {
		if ev.Type != 1 {
			continue
		}

		heldButtons, lastDown := buttonVals(m.state.buttonsHeld, ev)
		if lastDown != 0 {
			modifiers := buttonsToModifiers(heldButtons, lastDown)
			if err := m.EmitKeys(modifiers, lastDown); err != nil {
				fmt.Println("Button press:", err)
			}
			// fmt.Printf("OUTPUT: Modifiers: %v,  Just pressed: %d\n", modifiers, lastDown)
		}
		m.state.buttonsHeld = heldButtons
	}

	fmt.Println("---")
	for _, ev := range evs {
		fmt.Printf("TYPE: %d\tCODE: %d\tVALUE: %d\n", ev.Type, ev.Code, ev.Value)
	}

	// TODO: Lock on configuration changes

	return
}

func slowJogTiming() time.Duration {
	conf := currentConfiguration
	if conf == nil {
		return 200 * time.Millisecond
	}
	slowJog := 200
	if conf.SlowJog != nil {
		slowJog = *conf.SlowJog
	}

	return time.Duration(slowJog) * time.Millisecond
}

func (m *Mapper) EmitOther(key string) error {
	conf := currentConfiguration
	if conf == nil {
		return fmt.Errorf("No configuration for this Window")
	}

	upperKey := strings.ToUpper(key)

	fmt.Println("EmitOther:", key)

	for _, binding := range conf.bindings {
		if binding.otherKey == upperKey {
			return m.executeBinding(binding)
		}
	}

	return fmt.Errorf("No bindings for those movements")
}

func (m *Mapper) EmitKeys(modifiers map[int]bool, keyDown int) error {
	conf := currentConfiguration
	if conf == nil {
		return fmt.Errorf("No configuration for this Window")
	}

	fmt.Println("Emit Keys", modifiers, reverseShuttleKeys[keyDown])

	for _, binding := range conf.bindings {
		if reflect.DeepEqual(binding.heldButtons, modifiers) && binding.buttonDown == keyDown {
			return m.executeBinding(binding)
		}
	}

	return fmt.Errorf("No binding for these keys")
}

func (m *Mapper) executeBinding(binding *deviceBinding) error {
	time.Sleep(25 * time.Millisecond)

	// cookie := xtest.FakeInputChecked(m.watcher.conn, 2, 0x7b00, 0, m.watcher.lastWindowID, 0, 0, 0x00)
	// if err := cookie.Check(); err != nil {
	// 	return nil
	// }

	// cookie = xtest.FakeInputChecked(m.watcher.conn, 3, 0x7b00, 0, m.watcher.lastWindowID, 0, 0, 0x00)
	// return cookie.Check()

	fmt.Println("xdotool key --clearmodifiers", binding.original)
	return exec.Command("xdotool", "key", "--clearmodifiers", binding.original).Run()

	// holdButtons := binding.holdButtons
	// pressButton := binding.pressButton

	// fmt.Println("Executing bindings:", holdButtons, pressButton)

	// time.Sleep(10 * time.Millisecond)

	// for _, button := range holdButtons {
	// 	fmt.Println("Key down", button)
	// 	time.Sleep(10 * time.Millisecond)

	// 	if err := m.virtualKeyboard.KeyDown(keyboardKeysUpper[button]); err != nil {
	// 		return err
	// 	}
	// }

	// time.Sleep(10 * time.Millisecond)

	// fmt.Println("Key press", pressButton)
	// if err := m.virtualKeyboard.KeyDown(keyboardKeysUpper[pressButton]); err != nil {
	// 	return err
	// }

	// time.Sleep(10 * time.Millisecond)

	// if err := m.virtualKeyboard.KeyUp(keyboardKeysUpper[pressButton]); err != nil {
	// 	return err
	// }

	// time.Sleep(10 * time.Millisecond)

	// for _, button := range holdButtons {
	// 	fmt.Println("Key up", button)
	// 	time.Sleep(10 * time.Millisecond)
	// 	if err := m.virtualKeyboard.KeyUp(keyboardKeysUpper[button]); err != nil {
	// 		return err
	// 	}
	// }

	// time.Sleep(50 * time.Millisecond)

	// return nil
}

func jogVal(evs []evdev.InputEvent) int {
	for _, ev := range evs {
		if ev.Type == 2 && ev.Code == 7 {
			return int(ev.Value)
		}
	}
	return 0
}

func shuttleVal(evs []evdev.InputEvent) (out int) {
	for idx, ev := range evs {
		if ev.Type == 0 && idx != len(evs)-1 {
			out = 0
		}
		if ev.Type == 2 && ev.Code == 8 {
			out = int(ev.Value)
		}
	}
	return
}

func buttonVals(current map[int]bool, ev evdev.InputEvent) (out map[int]bool, lastDown int) {
	out = current

	if ev.Value == 1 {
		current[int(ev.Code)] = true
	} else {
		delete(current, int(ev.Code))
	}

	if ev.Value == 1 {
		lastDown = int(ev.Code)
	}

	return
}

func buttonsToModifiers(held map[int]bool, buttonDown int) (out map[int]bool) {
	out = make(map[int]bool)
	for k := range held {
		if k == buttonDown {
			continue
		}
		out[k] = true
	}
	return
}
