package pixelgl

import (
	"runtime"

	"github.com/go-gl/gl/v3.3-core/gl"
)

// Due to the limitations of OpenGL and operating systems, all OpenGL related calls must be done from the main thread.

var callQueue = make(chan func(), 32)

func init() {
	runtime.LockOSThread()
}

// Run is essentialy the "main" function of the pixelgl package.
// Run this function from the main function (because that's guaranteed to run in the main thread).
//
// This function reserves the main thread for the OpenGL stuff and runs a supplied run function in a
// separate goroutine.
//
// Run returns when the provided run function finishes.
func Run(run func()) {
	done := make(chan struct{})

	go func() {
		run()
		close(done)
	}()

loop:
	for {
		select {
		case f := <-callQueue:
			f()
		case <-done:
			break loop
		}
	}
}

// Init initializes OpenGL by loading the function pointers from the active OpenGL context.
// This function must be manually run inside the main thread (Do, DoErr, DoVal, etc.).
//
// It must be called under the presence of an active OpenGL context, e.g., always after calling window.MakeContextCurrent().
// Also, always call this function when switching contexts.
func Init() {
	err := gl.Init()
	if err != nil {
		panic(err)
	}
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
}

// DoNoBlock executes a function inside the main OpenGL thread.
// DoNoBlock does not wait until the function finishes.
func DoNoBlock(f func()) {
	callQueue <- f
}

// Do executes a function inside the main OpenGL thread.
// Do blocks until the function finishes.
//
// All OpenGL calls must be done in the dedicated thread.
func Do(f func()) {
	done := make(chan struct{})
	callQueue <- func() {
		f()
		close(done)
	}
	<-done
}

// DoErr executes a function inside the main OpenGL thread and returns an error to the called.
// DoErr blocks until the function finishes.
//
// All OpenGL calls must be done in the dedicated thread.
func DoErr(f func() error) error {
	err := make(chan error)
	callQueue <- func() {
		err <- f()
	}
	return <-err
}

// DoVal executes a function inside the main OpenGL thread and returns a value to the caller.
// DoVal blocks until the function finishes.
//
// All OpenGL calls must be done in the main thread.
func DoVal(f func() interface{}) interface{} {
	val := make(chan interface{})
	callQueue <- func() {
		val <- f()
	}
	return <-val
}