package allg5

/*
#define ALLEGRO_KCM_AUDIO_SRC

#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>

#define USER_EVENT_TYPE ALLEGRO_GET_EVENT_TYPE('u', 's', 'e', 'r')

void init_user_event(ALLEGRO_EVENT* e)
{
	e->user.type = USER_EVENT_TYPE;
}
*/
import "C"

import (
	"errors"
	"unsafe"
)

type EventQueue struct {
	queue *C.ALLEGRO_EVENT_QUEUE
}

type Event interface {
	Stamp() float64
}

type EventBase struct {
	stamp float64
}

func (eb EventBase) Stamp() float64 {
	return eb.stamp
}

type DisplayCloseEvent struct {
	EventBase
}

type DisplayResizeEvent struct {
	EventBase
	X, Y   int
	Width  int
	Height int
}

type DisplayOrientation int

const (
	DisplayOrientation0Degrees DisplayOrientation = iota
	DisplayOrientation90Degrees
	DisplayOrientation180Degrees
	DisplayOrientation270Degrees
	DisplayOrientationFaceUp
	DisplayOrientationFaceDown
)

func toDisplayOrientation(o C.int) DisplayOrientation {
	switch o {
	case C.ALLEGRO_DISPLAY_ORIENTATION_0_DEGREES:
		return DisplayOrientation0Degrees
	case C.ALLEGRO_DISPLAY_ORIENTATION_90_DEGREES:
		return DisplayOrientation90Degrees
	case C.ALLEGRO_DISPLAY_ORIENTATION_180_DEGREES:
		return DisplayOrientation180Degrees
	case C.ALLEGRO_DISPLAY_ORIENTATION_270_DEGREES:
		return DisplayOrientation270Degrees
	case C.ALLEGRO_DISPLAY_ORIENTATION_FACE_UP:
		return DisplayOrientationFaceUp
	case C.ALLEGRO_DISPLAY_ORIENTATION_FACE_DOWN:
		return DisplayOrientationFaceDown
	default:
		panic("not supported")
	}
}

type DisplayOrientationEvent struct {
	EventBase
	Orientation DisplayOrientation
}

type KeyEvent struct {
	EventBase
	KeyCode Key
	Display *Display
}

type KeyCharEvent struct {
	KeyEvent
	UnicodeCharacter rune
	Modifiers        KeyMod
	Repeat           bool
}

type KeyDownEvent struct {
	KeyEvent
}

type KeyUpEvent struct {
	KeyEvent
}

type MouseButtonDownEvent struct {
	MouseEvent
	Button   MouseButton
	Pressure float32
}

type MouseButtonUpEvent struct {
	MouseEvent
	Button   MouseButton
	Pressure float32
}

type MouseEnterEvent struct {
	MouseEvent
}

type MouseEvent struct {
	EventBase
	X, Y    int
	Z, W    int
	Display *Display
}
type MouseLeaveEvent struct {
	MouseEvent
}

type MouseMoveEvent struct {
	MouseEvent
	DeltaX, DeltaY int
	DeltaZ, DeltaW int
	Pressure       float32
}

type RecorderFragmentEvent struct {
	EventBase
	Buffer  unsafe.Pointer
	Samples int
}

type UserEvent struct {
	EventBase
}

type UserEventSource struct {
	source *C.ALLEGRO_EVENT_SOURCE
}

func NewEventQueue() (*EventQueue, error) {
	q := C.al_create_event_queue()
	if q == nil {
		return nil, errors.New("unable to create event queue")
	}
	return &EventQueue{q}, nil
}

func (eq *EventQueue) register(source *C.ALLEGRO_EVENT_SOURCE) {
	C.al_register_event_source(eq.queue, source)
}

func (eq *EventQueue) RegisterDisplay(d *Display) {
	eq.register(C.al_get_display_event_source(d.display))
}

func (eq *EventQueue) RegisterMouse() {
	eq.register(C.al_get_mouse_event_source())
}

func (eq *EventQueue) RegisterKeyboard() {
	eq.register(C.al_get_keyboard_event_source())
}

func (eq *EventQueue) RegisterRecorder(rec *Recorder) {
	eq.register(C.al_get_audio_recorder_event_source(rec.recorder))
}

func (eq *EventQueue) RegisterUserEvents(source *UserEventSource) {
	eq.register(source.source)
}

func (eq *EventQueue) mapEvent(e *C.ALLEGRO_EVENT) Event {
	any := (*C.ALLEGRO_ANY_EVENT)(unsafe.Pointer(e))
	eb := EventBase{float64(any.timestamp)}
	switch any._type {
	case C.ALLEGRO_EVENT_AUDIO_RECORDER_FRAGMENT:
		recorder := (*C.ALLEGRO_AUDIO_RECORDER_EVENT)(unsafe.Pointer(e))
		return &RecorderFragmentEvent{eb, unsafe.Pointer(recorder.buffer), int(recorder.samples)}
	case C.ALLEGRO_EVENT_DISPLAY_CLOSE:
		return &DisplayCloseEvent{eb}
	case C.ALLEGRO_EVENT_DISPLAY_ORIENTATION:
		display := (*C.ALLEGRO_DISPLAY_EVENT)(unsafe.Pointer(e))
		return &DisplayOrientationEvent{eb, toDisplayOrientation(display.orientation)}
	case C.ALLEGRO_EVENT_DISPLAY_RESIZE:
		display := (*C.ALLEGRO_DISPLAY_EVENT)(unsafe.Pointer(e))
		C.al_acknowledge_resize(display.source)
		return &DisplayResizeEvent{eb, int(display.x), int(display.y), int(display.width), int(display.height)}
	case C.ALLEGRO_EVENT_MOUSE_AXES:
		mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
		return &MouseMoveEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, int(mouse.dx), int(mouse.dy), int(mouse.dz), int(mouse.dw), float32(mouse.pressure)}
	case C.ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
		mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
		return &MouseButtonDownEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, MouseButton(mouse.button), float32(mouse.pressure)}
	case C.ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY:
		mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
		return &MouseEnterEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}}
	case C.ALLEGRO_EVENT_MOUSE_BUTTON_UP:
		mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
		return &MouseButtonUpEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, MouseButton(mouse.button), float32(mouse.pressure)}
	case C.ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY:
		mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
		return &MouseLeaveEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}}
	case C.ALLEGRO_EVENT_KEY_DOWN:
		key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e))
		return &KeyDownEvent{KeyEvent{eb, Key(key.keycode), nil}}
	case C.ALLEGRO_EVENT_KEY_UP:
		key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e))
		return &KeyUpEvent{KeyEvent{eb, Key(key.keycode), nil}}
	case C.ALLEGRO_EVENT_KEY_CHAR:
		key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e))
		return &KeyCharEvent{KeyEvent{eb, Key(key.keycode), nil}, rune(key.unichar), KeyMod(key.modifiers), bool(key.repeat)}
	case C.USER_EVENT_TYPE:
		return &UserEvent{eb}
	}
	return nil
}

func (eq *EventQueue) Get() Event {
	var event C.ALLEGRO_EVENT
	if !bool(C.al_get_next_event(eq.queue, &event)) {
		return nil
	}
	return eq.mapEvent(&event)
}

func (eq *EventQueue) GetWait() Event {
	var event C.ALLEGRO_EVENT
	C.al_wait_for_event(eq.queue, &event)
	return eq.mapEvent(&event)
}

func (eq *EventQueue) Destroy() {
	C.al_destroy_event_queue(eq.queue)
}

func NewUserEventSource() *UserEventSource {
	s := (*C.ALLEGRO_EVENT_SOURCE)(C.malloc(C.sizeof_ALLEGRO_EVENT_SOURCE))
	source := &UserEventSource{s}
	C.al_init_user_event_source(s)
	return source
}

func (s *UserEventSource) Destroy() {
	C.al_destroy_user_event_source(s.source)
	C.free(unsafe.Pointer(s.source))
}

func (s *UserEventSource) EmitEvent() bool {
	e := (*C.ALLEGRO_EVENT)(C.malloc(C.sizeof_ALLEGRO_EVENT))
	C.init_user_event(e)
	return bool(C.al_emit_user_event(s.source, e, nil))
}