package ui

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime/pprof"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/ebitenutil"
	"github.com/hajimehoshi/ebiten/inpututil"
)

var (
	screenScale = flag.Float64("screen-scale", 1.0, "Scale the window by this factor")

	winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension")
	winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension")

	cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
)

type Window struct {
	Title             string
	KeyUpHandlers     map[ebiten.Key]func()
	MouseWheelHandler func(float64, float64)

	// User-provided update actions
	updateFn func() error
	drawFn   func(*ebiten.Image) error

	debug    bool
	firstRun bool
}

// 0,0 is the *top left* of the window
//
// ebiten assumes a single window, so only call this once...
func NewWindow(title string) (*Window, error) {
	ebiten.SetRunnableInBackground(true)

	return &Window{
		Title:         title,
		KeyUpHandlers: make(map[ebiten.Key]func()),
		debug:         true,
		firstRun:      true,
	}, nil
}

// TODO: multiple handlers for the same key?
func (w *Window) OnKeyUp(key ebiten.Key, f func()) {
	w.KeyUpHandlers[key] = f
}

func (w *Window) OnMouseWheel(f func(x, y float64)) {
	w.MouseWheelHandler = f
}

func (w *Window) run(screen *ebiten.Image) error {
	if w.firstRun {
		ebiten.SetScreenScale(*screenScale)
		w.firstRun = false
	}

	if err := w.updateFn(); err != nil {
		return err
	}

	// Process keys
	// TODO: efficient set operations

	for key, cb := range w.KeyUpHandlers {
		if inpututil.IsKeyJustReleased(key) {
			cb()
		}
	}

	if w.MouseWheelHandler != nil {
		x, y := ebiten.Wheel()
		if x != 0 || y != 0 {
			w.MouseWheelHandler(x, y)
		}
	}

	if !ebiten.IsDrawingSkipped() {
		if err := w.drawFn(screen); err != nil {
			return err
		}

		if w.debug {
			// Draw FPS, etc, to the screen
			msg := fmt.Sprintf("tps=%0.2f fps=%0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS())
			ebitenutil.DebugPrint(screen, msg)
		}
	}

	return nil
}

// TODO: a stop or other cancellation mechanism
//
// Note that this must be called on the main OS thread
func (w *Window) Run(updateFn func() error, drawFn func(*ebiten.Image) error) error {
	w.updateFn = updateFn
	w.drawFn = drawFn

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal("could not create CPU profile: ", err)
		}
		defer f.Close() // error handling omitted for example
		if err := pprof.StartCPUProfile(f); err != nil {
			log.Fatal("could not start CPU profile: ", err)
		}
		defer pprof.StopCPUProfile()
	}

	return ebiten.Run(w.run, *winX, *winY, 1, w.Title) // Native game resolution: 640x480
}
