package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

var iso8601Format string = "2006-01-02 15:04:05 -07:00"

var codes = []int{
	http.StatusTeapot,
	http.StatusTeapot,
	http.StatusTeapot,
	http.StatusExpectationFailed,
	http.StatusPaymentRequired,
	http.StatusUnavailableForLegalReasons,
}

var countMutex sync.Mutex
var count uint64

var pageTemplate *template.Template

var quotes = []string{
	"In this devilish noise\nOn opposite sides\nSilently lie you and I\nWhen there's infinite play\nThere's no time to hate\nSuch a drone-ish waste of the day",
	"Please call to book\nYour success is our result\nIf the sun shines inside\nThe sun shines outside",
	"Constellation of Orion\nA picture with a past, a future so vast\nA mnemonic game on the arc of a journey",
	"Clouds float away like iron tools on the moon\nAll my time is in half-life\nMemories over memories\nCan I see more than I'm programmed to be?",
	"Seasons mean nothing\nYou went away, and I'm falling\nEven clouds have tried their best\nTo move and give my tears a rest",
	"I'm not going to answer you and I will not tell you why\nI'm not going to answer your question",
	"Succumb to the line\nThe finishing time\nThe long distance runner\nHas stopped on the corner\nBut I won't give up\nAlthough I've stopped, too",
	"Interpret the rooms\nMy tears in the typing pool\nThe letters are sighing\nThe ink is still drying\nI told you the truth\nAnd now I sigh too",
	"The page turns on me and you\nAcross that white plain\nThe land is unchanged",
	"Coal, coal light, the colours, the caress\nComb, the calm, the colours, the cortex\nCode, the codeine, the comma, context",
	"Vacantly they listen\nGiving no emotion\nLeaving no impression\nLove you all night",
}

type Settings struct {
	AppServeURL string
	ImgServeURL string
	HttpRootDir string
}

var appSettings Settings

func main() {
	fmt.Println("go away...")

	flag.StringVar(&appSettings.AppServeURL, "appurl", "/go/away/", "the url from which the app is served")
	flag.StringVar(&appSettings.ImgServeURL, "imgurl", "/appdata/go/away/img/", "the url from which images are served (relative to httproot)")
	flag.StringVar(&appSettings.HttpRootDir, "httproot", "/var/www/virtual/fruit/html", "the base public_html directory")
	flag.Parse()

	if appSettings.HttpRootDir == "" {
		ex, err := os.Executable()
		if err != nil {
			log.Fatal(err)
		}
		cwd := filepath.Dir(ex)
		appSettings.HttpRootDir = cwd
	}
	if !strings.HasPrefix(appSettings.ImgServeURL, "/") {
		appSettings.ImgServeURL = "/" + appSettings.ImgServeURL
	}
	if !strings.HasSuffix(appSettings.ImgServeURL, "/") {
		appSettings.ImgServeURL = appSettings.ImgServeURL + "/"
	}

	fmt.Printf("* serving from %q\n", appSettings.AppServeURL)
	fmt.Printf("* http root dir is %q\n", appSettings.HttpRootDir)
	fmt.Printf("* image dir is %q\n", appSettings.ImgServeURL)

	p, err := template.ParseFiles("gophers.html")
	if err != nil {
		log.Fatalf("cant parse template: %v", err)
	}
	pageTemplate = p

	http.HandleFunc(appSettings.AppServeURL, handler)
	//http.Handle(appSettings.ImgServeURL, http.FileServer(http.Dir(appSettings.HttpRootDir)))
	//http.Handle("/robots.txt", http.FileServer(http.Dir(appSettings.HttpRootDir)))
	log.Fatal(http.ListenAndServe("0.0.0.0:1024", nil))
}

type pageData struct {
	StatusCode int
	StatusText string
	RandImgSrc string
	VisitCount uint64
	Quote      string
}

func isImgFname(s string) bool {
	sLwr := strings.ToLower(s)
	validExts := []string{".jpg", ".jpeg", ".gif", ".png", ".webp", ".bmp"}
	for _, ext := range validExts {
		if strings.HasSuffix(sLwr, ext) {
			return true
		}
	}
	return false
}

func handler(w http.ResponseWriter, r *http.Request) {
	logRequest(r)

	statusCode := codes[rand.Intn(len(codes))]
	w.WriteHeader(statusCode)

	countMutex.Lock()
	count++
	countMutex.Unlock()

	imgSrc := ""

	files, err := os.ReadDir(path.Join(appSettings.HttpRootDir, appSettings.ImgServeURL))
	if err == nil {
		imgFnames := make([]string, 0)
		for _, f := range files {
			name := f.Name()
			if isImgFname(name) {
				imgFnames = append(imgFnames, name)
			}
		}
		if len(imgFnames) > 0 {
			imgSrc = path.Join("/", appSettings.ImgServeURL, imgFnames[rand.Intn(len(imgFnames))])
		}
	}

	p := pageData{
		StatusCode: statusCode,
		StatusText: http.StatusText(statusCode),
		RandImgSrc: imgSrc,
		VisitCount: count,
		Quote:      quotes[rand.Intn(len(quotes))],
	}
	var htmlBuf strings.Builder
	err = pageTemplate.Execute(&htmlBuf, p)
	if err != nil {
		fmt.Println("error executing template", err)
		fmt.Fprintf(w, "error: gophers r dumb")
	} else {
		w.Write([]byte(htmlBuf.String()))
	}
}

func logRequest(r *http.Request) {
	fmt.Println("")
	fmt.Printf("request at %s\n", time.Now().Format(iso8601Format))
	fmt.Printf("- url: %q\n", r.URL.Path)
	fmt.Printf("- referer: %q\n", r.Referer())
	fmt.Printf("- user-agent: %q\n", r.UserAgent())
	fmt.Printf("- ip: %q\n", readUserIP(r))
}

func readUserIP(r *http.Request) string {
	// cf. https://stackoverflow.com/a/55738279
	// lmao
	IPAddress := r.Header.Get("X-Real-Ip")
	if IPAddress == "" {
		IPAddress = r.Header.Get("X-Forwarded-For")
	}
	if IPAddress == "" {
		IPAddress = r.RemoteAddr + " (fallback)"
	}
	return IPAddress
}
