// Kommando tvwall: Media-Player und Webinterface fuer die TV-Wall.
//
// Der Dienst liest regelmaessig den Medienordner ein, haelt daraus eine
// Playliste aktuell, laesst sie von mpv im Vollbild in Endlosschleife
// abspielen und stellt zum Hochladen und Sortieren ein Webinterface bereit.
package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"ehc/tvwall/internal/config"
	"ehc/tvwall/internal/player"
	"ehc/tvwall/internal/playlist"
	"ehc/tvwall/internal/web"
)

// version wird beim Bauen ueber -ldflags gesetzt.
var version = "dev"

func main() {
	var (
		configPath = flag.String("config", "/etc/tvwall/tvwall.conf", "Pfad zur Konfigurationsdatei")
		addr       = flag.String("addr", "", "Adresse des Webservers (ueberschreibt WEB_PORT)")
		stateDir   = flag.String("state-dir", "", "Datenverzeichnis (ueberschreibt STATE_DIR)")
		mpvSocket  = flag.String("mpv-socket", "", "Pfad des mpv-IPC-Sockets (ueberschreibt MPV_SOCKET)")
		noPlayer   = flag.Bool("no-player", false, "nur das Webinterface starten, kein mpv")
		showVer    = flag.Bool("version", false, "Version ausgeben und beenden")
	)
	flag.Parse()

	if *showVer {
		fmt.Printf("tvwall %s\n", version)
		return
	}

	log.SetFlags(log.Ldate | log.Ltime)
	log.Printf("tvwall %s startet", version)

	cfg, err := config.Load(*configPath)
	if err != nil {
		log.Fatalf("konfiguration %s: %v", *configPath, err)
	}
	if *stateDir != "" {
		cfg.StateDir = *stateDir
	}
	if *mpvSocket != "" {
		cfg.SocketPath = *mpvSocket
	}
	for _, dir := range []string{cfg.StateDir, cfg.MediaDir(), cfg.ThumbDir()} {
		if err := os.MkdirAll(dir, 0o775); err != nil {
			log.Fatalf("verzeichnis %s anlegen: %v", dir, err)
		}
	}
	log.Printf("medienordner: %s", cfg.MediaDir())

	settings := config.NewStore(cfg)
	pl := playlist.New(cfg)
	if _, err := pl.Scan(); err != nil {
		log.Printf("erster durchlauf des medienordners: %v", err)
	}
	log.Printf("playliste: %d eintraege", len(pl.Entries()))

	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	pp := player.New(cfg, pl, settings)
	if *noPlayer {
		log.Printf("player deaktiviert (-no-player)")
	} else {
		go pp.Run(ctx)
	}

	go watchMedia(ctx, pl, time.Duration(cfg.ScanInterval)*time.Second)

	listen := *addr
	if listen == "" {
		listen = fmt.Sprintf(":%d", cfg.WebPort)
	}
	srv := web.New(cfg, pl, pp, settings)

	errCh := make(chan error, 1)
	go func() {
		if err := srv.ListenAndServe(listen); err != nil && !errors.Is(err, http.ErrServerClosed) {
			errCh <- err
		}
	}()

	select {
	case <-ctx.Done():
		log.Printf("beende …")
		// Dem Player einen Moment geben, mpv sauber zu beenden.
		time.Sleep(300 * time.Millisecond)
	case err := <-errCh:
		log.Fatalf("webserver: %v", err)
	}
}

// watchMedia liest den Medienordner regelmaessig ein. So landen auch Dateien
// in der Playliste, die per USB-Stick, SSH oder Netzwerkfreigabe ankommen.
func watchMedia(ctx context.Context, pl *playlist.Manager, every time.Duration) {
	if every < time.Second {
		every = 10 * time.Second
	}
	ticker := time.NewTicker(every)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			changed, err := pl.Scan()
			if err != nil {
				log.Printf("medienordner einlesen: %v", err)
				continue
			}
			if changed {
				log.Printf("medienordner geaendert: %d eintraege", len(pl.Entries()))
			}
		}
	}
}
