package d2screen

import (
	"log"

	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"

	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)

// Screen is an exported interface
type Screen interface{}

// ScreenLoadHandler is an exported interface
type ScreenLoadHandler interface {
	// OnLoad performs all necessary loading to prepare a screen to be shown such as loading assets, placing and binding
	// of ui elements, etc. This loading is done asynchronously. The provided channel will allow implementations to
	// provide progress via Error, Progress, or Done
	OnLoad(loading LoadingState)
}

// ScreenUnloadHandler is an exported interface
type ScreenUnloadHandler interface {
	OnUnload() error
}

// ScreenRenderHandler is an exported interface
type ScreenRenderHandler interface {
	Render(target d2interface.Surface) error
}

// ScreenAdvanceHandler is an exported interface
type ScreenAdvanceHandler interface {
	Advance(elapsed float64) error
}

var singleton struct {
	nextScreen    Screen
	loadingScreen Screen
	loadingState  LoadingState
	currentScreen Screen
}

// SetNextScreen is about to set a given screen as next
func SetNextScreen(screen Screen) {
	singleton.nextScreen = screen
}

// Advance updates the UI on every frame
func Advance(elapsed float64) error {
	switch {
	case singleton.loadingScreen != nil:
		// this call blocks execution and could lead to deadlock if a screen implements OnLoad incorreclty
		load, ok := <-singleton.loadingState.updates
		if !ok {
			log.Println("loadingState chan should not be closed while in a loading screen")
		}
		if load.err != nil {
			log.Printf("PROBLEM LOADING THE SCREEN: %v", load.err)
			return load.err
		}
		d2gui.ShowLoadScreen(load.progress)
		if load.done {
			singleton.currentScreen = singleton.loadingScreen
			singleton.loadingScreen = nil
			d2gui.ShowCursor()
			d2gui.HideLoadScreen()
		}
	case singleton.nextScreen != nil:
		if handler, ok := singleton.currentScreen.(ScreenUnloadHandler); ok {
			if err := handler.OnUnload(); err != nil {
				return err
			}
		}

		d2ui.Reset()
		d2gui.SetLayout(nil)

		if handler, ok := singleton.nextScreen.(ScreenLoadHandler); ok {
			d2gui.ShowLoadScreen(0)
			d2gui.HideCursor()
			singleton.loadingState = LoadingState{updates: make(chan loadingUpdate)}
			go func() {
				handler.OnLoad(singleton.loadingState)
				singleton.loadingState.Done()
			}()
			singleton.currentScreen = nil
			singleton.loadingScreen = singleton.nextScreen
		} else {
			singleton.currentScreen = singleton.nextScreen
			singleton.loadingScreen = nil
		}
		singleton.nextScreen = nil
	case singleton.currentScreen != nil:
		if handler, ok := singleton.currentScreen.(ScreenAdvanceHandler); ok {
			if err := handler.Advance(elapsed); err != nil {
				return err
			}
		}
	}

	return nil
}

// Render renders the UI by a given surface
func Render(surface d2interface.Surface) error {
	if handler, ok := singleton.currentScreen.(ScreenRenderHandler); ok {
		if err := handler.Render(surface); err != nil {
			return err
		}
	}

	return nil
}

// LoadingState represents the loading state
type LoadingState struct {
	updates chan loadingUpdate
}

type loadingUpdate struct {
	progress float64
	err      error
	done     bool
}

// Error provides a way for callers to report an error during loading.
func (l *LoadingState) Error(err error) {
	l.updates <- loadingUpdate{err: err}
}

// Progress provides a way for callers to report the ratio between `0` and `1` of the progress made loading a screen.
func (l *LoadingState) Progress(ratio float64) {
	l.updates <- loadingUpdate{progress: ratio}
}

// Done provides a way for callers to report that screen loading has been completed.
func (l *LoadingState) Done() {
	l.updates <- loadingUpdate{progress: 1.0}
	l.updates <- loadingUpdate{done: true}
}
