package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/url"
	"strconv"

	ffmpeg "github.com/u2takey/ffmpeg-go"

	"srs.epita.fr/fic-server/admin/sync"
	"srs.epita.fr/fic-server/libfic"
)

type VideoInfo struct {
	Streams []struct {
		Index         uint   `json:"index"`
		CodecType     string `json:"codec_type"`
		CodecName     string `json:"codec_name"`
		CodecLongName string `json:"codec_long_name"`
		Duration      string
		NbFrames      string `json:"nb_frames"`
		Width         int
		Height        int
		Tags          map[string]string `json:"tags"`
	} `json:"streams"`
}

func gcd(a, b int) int {
	var bgcd func(a, b, res int) int

	bgcd = func(a, b, res int) int {
		switch {
		case a == b:
			return res * a
		case a%2 == 0 && b%2 == 0:
			return bgcd(a/2, b/2, 2*res)
		case a%2 == 0:
			return bgcd(a/2, b, res)
		case b%2 == 0:
			return bgcd(a, b/2, res)
		case a > b:
			return bgcd(a-b, b, res)
		default:
			return bgcd(a, b-a, res)
		}
	}
	return bgcd(a, b, 1)
}

func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
	i, ok := sync.GlobalImporter.(sync.LocalImporter)
	if !ok {
		log.Printf("Unable to load `videos-rules.so` as the current Importer is not a LocalImporter (%T).", sync.GlobalImporter)
		return
	}

	if len(e.VideoURI) == 0 {
		return
	}

	// Filter exceptions to only keep related to resolution.mp4
	exceptions = exceptions.GetFileExceptions("resolution.mp4")

	path, err := url.PathUnescape(e.VideoURI[9:])
	if err != nil {
		path = e.VideoURI
	}
	path = i.GetLocalPath(path)

	data, err := ffmpeg.Probe(path)
	if err != nil {
		errs = append(errs, fmt.Errorf("unable to open %q: %w", path, err))
		return
	}

	vInfo := &VideoInfo{}
	err = json.Unmarshal([]byte(data), vInfo)
	if err != nil {
		panic(err)
	}

	video_seen := []int{}
	subtitles_seen := []int{}
	for idx, s := range vInfo.Streams {
		if s.CodecType == "video" {
			video_seen = append(video_seen, idx)
			if (s.Width > 1920 || s.Height > 1080) && !exceptions.HasException(":size:above_maximum") {
				errs = append(errs, fmt.Errorf("video track is too wide: %dx%d (maximum allowed: 1920x1080)", s.Width, s.Height))
			}

			ratio := s.Width * 10 / s.Height
			if ratio < 13 || ratio > 19 && !exceptions.HasException(":size:strange_ratio") {
				m := gcd(s.Width, s.Height)
				errs = append(errs, fmt.Errorf("video track has a strange ratio: %d:%d. Is this really expected?", s.Width/m, s.Height/m))
			}

			if s.CodecName != "h264" {
				errs = append(errs, fmt.Errorf("video codec has to be H264 (currently: %s)", s.CodecLongName))
			}

			duration, err := strconv.ParseFloat(s.Duration, 64)
			if err == nil {
				if duration < 45 && !exceptions.HasException(":duration:too_short") {
					errs = append(errs, fmt.Errorf("video is too short"))
				}
				if duration > 450 && !exceptions.HasException(":duration:too_long") {
					errs = append(errs, fmt.Errorf("video is too long"))
				}
			} else {
				errs = append(errs, fmt.Errorf("invalid track duration: %q", s.Duration))
			}
		} else if s.CodecType == "subtitle" {
			subtitles_seen = append(subtitles_seen, idx)

			if s.CodecName != "mov_text" {
				errs = append(errs, fmt.Errorf("subtitle format has to be MOV text/3GPP Timed Text (currently: %s)", s.CodecLongName))
			}

			nbframes, err := strconv.ParseInt(s.NbFrames, 10, 64)
			if err == nil {
				if nbframes < 5 && !exceptions.HasException(":subtitle:tiny") {
					errs = append(errs, fmt.Errorf("too few subtitles"))
				}
			} else {
				errs = append(errs, fmt.Errorf("invalid number of frame: %q", s.NbFrames))
			}
		} else if s.CodecType == "audio" {
			if !exceptions.HasException(":audio:allowed") {
				errs = append(errs, fmt.Errorf("an audio track is present, use subtitle for explainations"))
			}

			if s.CodecName != "aac" {
				errs = append(errs, fmt.Errorf("audio codec has to be AAC (Advanced Audio Coding) (currently: %s)", s.CodecLongName))
			}
		} else {
			errs = append(errs, fmt.Errorf("unknown track found of type %q", s.CodecType))
		}
	}

	if len(video_seen) == 0 {
		errs = append(errs, fmt.Errorf("no video track found"))
	} else if len(video_seen) > 1 {
		errs = append(errs, fmt.Errorf("%d video tracks found, is it expected?", len(video_seen)))
	}
	if len(subtitles_seen) == 0 && !exceptions.HasException(":subtitle:no_track") {
		errs = append(errs, fmt.Errorf("no subtitles track found"))
	} else if len(subtitles_seen) > 0 {
		for _, idx := range subtitles_seen {
			language := e.Language
			if lang, ok := vInfo.Streams[idx].Tags["language"]; e.Language != "" && (!ok || lang == "" || lang == "und") {
				errs = append(errs, fmt.Errorf("subtitles track %d with no language defined", vInfo.Streams[idx].Index))
			} else {
				language = lang
			}

			errs = append(errs, CheckGrammarSubtitleTrack(path, vInfo.Streams[idx].Index, language, exceptions)...)
		}

		if e.Language != "" && len(subtitles_seen) < 2 {
			errs = append(errs, fmt.Errorf("subtitle tracks must exist in original language and translated, only one subtitle track found"))
		}

	}

	return
}
