// 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/renderer"
	"github.com/g3n/engine/window"
	"syscall/js"
	"time"
)

// Default canvas id
const canvasId = "g3n-canvas"

// Application
type Application struct {
	window.IWindow                    // Embedded WebGLCanvas
	keyState       *window.KeyState   // Keep track of keyboard state
	renderer       *renderer.Renderer // Renderer object
	frameStart     time.Time          // Frame start time
	frameDelta     time.Duration      // Duration of last frame
	exit           bool
	cbid           js.Value
}

// 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(canvasId)
	if err != nil {
		panic(err)
	}
	app.IWindow = window.Get()
	// TODO audio setup here
	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)) {

	// Create channel so later we can prevent application from finishing while we wait for callbacks
	done := make(chan bool, 0)

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

	// Set up recurring calls to user's update function
	var tick js.Func
	tick = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// 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)
		// Set up new callback if not exiting
		if !app.exit {
			app.cbid = js.Global().Call("requestAnimationFrame", tick)
		} else {
			app.Dispatch(OnExit, nil)
			done <- true // Write to done channel to exit the app
		}
		return nil
	})
	app.cbid = js.Global().Call("requestAnimationFrame", tick)

	// Read from done channel
	// This channel will be empty (except when we want to exit the app)
	// It keeps the app from finishing while we wait for the next call to tick()
	<-done

	// Destroy the window
	app.IWindow.Destroy()
}

// Exit exits the app.
func (app *Application) Exit() {

	app.exit = 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
}
