package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"time"

	"github.com/gorilla/mux"
)

func limitLength(s string, length int) string {
	if len(s) < length {
		return s
	}
	return s[:length]
}

// globals, huh, faster than channels and oddly more appropriate, we care less about races than feedback.
var lastPercentage float64
var lastFilename string
var lastDurationSeconds float64

func convertFile(sourceFileName string, destinationFilename string) error {
	launch := "./ffmpeg"
	if runtime.GOOS == "windows" {
		launch += ".exe"
	}

	os.Remove(destinationFilename)
	DurationRe := regexp.MustCompile(`DURATION *: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+)`)
	FrameRe := regexp.MustCompile(`frame= ?([0-9]+) `)
	FPSValue := 0.0
	finalFramesValue := 0.0
	FPSRe := regexp.MustCompile(`, ([0-9\.]+) fps,`)
	fmt.Printf("%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s", launch, "-loglevel", "repeat+level+verbose", "-i", sourceFileName, "-c:v", "libvpx", "-crf", "10", "-b:v", "1M", "-c:a", "libvorbis", "-y", destinationFilename)
	cmd := exec.Command(launch, "-progress", "pipe:2", "-i", sourceFileName, "-c:v", "libvpx", "-crf", "10", "-b:v", "1M", "-c:a", "libvorbis", "-y", destinationFilename)
	stdout, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	// Start reading from the file with a reader.
	reader := bufio.NewReader(stdout)
	var line string
	for {
		line, err = reader.ReadString('\n')
		if err != nil && err != io.EOF {
			break
		}
		fpses := FPSRe.FindAllStringSubmatch(line, 1)
		if len(fpses) > 0 {
			fmt.Printf("FPS' is %v\n", fpses)
			FPSValue, _ = strconv.ParseFloat(fpses[0][1], 64)
		}
		durations := DurationRe.FindAllStringSubmatch(line, 1)
		if len(durations) > 0 {
			fmt.Printf("Durations is %v\n", durations)
			if FPSValue != 0.0 {
				// we have an fps, we can work out max frames!
				hrs, _ := strconv.ParseFloat(durations[0][1], 64)
				mins, _ := strconv.ParseFloat(durations[0][2], 64)
				secs, _ := strconv.ParseFloat(durations[0][3], 64)
				aaand, _ := strconv.ParseFloat("0."+durations[0][4], 64)
				lastDurationSeconds = (hrs * 60 * 60) + (mins * 60) + secs + aaand
				finalFramesValue = lastDurationSeconds * FPSValue
				fmt.Printf("%0f:%0f:%0f%0.4f * %0.2f = %0.2f seconds = %0.2f frames\n", hrs, mins, secs, aaand, FPSValue, lastDurationSeconds, finalFramesValue)
			}
		}
		currentFrames := FrameRe.FindAllStringSubmatch(line, 1)
		if len(currentFrames) > 0 {
			currentFrame, _ := strconv.ParseFloat(currentFrames[0][1], 64)
			percentage := currentFrame / finalFramesValue * 100
			lastPercentage = (percentage / 2) + 50
			fmt.Printf("Frame %0f / %0f = Percent : %0.2f\n", currentFrame, finalFramesValue, lastPercentage)
		}

		if len(fpses) == 0 && len(durations) == 0 && len(currentFrames) == 0 {
			//fmt.Printf("X: %s", line)
		}
		if err != nil {
			break
		}
	}
	if err != io.EOF {
		fmt.Printf(" > Failed with error: %v\n", err)
		return err
	}
	lastPercentage = 100
	fmt.Printf("Setting to %0f percent.\n", lastPercentage)
	os.Remove(sourceFileName)
	go func() {
		time.Sleep(10 * time.Second)
		lastPercentage = 0
	}()
	return nil
}

func downloadFile(sourceURL string, outputFile string, statusChannel chan float64, nameChannel chan string) error {
	progressBackwards := false
	lastLastPercentage := 0.0
	launch := "./youtube-dl"
	if runtime.GOOS == "windows" {
		launch += ".exe"
	}
	fmt.Printf("%s %s %s %s %s\n", launch, sourceURL, "-o", outputFile, "--newline")
	cmd := exec.Command(launch, sourceURL, "-o", outputFile, "--newline")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	re := regexp.MustCompile(`([0-9.]+)%`)
	reName := regexp.MustCompile(`Merging formats into "([^"]+)"`)

	// Start reading from the file with a reader.
	reader := bufio.NewReader(stdout)
	var line string
	for {
		line, err = reader.ReadString('\n')
		if err != nil && err != io.EOF {
			break
		}
		percentages := re.FindAllStringSubmatch(line, 1)
		if len(percentages) > 0 {
			p, err := strconv.ParseFloat(string(percentages[0][1]), 64)
			if err != nil {
				fmt.Printf("What kind of number is %v", percentages[0][1])
			} else {
				if lastLastPercentage > p {
					progressBackwards = true
				}
				lastLastPercentage = p
				if progressBackwards {
					// youtube-dl does video, then audio - it's not really half but an estimation
					lastPercentage = float64(25) + p/4
				} else {
					lastPercentage = p / 4
				}
				statusChannel <- lastPercentage
			}
		}
		fmt.Printf("%s\n", line)
		if err != nil {
			break
		}
		filenames := reName.FindAllStringSubmatch(line, 1)
		if len(filenames) > 0 {
			fmt.Printf("Filename: %v", filenames)
			lastFilename = filenames[0][1]
			nameChannel <- filenames[0][1]
		}
	}
	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
	if err != io.EOF {
		fmt.Printf(" > Failed with error: %v\n", err)
		return err
	}
	return convertFile(lastFilename, "converted.webm")
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "Hello %s\n", "world")
}

var c chan float64
var n chan string

func FetchHandler(w http.ResponseWriter, r *http.Request) {
	//vars := mux.Vars(r)
	w.WriteHeader(http.StatusOK)
	if lastFilename != "" {
		os.Remove(lastFilename)
	}
	go downloadFile(r.FormValue("url"), "test", c, n)
	fmt.Fprint(w, "OK\n")
}

func PidHandler(w http.ResponseWriter, r *http.Request) {
	//vars := mux.Vars(r)
	w.WriteHeader(http.StatusOK)
	if lastFilename != "" {
		os.Remove(lastFilename)
	}
	fmt.Fprintf(w, "%d\n", os.Getpid())
}

func QuitHandler(w http.ResponseWriter, r *http.Request) {
	//vars := mux.Vars(r)
	w.WriteHeader(http.StatusOK)
	if lastFilename != "" {
		os.Remove(lastFilename)
	}
	fmt.Fprint(w, "BYE!\n")
	go func() {
		time.Sleep(2 * time.Second)
		os.Exit(0)
	}()
}

func StatusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "application/json")
	w.WriteHeader(http.StatusOK)
	// Oddly, using channels here is slower and we don't actually care if there's a race condition.
	/*	if len(c) > 0 {
			lastPercentage = <-c
		}
		if len(n) > 0 {
			lastFilename = <-n
		} */
	fmt.Printf("Returning %0.2f for %s\n", lastPercentage, lastFilename)
	fmt.Fprintf(w, `{"filename": "%s","percentage":%0.2f,"duration_seconds":%0.6f}`+"\n", lastFilename, lastPercentage, lastDurationSeconds)
}

func main() {
	if len(os.Args) > 0 {
		os.Chdir(os.Args[1])
	}
	r := mux.NewRouter()
	r.SkipClean(true)
	r.Path("/get").Queries("url", "{.*}").HandlerFunc(FetchHandler)
	r.HandleFunc("/status/", StatusHandler)
	r.HandleFunc("/pid/", PidHandler)
	r.HandleFunc("/quit/", QuitHandler)
	r.HandleFunc("/", HomeHandler)
	http.Handle("/", r)
	c = make(chan float64, 999)
	n = make(chan string, 999)
	srv := &http.Server{
		Handler: r,
		Addr:    "127.0.0.1:10435",
		// Good practice: enforce timeouts for servers you create!
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Fatal(srv.ListenAndServe())

}
