package config

import (
	"fmt"
	"path/filepath"

	"github.com/photoprism/photoprism/internal/ffmpeg/encode"
	"github.com/photoprism/photoprism/internal/thumb"
	"github.com/photoprism/photoprism/pkg/fs"
	"github.com/photoprism/photoprism/pkg/txt"
)

// FFmpegBin returns the ffmpeg executable file name.
func (c *Config) FFmpegBin() string {
	return FindBin(c.options.FFmpegBin, encode.FFmpegBin)
}

// FFprobeBin returns the ffprobe executable file name.
func (c *Config) FFprobeBin() string {
	if ffmpegBin := c.FFmpegBin(); ffmpegBin != "" && fs.FileExistsNotEmpty(ffmpegBin) {
		return FindBin(filepath.Join(filepath.Dir(ffmpegBin), encode.FFprobeBin), encode.FFprobeBin)
	}

	return FindBin(encode.FFprobeBin)
}

// YtDlpBin returns the name of the video download executable file, if installed.
func (c *Config) YtDlpBin() string {
	return FindBin("yt-dlp", "yt-dl", "youtube-dl", "dl")
}

// FFmpegEnabled checks if FFmpeg is enabled for video transcoding.
func (c *Config) FFmpegEnabled() bool {
	return !c.DisableFFmpeg()
}

// FFmpegEncoder returns the FFmpeg AVC encoder name.
func (c *Config) FFmpegEncoder() encode.Encoder {
	if c.options.FFmpegEncoder == encode.SoftwareAvc.String() {
		return encode.SoftwareAvc
	} else if c.options.FFmpegEncoder == "" {
		return encode.DefaultAvcEncoder()
	}

	return encode.FindEncoder(c.options.FFmpegEncoder)
}

// FFmpegSize returns the maximum ffmpeg video encoding size in pixels (720-7680).
func (c *Config) FFmpegSize() int {
	return thumb.VideoSize(c.options.FFmpegSize).Width
}

// FFmpegQuality returns the ffmpeg encoding quality from 1 to 100,
// with a default of 50 and where 100 is almost lossless.
func (c *Config) FFmpegQuality() int {
	switch {
	case c.options.FFmpegQuality <= 0:
		return encode.DefaultQuality
	case c.options.FFmpegQuality < encode.WorstQuality:
		return encode.WorstQuality
	case c.options.FFmpegQuality > 100:
		return encode.BestQuality
	default:
		return c.options.FFmpegQuality
	}
}

// FFmpegBitrate returns the ffmpeg bitrate limit in Mbps for non-AVC videos to be transcoded
// even if they could be played natively (optional).
func (c *Config) FFmpegBitrate() int {
	switch {
	case c.options.FFmpegBitrate < 0:
		return encode.NoBitrateLimit
	case c.options.FFmpegBitrate == 0:
		return encode.DefaultBitrateLimit
	case c.options.FFmpegBitrate < encode.MinBitrateLimit:
		return encode.MinBitrateLimit
	case c.options.FFmpegBitrate >= encode.MaxBitrateLimit:
		return encode.MaxBitrateLimit
	default:
		return c.options.FFmpegBitrate
	}
}

// FFmpegBitrateExceeded tests if the ffmpeg bitrate limit in Mbps is exceeded.
func (c *Config) FFmpegBitrateExceeded(bitrate float64) bool {
	if bitrate <= 0 {
		return false
	} else if limit := c.FFmpegBitrate(); limit <= 0 {
		return false
	} else {
		return bitrate > float64(limit)
	}
}

// FFmpegPreset returns the ffmpeg encoding preset from "ultrafast" to "veryslow",
// see https://trac.ffmpeg.org/wiki/Encode/H.264#Preset.
func (c *Config) FFmpegPreset() string {
	if c.options.FFmpegPreset == "" {
		return encode.PresetFast
	}

	return c.options.FFmpegPreset
}

// FFmpegDevice returns the ffmpeg device path for supported hardware encoders (optional).
func (c *Config) FFmpegDevice() string {
	switch {
	case c.options.FFmpegDevice == "":
		return ""
	case txt.IsUInt(c.options.FFmpegDevice):
		return c.options.FFmpegDevice
	case fs.DeviceExists(c.options.FFmpegDevice):
		return c.options.FFmpegDevice
	default:
		return ""
	}
}

// FFmpegMapVideo returns the video streams to be transcoded as string.
func (c *Config) FFmpegMapVideo() string {
	if c.options.FFmpegMapVideo == "" {
		return encode.DefaultMapVideo
	}

	return c.options.FFmpegMapVideo
}

// FFmpegMapAudio returns the audio streams to be transcoded as string.
func (c *Config) FFmpegMapAudio() string {
	if c.options.FFmpegMapAudio == "" {
		return encode.DefaultMapAudio
	}

	return c.options.FFmpegMapAudio
}

// FFmpegOptions returns the FFmpeg options to use for video transcoding.
func (c *Config) FFmpegOptions(encoder encode.Encoder, bitrate string) (encode.Options, error) {
	// Get options to transcode other formats with FFmpeg.
	opt := encode.NewVideoOptions(c.FFmpegBin(), encoder, c.FFmpegSize(), c.FFmpegQuality(), c.FFmpegPreset(), c.FFmpegDevice(), c.FFmpegMapVideo(), c.FFmpegMapAudio())

	// Check options and return error if invalid.
	switch {
	case opt.Bin == "":
		return opt, fmt.Errorf("ffmpeg is not installed")
	case c.DisableFFmpeg():
		return opt, fmt.Errorf("ffmpeg is disabled")
	case bitrate == "":
		return opt, fmt.Errorf("bitrate must not be empty")
	case encoder.String() == "":
		return opt, fmt.Errorf("encoder must not be empty")
	}

	return opt, nil
}
