package main

import (
	"log"
	"path"
	"sort"
	"strings"
	"sync"
	"time"

	"fyne.io/systray"
)

type iconVariant struct {
	name     string
	template bool
}

var (
	animMu   sync.Mutex
	animStop chan struct{}

	iconMu   sync.RWMutex
	iconMode = defaultIconMode()

	alertMu     sync.RWMutex
	currentIcon = "idle"
	alertIcon   []byte
)

func normalizeIconMode(mode string) string {
	mode = strings.ToLower(strings.TrimSpace(mode))
	switch mode {
	case "color", "mono", "white", "black":
		return mode
	default:
		return defaultIconMode()
	}
}

func applyIconMode(mode string) bool {
	mode = normalizeIconMode(mode)

	iconMu.Lock()
	changed := iconMode != mode
	iconMode = mode
	iconMu.Unlock()

	if mode == "mono" {
		startIconThemeWatcher()
	} else {
		stopIconThemeWatcher()
	}
	if changed || len(currentAlertIcon()) == 0 {
		initAlertIcon()
	}
	return changed
}

func currentIconMode() string {
	iconMu.RLock()
	defer iconMu.RUnlock()
	return iconMode
}

func cancelAnimation() {
	animMu.Lock()
	defer animMu.Unlock()
	if animStop != nil {
		close(animStop)
		animStop = nil
	}
}

func playAnimation(name string) {
	cancelAnimation()
	animMu.Lock()
	stop := make(chan struct{})
	animStop = stop
	animMu.Unlock()
	defer clearAnimation(stop)

	frames, template := loadIconFrames(name)
	for _, frame := range frames {
		select {
		case <-stop:
			return
		default:
			setIcon(frame, template)
			time.Sleep(50 * time.Millisecond)
		}
	}
}

func clearAnimation(stop chan struct{}) {
	animMu.Lock()
	defer animMu.Unlock()
	if animStop == stop {
		animStop = nil
	}
}

func loadIcons(prefix string) [][]byte {
	entries, err := iconFS.ReadDir("icons/generated")
	if err != nil {
		log.Printf("icons: read embedded directory: %v", err)
		return nil
	}

	var names []string
	for _, e := range entries {
		if strings.HasPrefix(e.Name(), prefix) {
			names = append(names, e.Name())
		}
	}
	sort.Strings(names)

	frames := make([][]byte, 0, len(names))
	for _, n := range names {
		frame, err := iconFS.ReadFile(path.Join("icons/generated", n))
		if err != nil {
			log.Printf("icons: read embedded icon %s: %v", n, err)
			continue
		}
		frames = append(frames, frame)
	}
	return frames
}

func loadIconFrames(name string) ([][]byte, bool) {
	variant := platformIconVariant(currentIconMode())
	frames := loadIcons(variant.name + "-" + name + "-")
	if len(frames) == 0 && variant.name != "color" {
		frames = loadIcons("color-" + name + "-")
		variant.template = false
	}
	return frames, variant.template
}

func setIcon(icon []byte, template bool) {
	if template {
		systray.SetTemplateIcon(icon, icon)
		return
	}
	systray.SetIcon(icon)
}

func setIconAnimated(name string) {
	alertMu.Lock()
	currentIcon = name
	alertMu.Unlock()
	go playAnimation(name)
}

func refreshIcon() {
	alertMu.RLock()
	name := currentIcon
	alertMu.RUnlock()
	setIconAnimated(name)
}

func initAlertIcon() {
	frames, _ := loadIconFrames("fail")
	if len(frames) == 0 {
		return
	}

	alertMu.Lock()
	alertIcon = frames[len(frames)-1]
	alertMu.Unlock()
}

func currentAlertIcon() []byte {
	alertMu.RLock()
	defer alertMu.RUnlock()
	return alertIcon
}
