package main

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"local/args"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"syscall"
	"time"

	"golang.org/x/sys/unix"
)

var rtspCmd *exec.Cmd
var done bool
var as *args.ArgSet
var rtspAddr string
var ss string

func main() {
	as = args.NewArgSet()
	as.Append(args.INT, "p", "port to listen on", "8101")
	as.Append(args.STRING, "d", "dir to serve", ".")
	as.Append(args.STRING, "rtsp", "rtsp ip:port", "192.168.0.96:8554")
	as.Append(args.STRING, "rtsp-pass", "rtsp password", "fwees123")
	as.Append(args.STRING, "input", "input override", "")
	as.Append(args.STRING, "hls", "hsl params", "-hls_time 3 -hls_list_size 10 -hls_flags +delete_segments+append_list+omit_endlist")
	as.Append(args.STRING, "ss", "time to seek to", "00:00:00")
	as.Append(args.INT, "kbps", "stream kbps", "500")
	if err := as.Parse(); err != nil {
		panic(err)
	}
	rtspAddr = as.GetString("rtsp")
	ss = as.GetString("ss")

	stream := path.Join(as.GetString("d"), "stream")
	os.RemoveAll(stream)
	if err := os.MkdirAll(stream, os.ModePerm); err != nil {
		panic(err)
	}

	go rtsp()

	for {
		_, err := os.Stat(path.Join(stream, "test.m3u8"))
		if err == nil {
			break
		} else if !os.IsNotExist(err) {
			panic(err)
		}
		time.Sleep(time.Second * 1)
	}

	h := http.FileServer(http.Dir(as.GetString("d")))
	go func() {
		if err := http.ListenAndServe(fmt.Sprintf(":%d", as.GetInt("p")), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Method == http.MethodPost && path.Dir(r.URL.Path) == "/player" {
				switch path.Base(r.URL.Path) {
				case "set":
					var address struct {
						Address string `json:"address"`
					}
					err := json.NewDecoder(r.Body).Decode(&address)
					if err != nil {
						http.Error(w, err.Error(), http.StatusBadRequest)
						return
					}
					rtspAddr = address.Address
					killStream()
					log.Printf("new rtsp: %s", rtspAddr)
				case "seek":
					var ts struct {
						TS string `json:"ts"`
					}
					err := json.NewDecoder(r.Body).Decode(&ts)
					if err != nil {
						http.Error(w, err.Error(), http.StatusBadRequest)
						return
					}
					ss = ts.TS
					killStream()
					log.Printf("new ss: %s", ss)
				case "pause":
					stopStream()
					log.Printf("stopped stream")
				case "resume":
					startStream()
					log.Printf("resumed stream")
				default:
					log.Printf("bad cmd: %q", r.URL.Path)
					http.NotFound(w, r)
				}
			} else {
				h.ServeHTTP(w, r)
			}
		})); err != nil {
			panic(err)
		}
	}()

	stop := make(chan os.Signal)
	signal.Notify(stop, os.Interrupt)
	<-stop
	permaKillStream()
	os.RemoveAll(stream)
}

func rtsp() {
	log.Println("installing...")
	install := exec.Command("bash", "-c", `
      if ! which ffmpeg; then
         apk add --no-cache ffmpeg \
            || sudo apk add --no-cache ffmpeg \
            || (apt update; apt -y install ffmpeg) \
            || (apt -y update; apt -y install ffmpeg) \
            || (sudo apt -y update; sudo apt -y install ffmpeg) \
            || (apt-get update; apt-get -y install ffmpeg) \
            || (apt-get -y update; apt-get -y install ffmpeg) \
            || (sudo apt-get -y update; sudo apt-get -y install ffmpeg) \
            || true
      fi
      if ! which ffmpeg; then
         exit 499
      fi
   `)
	if o, err := install.StdoutPipe(); err != nil {
		panic(err)
	} else {
		go io.Copy(os.Stdout, o)
	}
	if o, err := install.StderrPipe(); err != nil {
		panic(err)
	} else {
		go io.Copy(os.Stderr, o)
	}
	if err := install.Run(); err != nil {
		panic(err)
	}
	log.Println("looping...")
	for !done {
		log.Println("streaming...")
		i := `-rtsp_transport tcp -stimeout 10000000 -i rtsp://` + rtspAddr + `/unicast -an`
		if input := as.GetString("input"); len(input) > 0 {
			i = input
		}
		s := ""
		if len(ss) > 0 {
			s = "-ss " + ss
		}
		rtspCmd = exec.Command("bash", "-c", ` 
         mkdir -p `+path.Join(as.GetString("d"), "stream")+`;
         rm -rf `+path.Join(as.GetString("d"), "stream", "*")+`;
         exec ffmpeg \
            -re \
            -nostats \
            -loglevel warning \
            -fflags nobuffer \
            `+s+` \
            `+i+` \
            -preset ultrafast \
            -c:v copy \
            -b:v `+fmt.Sprint(as.GetInt("kbps"))+`k \
            -flags +cgop \
            -g 30 \
            `+as.GetString("hls")+` \
            `+path.Join(as.GetString("d"), "stream", "test.m3u8")+`
      `)
		if o, err := rtspCmd.StdoutPipe(); err != nil {
			panic(err)
		} else {
			go io.Copy(os.Stdout, o)
		}
		if o, err := rtspCmd.StderrPipe(); err != nil {
			panic(err)
		} else {
			go io.Copy(os.Stderr, o)
		}
		log.Println("starting rtsp cmd", rtspCmd)
		if err := rtspCmd.Start(); err != nil {
			panic(err)
		}
		log.Println("waiting rtsp cmd")
		log.Println(rtspCmd.Wait())
	}
}

func startStream() {
	signalStream(syscall.Signal(unix.SIGCONT))
}

func stopStream() {
	signalStream(syscall.Signal(unix.SIGSTOP))
}

func killStream() {
	signalStream(syscall.Signal(unix.SIGKILL))
}

func permaKillStream() {
	done = true
	killStream()
}

func signalStream(s syscall.Signal) {
	for rtspCmd == nil {
		log.Println("rtspCmdis nil")
		time.Sleep(time.Second * 3)
	}
	for rtspCmd.Process == nil {
		log.Println("rtspCmd.Process is nil")
		time.Sleep(time.Second * 3)
	}
	log.Println(s, rtspCmd.Process.Signal(os.Signal(s)))
}

func rebootCam() {
	c := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
	r, err := http.NewRequest("GET", "https://"+rtspAddr+"/cgi-bin/action.cgi?cmd=reboot", nil)
	if err != nil {
		panic(err)
	}
	pass := as.GetString("rtsp-pass")
	r.SetBasicAuth("root", pass)
	resp, err := c.Do(r)
	if err != nil {
		panic(err)
	}
	resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		panic(resp.StatusCode)
	}
}
