// Copyright 2016 The G3N Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package window

import (
	"github.com/g3n/engine/core"
	"github.com/go-gl/glfw/v3.2/glfw"
)

type GLFW struct {
	core.Dispatcher
	win             *glfw.Window
	keyEv           KeyEvent
	charEv          CharEvent
	mouseEv         MouseEvent
	posEv           PosEvent
	sizeEv          SizeEvent
	cursorEv        CursorEvent
	scrollEv        ScrollEvent
	arrowCursor     *glfw.Cursor
	ibeamCursor     *glfw.Cursor
	crosshairCursor *glfw.Cursor
	handCursor      *glfw.Cursor
	hresizeCursor   *glfw.Cursor
	vresizeCursor   *glfw.Cursor
}

// Global GLFW initialization flag
// is initialized when the first window is created
var initialized bool = false

func newGLFW(width, height int, title string, full bool) (*GLFW, error) {

	// Initialize GLFW once before the first window is created
	if !initialized {
		err := glfw.Init()
		if err != nil {
			return nil, err
		}
		// Sets window hints
		glfw.WindowHint(glfw.ContextVersionMajor, 3)
		glfw.WindowHint(glfw.ContextVersionMinor, 3)
		glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
		glfw.WindowHint(glfw.Samples, 8)
		initialized = true
	}

	// Creates window and sets it as the current context
	win, err := glfw.CreateWindow(width, height, title, nil, nil)
	if err != nil {
		return nil, err
	}
	win.MakeContextCurrent()

	// Create wrapper window with dispacher
	w := new(GLFW)
	w.win = win
	w.Dispatcher.Initialize()

	// Set key callback to dispatch event
	win.SetKeyCallback(func(x *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {

		w.keyEv.W = w
		w.keyEv.Keycode = Key(key)
		w.keyEv.Scancode = scancode
		w.keyEv.Action = Action(action)
		w.keyEv.Mods = ModifierKey(mods)
		if action == glfw.Press {
			w.Dispatch(OnKeyDown, &w.keyEv)
			return
		}
		if action == glfw.Release {
			w.Dispatch(OnKeyUp, &w.keyEv)
			return
		}
	})

	// Set char callback
	win.SetCharModsCallback(func(x *glfw.Window, char rune, mods glfw.ModifierKey) {

		w.charEv.W = w
		w.charEv.Char = char
		w.charEv.Mods = ModifierKey(mods)
		w.Dispatch(OnChar, &w.charEv)
	})

	// Set mouse button callback to dispatch event
	win.SetMouseButtonCallback(func(x *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {

		xpos, ypos := x.GetCursorPos()
		w.mouseEv.W = w
		w.mouseEv.Button = MouseButton(button)
		w.mouseEv.Action = Action(action)
		w.mouseEv.Mods = ModifierKey(mods)
		w.mouseEv.Xpos = float32(xpos)
		w.mouseEv.Ypos = float32(ypos)
		if action == glfw.Press {
			w.Dispatch(OnMouseDown, &w.mouseEv)
			return
		}
		if action == glfw.Release {
			w.Dispatch(OnMouseUp, &w.mouseEv)
			return
		}
	})

	// Set window size callback to dispatch event
	win.SetSizeCallback(func(x *glfw.Window, width int, height int) {

		w.sizeEv.W = w
		w.sizeEv.Width = width
		w.sizeEv.Height = height
		w.Dispatch(OnWindowSize, &w.sizeEv)
	})

	// Set window position event callback to dispatch event
	win.SetPosCallback(func(x *glfw.Window, xpos int, ypos int) {

		w.posEv.W = w
		w.posEv.Xpos = xpos
		w.posEv.Ypos = ypos
		w.Dispatch(OnWindowPos, &w.posEv)
	})

	// Set window cursor position event callback to dispatch event
	win.SetCursorPosCallback(func(x *glfw.Window, xpos float64, ypos float64) {

		w.cursorEv.W = w
		w.cursorEv.Xpos = float32(xpos)
		w.cursorEv.Ypos = float32(ypos)
		w.Dispatch(OnCursor, &w.cursorEv)
	})

	// Set mouse wheel scroll event callback to dispatch event
	win.SetScrollCallback(func(x *glfw.Window, xoff float64, yoff float64) {

		w.scrollEv.W = w
		w.scrollEv.Xoffset = float32(xoff)
		w.scrollEv.Yoffset = float32(yoff)
		w.Dispatch(OnScroll, &w.scrollEv)
	})

	// Preallocate standard cursors
	w.arrowCursor = glfw.CreateStandardCursor(int(glfw.ArrowCursor))
	w.ibeamCursor = glfw.CreateStandardCursor(int(glfw.IBeamCursor))
	w.crosshairCursor = glfw.CreateStandardCursor(int(glfw.CrosshairCursor))
	w.handCursor = glfw.CreateStandardCursor(int(glfw.HandCursor))
	w.hresizeCursor = glfw.CreateStandardCursor(int(glfw.HResizeCursor))
	w.vresizeCursor = glfw.CreateStandardCursor(int(glfw.VResizeCursor))

	return w, nil
}

func (w *GLFW) SwapInterval(interval int) {

	glfw.SwapInterval(interval)
}

func (w *GLFW) MakeContextCurrent() {

	w.win.MakeContextCurrent()
}

func (w *GLFW) GetSize() (width int, height int) {

	return w.win.GetSize()
}

func (w *GLFW) SetSize(width int, height int) {

	w.win.SetSize(width, height)
}

func (w *GLFW) GetPos() (xpos, ypos int) {

	return w.win.GetPos()
}

func (w *GLFW) SetPos(xpos, ypos int) {

	w.win.SetPos(xpos, ypos)
}

func (w *GLFW) SetTitle(title string) {

	w.win.SetTitle(title)
}

func (w *GLFW) SetStandardCursor(cursor StandardCursor) {

	switch cursor {
	case ArrowCursor:
		w.win.SetCursor(w.arrowCursor)
	case IBeamCursor:
		w.win.SetCursor(w.ibeamCursor)
	case CrosshairCursor:
		w.win.SetCursor(w.crosshairCursor)
	case HandCursor:
		w.win.SetCursor(w.handCursor)
	case HResizeCursor:
		w.win.SetCursor(w.hresizeCursor)
	case VResizeCursor:
		w.win.SetCursor(w.vresizeCursor)
	default:
		panic("Invalid cursor")
	}
}

func (w *GLFW) ShouldClose() bool {

	return w.win.ShouldClose()
}

func (w *GLFW) SetShouldClose(v bool) {

	w.win.SetShouldClose(v)
}

func (w *GLFW) SwapBuffers() {

	w.win.SwapBuffers()
}

func (w *GLFW) PollEvents() {

	glfw.PollEvents()
}

func (w *GLFW) GetTime() float64 {

	return glfw.GetTime()
}
