package hardware

import (
	"net/http"
	"os/exec"
	"strings"

	"github.com/AlexxIT/go2rtc/internal/api"
	"github.com/AlexxIT/go2rtc/pkg/ffmpeg"
)

const (
	EngineSoftware     = "software"
	EngineVAAPI        = "vaapi"        // Intel iGPU and AMD GPU
	EngineV4L2M2M      = "v4l2m2m"      // Raspberry Pi 3 and 4
	EngineCUDA         = "cuda"         // NVidia on Windows and Linux
	EngineDXVA2        = "dxva2"        // Intel on Windows
	EngineVideoToolbox = "videotoolbox" // macOS
	EngineRKMPP        = "rkmpp"        // Rockchip
)

func Init(bin string) {
	api.HandleFunc("api/ffmpeg/hardware", func(w http.ResponseWriter, r *http.Request) {
		api.ResponseSources(w, ProbeAll(bin))
	})
}

// MakeHardware converts software FFmpeg args to hardware args
// empty engine for autoselect
func MakeHardware(args *ffmpeg.Args, engine string, defaults map[string]string) {
	for i, codec := range args.Codecs {
		if len(codec) < 10 {
			continue // skip short line (-c:v mjpeg...)
		}

		// get current codec name
		name := cut(codec, ' ', 1)
		switch name {
		case "libx264":
			name = "h264"
		case "libx265":
			name = "h265"
		case "mjpeg":
		default:
			continue // skip unsupported codec
		}

		// temporary disable probe for H265
		if engine == "" && name != "h265" {
			if engine = cache[name]; engine == "" {
				engine = ProbeHardware(args.Bin, name)
				cache[name] = engine
			}
		}

		switch engine {
		case EngineVAAPI:
			args.Codecs[i] = defaults[name+"/"+engine]

			if !args.HasFilters("drawtext=") {
				args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_flags allow_profile_mismatch " + args.Input

				if name == "h264" {
					fixPixelFormat(args)
				}

				for i, filter := range args.Filters {
					if strings.HasPrefix(filter, "scale=") {
						args.Filters[i] = "scale_vaapi=" + filter[6:]
					}
					if strings.HasPrefix(filter, "transpose=") {
						if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
							args.Filters[i] = "transpose_vaapi=4" // reversal
						} else {
							args.Filters[i] = "transpose_vaapi=" + filter[10:]
						}
					}
				}

				// fix if input doesn't support hwaccel, do nothing when support
				// insert as first filter before hardware scale and transpose
				args.InsertFilter("format=vaapi|nv12,hwupload")
			} else {
				// enable software pixel for drawtext, scale and transpose
				args.Input = "-hwaccel vaapi -hwaccel_output_format nv12 -hwaccel_flags allow_profile_mismatch " + args.Input

				args.AddFilter("hwupload")
			}

		case EngineCUDA:
			args.Codecs[i] = defaults[name+"/"+engine]

			// CUDA doesn't support hardware transpose
			// https://github.com/AlexxIT/go2rtc/issues/389
			if !args.HasFilters("drawtext=", "transpose=") {
				args.Input = "-hwaccel cuda -hwaccel_output_format cuda " + args.Input

				for i, filter := range args.Filters {
					if strings.HasPrefix(filter, "scale=") {
						args.Filters[i] = "scale_cuda=" + filter[6:]
					}
				}
			} else {
				args.Input = "-hwaccel cuda -hwaccel_output_format nv12 " + args.Input

				args.AddFilter("hwupload")
			}

		case EngineDXVA2:
			args.Input = "-hwaccel dxva2 -hwaccel_output_format dxva2_vld " + args.Input
			args.Codecs[i] = defaults[name+"/"+engine]

			for i, filter := range args.Filters {
				if strings.HasPrefix(filter, "scale=") {
					args.Filters[i] = "scale_qsv=" + filter[6:]
				}
			}

			args.InsertFilter("hwmap=derive_device=qsv,format=qsv")

		case EngineVideoToolbox:
			args.Input = "-hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld " + args.Input
			args.Codecs[i] = defaults[name+"/"+engine]

		case EngineV4L2M2M:
			args.Codecs[i] = defaults[name+"/"+engine]

		case EngineRKMPP:
			args.Codecs[i] = defaults[name+"/"+engine]

			if !args.HasFilters("drawtext=") {
				args.Input = "-hwaccel rkmpp -hwaccel_output_format drm_prime -afbc rga " + args.Input

				for i, filter := range args.Filters {
					if strings.HasPrefix(filter, "scale=") {
						args.Filters[i] = "scale_rkrga=" + filter[6:] + ":force_original_aspect_ratio=0"
					}
					if strings.HasPrefix(filter, "transpose=") {
						if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
							args.Filters[i] = "vpp_rkrga=transpose=4" // reversal
						} else {
							args.Filters[i] = "vpp_rkrga=transpose=" + filter[10:]
						}
					}
				}

				if len(args.Filters) > 0 {
					// fix if input doesn't support hwaccel, do nothing when support
					// insert as first filter before hardware scale and transpose
					args.InsertFilter("format=drm_prime|nv12,hwupload")
				}
			} else {
				// enable software pixel for drawtext, scale and transpose
				args.Input = "-hwaccel rkmpp -hwaccel_output_format nv12 -afbc rga " + args.Input

				args.AddFilter("hwupload")
			}
		}
	}
}

var cache = map[string]string{}

func run(bin string, args string) bool {
	err := exec.Command(bin, strings.Split(args, " ")...).Run()
	return err == nil
}

func runToString(bin string, args string) string {
	if run(bin, args) {
		return "OK"
	} else {
		return "ERROR"
	}
}

func cut(s string, sep byte, pos int) string {
	for n := 0; n < pos; n++ {
		if i := strings.IndexByte(s, sep); i > 0 {
			s = s[i+1:]
		} else {
			return ""
		}
	}
	if i := strings.IndexByte(s, sep); i > 0 {
		return s[:i]
	}
	return s
}

// fixPixelFormat:
//   - good h264 pixel: yuv420p(tv, bt709) == yuv420p (mpeg/limited/tv)
//   - bad h264 pixel: yuvj420p(pc, bt709) == yuvj420p (jpeg/full/pc)
//   - bad jpeg pixel: yuvj422p(pc, bt470bg)
func fixPixelFormat(args *ffmpeg.Args) {
	// in my tests this filters has same CPU/GPU load:
	//   - "hwupload"
	//   - "hwupload,scale_vaapi=out_color_matrix=bt709:out_range=tv"
	//   - "hwupload,scale_vaapi=out_color_matrix=bt709:out_range=tv:format=nv12"
	const fixPixFmt = "out_color_matrix=bt709:out_range=tv:format=nv12"

	for i, filter := range args.Filters {
		if strings.HasPrefix(filter, "scale=") {
			args.Filters[i] = filter + ":" + fixPixFmt
			return
		}
	}

	args.Filters = append(args.Filters, "scale="+fixPixFmt)
}
