package rui

import "strings"

const (
	// KeyDown is the constant for "key-down-event" property tag.
	// The "key-down-event" event is fired when a key is pressed.
	// The main listener format:
	//   func(View, KeyEvent).
	// The additional listener formats:
	//   func(KeyEvent), func(View), and func().
	KeyDownEvent = "key-down-event"

	// KeyPp is the constant for "key-up-event" property tag.
	// The "key-up-event" event is fired when a key is released.
	// The main listener format:
	//   func(View, KeyEvent).
	// The additional listener formats:
	//   func(KeyEvent), func(View), and func().
	KeyUpEvent = "key-up-event"
)

type KeyEvent struct {
	// TimeStamp is the time at which the event was created (in milliseconds).
	// This value is time since epoch—but in reality, browsers' definitions vary.
	TimeStamp uint64

	// Key is the key value of the key represented by the event. If the value has a printed representation,
	// this attribute's value is the same as the char property. Otherwise, it's one of the key value strings
	// specified in Key values. If the key can't be identified, its value is the string "Unidentified".
	Key string

	// Code holds a string that identifies the physical key being pressed. The value is not affected
	// by the current keyboard layout or modifier state, so a particular key will always return the same value.
	Code string

	// Repeat == true if a key has been depressed long enough to trigger key repetition, otherwise false.
	Repeat bool

	// CtrlKey == true if the control key was down when the event was fired. false otherwise.
	CtrlKey bool

	// ShiftKey == true if the shift key was down when the event was fired. false otherwise.
	ShiftKey bool

	// AltKey == true if the alt key was down when the event was fired. false otherwise.
	AltKey bool

	// MetaKey == true if the meta key was down when the event was fired. false otherwise.
	MetaKey bool
}

func valueToKeyListeners(value interface{}) ([]func(View, KeyEvent), bool) {
	if value == nil {
		return nil, true
	}

	switch value := value.(type) {
	case func(View, KeyEvent):
		return []func(View, KeyEvent){value}, true

	case func(KeyEvent):
		fn := func(view View, event KeyEvent) {
			value(event)
		}
		return []func(View, KeyEvent){fn}, true

	case func(View):
		fn := func(view View, event KeyEvent) {
			value(view)
		}
		return []func(View, KeyEvent){fn}, true

	case func():
		fn := func(view View, event KeyEvent) {
			value()
		}
		return []func(View, KeyEvent){fn}, true

	case []func(View, KeyEvent):
		if len(value) == 0 {
			return nil, true
		}
		for _, fn := range value {
			if fn == nil {
				return nil, false
			}
		}
		return value, true

	case []func(KeyEvent):
		count := len(value)
		if count == 0 {
			return nil, true
		}
		listeners := make([]func(View, KeyEvent), count)
		for i, v := range value {
			if v == nil {
				return nil, false
			}
			listeners[i] = func(view View, event KeyEvent) {
				v(event)
			}
		}
		return listeners, true

	case []func(View):
		count := len(value)
		if count == 0 {
			return nil, true
		}
		listeners := make([]func(View, KeyEvent), count)
		for i, v := range value {
			if v == nil {
				return nil, false
			}
			listeners[i] = func(view View, event KeyEvent) {
				v(view)
			}
		}
		return listeners, true

	case []func():
		count := len(value)
		if count == 0 {
			return nil, true
		}
		listeners := make([]func(View, KeyEvent), count)
		for i, v := range value {
			if v == nil {
				return nil, false
			}
			listeners[i] = func(view View, event KeyEvent) {
				v()
			}
		}
		return listeners, true

	case []interface{}:
		count := len(value)
		if count == 0 {
			return nil, true
		}
		listeners := make([]func(View, KeyEvent), count)
		for i, v := range value {
			if v == nil {
				return nil, false
			}
			switch v := v.(type) {
			case func(View, KeyEvent):
				listeners[i] = v

			case func(KeyEvent):
				listeners[i] = func(view View, event KeyEvent) {
					v(event)
				}

			case func(View):
				listeners[i] = func(view View, event KeyEvent) {
					v(view)
				}

			case func():
				listeners[i] = func(view View, event KeyEvent) {
					v()
				}

			default:
				return nil, false
			}
		}
		return listeners, true
	}

	return nil, false
}

var keyEvents = map[string]struct{ jsEvent, jsFunc string }{
	KeyDownEvent: {jsEvent: "onkeydown", jsFunc: "keyDownEvent"},
	KeyUpEvent:   {jsEvent: "onkeyup", jsFunc: "keyUpEvent"},
}

func (view *viewData) setKeyListener(tag string, value interface{}) bool {
	listeners, ok := valueToKeyListeners(value)
	if !ok {
		notCompatibleType(tag, value)
		return false
	}

	if listeners == nil {
		view.removeKeyListener(tag)
	} else if js, ok := keyEvents[tag]; ok {
		view.properties[tag] = listeners
		if view.created {
			updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)", view.Session())
		}
	} else {
		return false
	}
	return true
}

func (view *viewData) removeKeyListener(tag string) {
	delete(view.properties, tag)
	if view.created {
		if js, ok := keyEvents[tag]; ok {
			updateProperty(view.htmlID(), js.jsEvent, "", view.Session())
		}
	}
}

func getKeyListeners(view View, subviewID string, tag string) []func(View, KeyEvent) {
	if subviewID != "" {
		view = ViewByID(view, subviewID)
	}
	if view != nil {
		if value := view.Get(tag); value != nil {
			if result, ok := value.([]func(View, KeyEvent)); ok {
				return result
			}
		}
	}
	return []func(View, KeyEvent){}
}

func keyEventsHtml(view View, buffer *strings.Builder) {
	for tag, js := range keyEvents {
		if listeners := getKeyListeners(view, "", tag); len(listeners) > 0 {
			buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
		}
	}
}

func handleKeyEvents(view View, tag string, data DataObject) {
	listeners := getKeyListeners(view, "", tag)
	if len(listeners) == 0 {
		return
	}

	getBool := func(tag string) bool {
		if value, ok := data.PropertyValue(tag); ok && value == "1" {
			return true
		}
		return false
	}

	key, _ := data.PropertyValue("key")
	code, _ := data.PropertyValue("code")
	event := KeyEvent{
		TimeStamp: getTimeStamp(data),
		Key:       key,
		Code:      code,
		Repeat:    getBool("repeat"),
		CtrlKey:   getBool("ctrlKey"),
		ShiftKey:  getBool("shiftKey"),
		AltKey:    getBool("altKey"),
		MetaKey:   getBool("metaKey"),
	}

	for _, listener := range listeners {
		listener(view, event)
	}
}

// GetKeyDownListeners returns the "key-down-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetKeyDownListeners(view View, subviewID string) []func(View, KeyEvent) {
	return getKeyListeners(view, subviewID, KeyDownEvent)
}

// GetKeyUpListeners returns the "key-up-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetKeyUpListeners(view View, subviewID string) []func(View, KeyEvent) {
	return getKeyListeners(view, subviewID, KeyUpEvent)
}