// 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.

// +build !wasm

package app

import (
	"fmt"
	"github.com/g3n/engine/audio/al"
	"github.com/g3n/engine/audio/vorbis"
	"github.com/g3n/engine/renderer"
	"github.com/g3n/engine/window"
	"time"
)

// Desktop application defaults
const (
	title  = "G3N Application"
	width  = 800
	height = 600
)

// Application
type Application struct {
	window.IWindow                    // Embedded GlfwWindow
	keyState       *window.KeyState   // Keep track of keyboard state
	renderer       *renderer.Renderer // Renderer object
	audioDev       *al.Device         // Default audio device
	frameStart     time.Time          // Frame start time
	frameDelta     time.Duration      // Duration of last frame
}

// App returns the Application singleton, creating it the first time.
func App() *Application {

	// Return singleton if already created
	if app != nil {
		return app
	}
	app = new(Application)
	// Initialize window
	err := window.Init(width, height, title)
	if err != nil {
		panic(err)
	}
	app.IWindow = window.Get()
	app.openDefaultAudioDevice()           // Set up audio
	app.keyState = window.NewKeyState(app) // Create KeyState
	// Create renderer and add default shaders
	app.renderer = renderer.NewRenderer(app.Gls())
	err = app.renderer.AddDefaultShaders()
	if err != nil {
		panic(fmt.Errorf("AddDefaultShaders:%v", err))
	}
	return app
}

// Run starts the update loop.
// It calls the user-provided update function every frame.
func (app *Application) Run(update func(renderer *renderer.Renderer, deltaTime time.Duration)) {

	// Initialize frame time
	app.frameStart = time.Now()

	// Set up recurring calls to user's update function
	for true {
		// If Exit() was called or there was an attempt to close the window dispatch OnExit event for subscribers.
		// If no subscriber cancelled the event, terminate the application.
		if app.IWindow.(*window.GlfwWindow).ShouldClose() {
			app.Dispatch(OnExit, nil)
			// TODO allow for cancelling exit e.g. showing dialog asking the user if he/she wants to save changes
			// if exit was cancelled {
			//     app.IWindow.(*window.GlfwWindow).SetShouldClose(false)
			// } else {
			break
			// }
		}
		// Update frame start and frame delta
		now := time.Now()
		app.frameDelta = now.Sub(app.frameStart)
		app.frameStart = now
		// Call user's update function
		update(app.renderer, app.frameDelta)
		// Swap buffers and poll events
		app.IWindow.(*window.GlfwWindow).SwapBuffers()
		app.IWindow.(*window.GlfwWindow).PollEvents()
	}

	// Close default audio device
	if app.audioDev != nil {
		al.CloseDevice(app.audioDev)
	}
	// Destroy window
	app.Destroy()
}

// Exit requests to terminate the application
// Application will dispatch OnQuit events to registered subscribers which
// can cancel the process by calling CancelDispatch().
func (app *Application) Exit() {

	app.IWindow.(*window.GlfwWindow).SetShouldClose(true)
}

// Renderer returns the application's renderer.
func (app *Application) Renderer() *renderer.Renderer {

	return app.renderer
}

// KeyState returns the application's KeyState.
func (app *Application) KeyState() *window.KeyState {

	return app.keyState
}

// openDefaultAudioDevice opens the default audio device setting it to the current context
func (app *Application) openDefaultAudioDevice() error {

	// Opens default audio device
	var err error
	app.audioDev, err = al.OpenDevice("")
	if err != nil {
		return fmt.Errorf("opening OpenAL default device: %s", err)
	}
	// Check for OpenAL effects extension support
	var attribs []int
	if al.IsExtensionPresent("ALC_EXT_EFX") {
		attribs = []int{al.MAX_AUXILIARY_SENDS, 4}
	}
	// Create audio context
	acx, err := al.CreateContext(app.audioDev, attribs)
	if err != nil {
		return fmt.Errorf("creating OpenAL context: %s", err)
	}
	// Makes the context the current one
	err = al.MakeContextCurrent(acx)
	if err != nil {
		return fmt.Errorf("setting OpenAL context current: %s", err)
	}
	// Logs audio library versions
	fmt.Println("LOGGING")
	log.Info("%s version: %s", al.GetString(al.Vendor), al.GetString(al.Version))
	log.Info("%s", vorbis.VersionString())
	return nil
}
