package flow

import (
	"errors"
	"fmt"

	"github.com/hajimehoshi/ebiten"

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

// type Flow is responsible for wiring up UI elements to each other and ensuring
// they behave as expected. This includes forward / back buttons to switch
// between screens, loading and saving options, launching a scenario, etc
type Flow struct {
	assets  *assetstore.AssetStore
	config  *config.Config
	current *ui.Driver
	drivers map[driverName]*ui.Driver

	// Some screens can be returned to from more than one place. Where this is
	// the case, instead of hardcoding it, we'll store an entry in here so we
	// know where we're going back to
	//
	// FIXME: this really suggests wiring everything up at the start is wrong.
	returns map[driverName]driverName

	exit error
}

type driverName string

const (
	// Names of all the drivers
	main      driverName = "Main"
	levelPly  driverName = "LevelPly"
	singles   driverName = "Singles"
	randomMap driverName = "RandomMap"
	newGame   driverName = "NewGame"
	loadGame  driverName = "LoadGame"
	options   driverName = "Options"
	kbd       driverName = "Keyboard"
	bridge    driverName = "Bridge"
	briefing  driverName = "Briefing"
	choices   driverName = "Choices"
	saveGame  driverName = "SaveGame"
	credits   driverName = "Credits"
	arrange   driverName = "Arrange"

	configureUltEquip      driverName = "Configure_UltEquip"
	configureVehiclesUltra driverName = "Configure_Vehicles_Ultra"
)

var (
	ErrExit = errors.New("exiting gracefully")

	driverNames = []driverName{
		main, levelPly, singles, randomMap, newGame, loadGame, options, kbd,
		bridge, briefing, choices, saveGame, credits, arrange,
		configureUltEquip, configureVehiclesUltra,
	}

	// Constants used for sliders

	h3Slider = map[int]int{1: 8, 2: 56, 3: 110, 4: 120}

	v10Slider = map[int]int{
		0:  0,
		10: 9, 20: 18, 30: 27, 40: 36, 50: 45,
		60: 54, 70: 63, 80: 72, 90: 81, 100: 90,
	}

	h9Slider = map[int]int{
		0:  0,
		10: 10, 20: 20, 30: 30, 40: 40,
		50: 50, 60: 60, 70: 70, 80: 80,
	}
)

func New(assets *assetstore.AssetStore, config *config.Config) (*Flow, error) {
	out := &Flow{
		assets:  assets,
		config:  config,
		drivers: make(map[driverName]*ui.Driver, len(driverNames)),
		returns: make(map[driverName]driverName),
	}

	// Load all the drivers upfront
	for _, name := range driverNames {
		driver, err := buildDriver(assets, name)
		if err != nil {
			return nil, err
		}

		out.drivers[name] = driver
	}

	// Initial load of the config into the options UI
	if err := out.configIntoOptions(); err != nil {
		return nil, err
	}

	out.linkDrivers()
	out.setDriverNow(main)

	return out, nil
}

func buildDriver(assets *assetstore.AssetStore, name driverName) (*ui.Driver, error) {
	menu, err := assets.Menu(string(name))
	if err != nil {
		return nil, err
	}

	driver, err := ui.NewDriver(menu)
	if err != nil {
		return nil, err
	}

	return driver, nil
}

func (f *Flow) Update(screenX, screenY int) error {
	if f.exit != nil {
		return f.exit
	}

	return f.current.Update(screenX, screenY)
}

func (f *Flow) Draw(screen *ebiten.Image) error {
	if f.exit != nil {
		return f.exit
	}

	return f.current.Draw(screen)
}

func (f *Flow) linkDrivers() {
	// linkMain
	f.onClick(main, "2.1", f.setDriver(newGame))                // New game
	f.onClick(main, "2.2", f.setDriver(loadGame))               // Load game
	f.setFreeze(main, "2.3", true)                              // Multiplayer - disable for now
	f.onClick(main, "2.4", f.setReturningDriver(main, options)) // Options
	f.onClick(main, "2.5", f.setExit)                           // Quit

	// Now link immediate children. They will link their children, and so on
	f.linkNewGame()
	f.linkLoadGame()
	// TODO: link multiplayer
	f.linkOptions()
}

func maybeErr(driver driverName, err error) error {
	if err != nil {
		return fmt.Errorf("%v: %v", driver, err)
	}

	return nil
}

func (f *Flow) configureSlider(driver driverName, id string, steps map[int]int) {
	if f.exit != nil {
		return
	}

	f.exit = f.drivers[driver].ConfigureSlider(id, steps)
}

func (f *Flow) onClick(driver driverName, id string, fn func()) {
	if f.exit != nil {
		return
	}

	f.exit = maybeErr(driver, f.drivers[driver].OnClick(id, fn))
}

func (f *Flow) setFreeze(driver driverName, id string, value bool) {
	if f.exit != nil {
		return
	}

	f.exit = maybeErr(driver, f.drivers[driver].SetFreeze(id, value))
}

func (f *Flow) setValueBool(driver driverName, id string, value bool) {
	if f.exit != nil {
		return
	}

	f.exit = f.drivers[driver].SetValueBool(id, value)
}

func (f *Flow) setDriver(name driverName) func() {
	return func() {
		f.setDriverNow(name)
	}
}

func (f *Flow) setDriverNow(name driverName) {
	f.current = f.drivers[name]
}

// from is the parent menu, to is the child
func (f *Flow) setReturningDriver(from, to driverName) func() {
	return func() {
		f.returns[to] = from
		f.setDriverNow(to)
	}
}

// from is the child menu, to is the parent
func (f *Flow) returnToLastDriverNow(from driverName) error {
	to, ok := f.returns[from]
	if !ok {
		return fmt.Errorf("Couldn't work out where to return to from %v", from)
	}

	delete(f.returns, from)

	f.setDriverNow(to)
	return nil
}

func (f *Flow) setExit() {
	f.exit = ErrExit
}
