package gui

import (
	"fmt"
	"image"
	"image/color"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/liamg/darktile/internal/app/darktile/font"
	"github.com/liamg/darktile/internal/app/darktile/hinters"
	"github.com/liamg/darktile/internal/app/darktile/termutil"

	"github.com/hajimehoshi/ebiten/v2"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

type GUI struct {
	mouseStateLeft      MouseState
	mouseStateRight     MouseState
	mouseStateMiddle    MouseState
	mouseDrag           bool
	size                image.Point // pixels
	terminal            *termutil.Terminal
	updateChan          chan struct{}
	lastClick           time.Time
	clickCount          int
	fontManager         *font.Manager
	mousePos            termutil.Position
	hinters             []hinters.Hinter
	activeHinter        int
	popupMessages       []PopupMessage
	screenshotRequested bool
	screenshotFilename  string
	startupFuncs        []func(g *GUI)
	keyState            *keyState
}

type PopupMessage struct {
	Text       string
	Expiry     time.Time
	Foreground color.Color
	Background color.Color
}

type MouseState uint8

const (
	MouseStateNone MouseState = iota
	MouseStatePressed
)

func New(terminal *termutil.Terminal, options ...Option) (*GUI, error) {

	g := &GUI{
		terminal:     terminal,
		size:         image.Point{80, 30},
		updateChan:   make(chan struct{}),
		fontManager:  font.NewManager(),
		activeHinter: -1,
		keyState:     newKeyState(),
	}

	for _, option := range options {
		if err := option(g); err != nil {
			return nil, err
		}
	}

	terminal.SetWindowManipulator(NewManipulator(g))

	return g, nil
}

func (g *GUI) Run() error {

	go func() {
		if err := g.terminal.Run(g.updateChan, uint16(g.size.X), uint16(g.size.Y)); err != nil {
			fmt.Fprintf(os.Stderr, "Fatal error: %s", err)
			os.Exit(1)
		}
		os.Exit(0)
	}()

	ebiten.SetScreenTransparent(true)
	ebiten.SetWindowResizable(true)
	ebiten.SetRunnableOnUnfocused(true)
	ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMinimum)

	for _, f := range g.startupFuncs {
		go f(g)
	}

	go g.watchForUpdate()

	return ebiten.RunGame(g)
}

func (g *GUI) watchForUpdate() {
	for range g.updateChan {
		ebiten.ScheduleFrame()
		go func() {
			for g.keyState.AnythingPressed() {
				time.Sleep(time.Millisecond * 10)
				ebiten.ScheduleFrame()
			}
		}()
	}
}

func (g *GUI) CellSize() image.Point {
	return g.fontManager.CharSize()
}

func (g *GUI) Highlight(start termutil.Position, end termutil.Position, label string, img image.Image) {

	if label == "" && img == nil {
		g.terminal.GetActiveBuffer().Highlight(start, end, nil)
		return
	}

	annotation := &termutil.Annotation{
		Text:  label,
		Image: img,
	}

	if label != "" {
		lines := strings.Split(label, "\n")
		annotation.Height = float64(len(lines))
		for _, line := range lines {
			if float64(len(line)) > annotation.Width {
				annotation.Width = float64(len(line))
			}
		}
	}

	if img != nil {
		annotation.Height += float64(img.Bounds().Dy() / g.fontManager.CharSize().Y)
		if label != "" {
			annotation.Height += 0.5 // half line spacing between image + text
		}
		imgCellWidth := img.Bounds().Dx() / g.fontManager.CharSize().X
		if float64(imgCellWidth) > annotation.Width {
			annotation.Width = float64(imgCellWidth)
		}
	}

	g.terminal.GetActiveBuffer().Highlight(start, end, annotation)
}

func (g *GUI) ClearHighlight() {
	g.terminal.GetActiveBuffer().ClearHighlight()
}
