// package ordoor implements the full WH40K.EXE functionality, and is used from
// cmd/ordoor/main.go
//
// Entrypoint is Run()
package ordoor

import (
	"fmt"
	"log"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/audio"

	"code.ur.gs/lupine/ordoor/internal/assetstore"
	"code.ur.gs/lupine/ordoor/internal/config"
	"code.ur.gs/lupine/ordoor/internal/flow"
	"code.ur.gs/lupine/ordoor/internal/ship"
	"code.ur.gs/lupine/ordoor/internal/ui"
)

type gameState int

type Ordoor struct {
	assets *assetstore.AssetStore
	config *config.Config
	music  *audio.Player
	win    *ui.Window

	// Relevant to interface state
	flow *flow.Flow

	// Relevant to campaign state
	ship *ship.Ship
}

func Run(configFile string, overrideX, overrideY int) error {
	cfg, err := config.Load(configFile)
	if err != nil {
		return fmt.Errorf("Couldn't load config file: %v", err)
	}

	assets, err := assetstore.New(cfg.Ordoor.DataDir)
	if err != nil {
		return fmt.Errorf("Failed to initialize asset store: %v", err)
	}

	defaults, err := assets.DefaultOptions()
	if err != nil {
		return fmt.Errorf("Failed to read option defaults: %v", err)
	}

	cfg.Defaults = defaults
	if cfg.HasUnsetOptions() {
		if err := cfg.ResetDefaults(); err != nil {
			return fmt.Errorf("Failed to set options on first-start: %v", err)
		}
	}

	if _, err := audio.NewContext(48000); err != nil {
		return fmt.Errorf("Failed to set up audio context: %v", err)
	}

	ordoor := &Ordoor{
		assets: assets,
		config: cfg,
		ship:   ship.New(),
	}

	x, y := cfg.Options.XRes, cfg.Options.YRes
	if overrideX > 0 {
		x = overrideX
	}
	if overrideY > 0 {
		y = overrideY
	}

	win, err := ui.NewWindow(ordoor, "Ordoor", x, y)
	if err != nil {
		return fmt.Errorf("Failed to create window: %v", err)
	}

	ordoor.win = win

	if err := ordoor.setupFlow(); err != nil {
		return fmt.Errorf("failed to setup UI flow: %v", err)
	}

	if err := ordoor.Run(); err != nil {
		return fmt.Errorf("Run finished with error: %v", err)
	}

	return nil
}

func (o *Ordoor) Run() error {
	// FIXME: we're missing a screen about SSI here
	if o.config.Options.PlayMovies {
		o.PlaySkippableVideo("LOGOS")
		o.PlaySkippableVideo("movie1")
	}

	err := o.win.Run()
	if err == flow.ErrExit {
		log.Printf("Exit requested")
		return nil
	}

	return err
}

// Only one music track can play at a time. This is handled at the toplevel.
// FIXME: should take references from Sounds.dat
// FIXME: music probably properly belongs to flow. This package can just do
// initialization and wire the flow to the ship?
func (o *Ordoor) PlayMusic(name string) error {
	if o.music != nil {
		if err := o.music.Close(); err != nil {
			return fmt.Errorf("Failed to close old music: %v", err)
		}
	}

	sound, err := o.assets.Sound(name)
	if err != nil {
		return fmt.Errorf("Failed to find sound %v: %v", name, err)
	}

	player, err := sound.InfinitePlayer()
	if err != nil {
		return fmt.Errorf("Failed to generate music player for %v: %v", name, err)
	}
	o.music = player

	if o.config.Options.PlayMusic {
		player.Play()
	}

	return nil
}

func (o *Ordoor) setupFlow() error {
	o.PlayMusic("music_interface")

	flow, err := flow.New(o.assets, o.config, o.ship)
	if err != nil {
		return err
	}

	o.flow = flow

	return nil
}

func (o *Ordoor) Update(screenX, screenY int) error {
	// Ensure music is doing the right thing
	if o.music != nil && o.music.IsPlaying() != o.config.Options.PlayMusic {
		if o.config.Options.PlayMusic {
			o.music.Rewind()
			o.music.Play()
		} else {
			o.music.Pause()
		}
	}

	return o.flow.Update(screenX, screenY)
}

func (o *Ordoor) Draw(screen *ebiten.Image) error {
	return o.flow.Draw(screen)
}

func (o *Ordoor) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
	return o.flow.Cursor()
}
