package port
import (
	"crypto/md5"
	"fmt"
	"html"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strings"
	"github.com/NYTimes/gziphandler"
	"github.com/davidbyttow/govips/pkg/vips"
	"github.com/gobuffalo/packr/v2"
	"github.com/temoto/robotstxt"
)
type AssetList struct {
	Style      string
	JS         string
	FontW      string
	FontW2     string
	PropFontW  string
	PropFontW2 string
}
type TemplateVariables struct {
	Title    string
	URI      string
	Assets   AssetList
	RawText  string
	Lines    []Item
	Error    bool
	Protocol string
}
func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		if err := tpl.Execute(w, TemplateVariables{
			Title:    "Gopher/Gemini proxy",
			Assets:   assetList,
			RawText:  startpagetext,
			Protocol: "startpage",
		}); err != nil {
			log.Println("Template error: " + err.Error())
		}
	}
}
// RobotsTxtHandler returns the contents of the robots.txt file
// if configured and valid.
func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		if robotstxtdata == nil {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.Header().Set("Content-Type", "text/plain")
		w.Write(robotstxtdata)
	}
}
func FaviconHandler(favicondata []byte) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		if favicondata == nil {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
		w.Header().Set("Cache-Control", "max-age=2592000")
		w.Write(favicondata)
	}
}
func StyleHandler(styledata []byte) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "text/css")
		w.Header().Set("Cache-Control", "max-age=2592000")
		w.Write(styledata)
	}
}
func JavaScriptHandler(jsdata []byte) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "text/javascript")
		w.Header().Set("Cache-Control", "max-age=2592000")
		w.Write(jsdata)
	}
}
func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		if fontdata == nil {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		if woff2 {
			w.Header().Set("Content-Type", "font/woff2")
		} else {
			w.Header().Set("Content-Type", "font/woff")
		}
		w.Header().Set("Cache-Control", "max-age=2592000")
		w.Write(fontdata)
	}
}
// ListenAndServe creates a listening HTTP server bound to
// the interface specified by bind and sets up a Gopher to HTTP
// proxy proxying requests as requested and by default will prozy
// to a Gopher server address specified by uri if no servers is
// specified by the request. The robots argument is a pointer to
// a robotstxt.RobotsData struct for testing user agents against
// a configurable robots.txt file.
func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error {
	box := packr.New("assets", "../assets")
	//
	// Robots
	var robotsdata *robotstxt.RobotsData
	robotstxtdata, err := ioutil.ReadFile(robotsfile)
	if err != nil {
		log.Printf("error reading robots.txt: %s", err)
		robotstxtdata = nil
	} else {
		robotsdata, err = robotstxt.FromBytes(robotstxtdata)
		if err != nil {
			log.Printf("error reading robots.txt: %s", err)
			robotstxtdata = nil
		}
	}
	//
	// Fonts
	fontdataw, err := box.Find("iosevka-term-ss03-regular.woff")
	if err != nil {
		fontdataw = []byte{}
	}
	fontwAsset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff", md5.Sum(fontdataw))
	fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2")
	if err != nil {
		fontdataw2 = []byte{}
	}
	fontw2Asset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff2", md5.Sum(fontdataw2))
	propfontdataw, err := box.Find("iosevka-aile-regular.woff")
	if err != nil {
		propfontdataw = []byte{}
	}
	propfontwAsset := fmt.Sprintf("/iosevka-aile-regular-%x.woff", md5.Sum(propfontdataw))
	propfontdataw2, err := box.Find("iosevka-aile-regular.woff2")
	if err != nil {
		propfontdataw2 = []byte{}
	}
	propfontw2Asset := fmt.Sprintf("/iosevka-aile-regular-%x.woff2", md5.Sum(propfontdataw2))
	//
	// Stylesheet
	styledata, err := box.Find("style.css")
	if err != nil {
		styledata = []byte{}
	}
	styleAsset := fmt.Sprintf("/style-%x.css", md5.Sum(styledata))
	//
	// JavaScript
	jsdata, err := box.Find("main.js")
	if err != nil {
		jsdata = []byte{}
	}
	jsAsset := fmt.Sprintf("/main-%x.js", md5.Sum(jsdata))
	//
	// Favicon
	favicondata, err := box.Find("favicon.ico")
	if err != nil {
		favicondata = []byte{}
	}
	//
	// Start page text
	startpagedata, err := ioutil.ReadFile(startpagefile)
	if err != nil {
		startpagedata, err = box.Find("startpage.txt")
		if err != nil {
			startpagedata = []byte{}
		}
	}
	startpagetext := string(startpagedata)
	//
	//
	var allFiles []string
	files, err := ioutil.ReadDir("./tpl")
	if err != nil {
		fmt.Println(err)
	}
	for _, file := range files {
		filename := file.Name()
		if strings.HasSuffix(filename, ".html") {
			allFiles = append(allFiles, "./tpl/"+filename)
		}
	}
	templates, err = template.ParseFiles(allFiles...)
	//
	funcMap := template.FuncMap{
		"safeHtml": func(s string) template.HTML {
			return template.HTML(s)
		},
		"safeCss": func(s string) template.CSS {
			return template.CSS(s)
		},
		"safeJs": func(s string) template.JS {
			return template.JS(s)
		},
		"HTMLEscape": func(s string) string {
			return html.EscapeString(s)
		},
		"split": strings.Split,
		"last": func(s []string) string {
			return s[len(s)-1]
		},
		"pop": func(s []string) []string {
			return s[:len(s)-1]
		},
		"replace": func(pattern, output string, input interface{}) string {
			var re = regexp.MustCompile(pattern)
			var inputStr = fmt.Sprintf("%v", input)
			return re.ReplaceAllString(inputStr, output)
		},
		"trimLeftChar": func(s string) string {
			for i := range s {
				if i > 0 {
					return s[i:]
				}
			}
			return s[:0]
		},
		"hasPrefix": func(s string, prefix string) bool {
			return strings.HasPrefix(s, prefix)
		},
		"title": func(s string) string {
			return strings.Title(s)
		},
	}
	//
	startpageTpl := templates.Lookup("startpage.html").Funcs(funcMap)
	geminiTpl := templates.Lookup("gemini.html").Funcs(funcMap)
	gopherTpl := templates.Lookup("gopher.html").Funcs(funcMap)
	//
	//
	vips.Startup(&vips.Config{
		ConcurrencyLevel: vipsconcurrency,
	})
	assets := AssetList{
		Style:      styleAsset,
		JS:         jsAsset,
		FontW:      fontwAsset,
		FontW2:     fontw2Asset,
		PropFontW:  propfontwAsset,
		PropFontW2: propfontw2Asset,
	}
	http.Handle("/", gziphandler.GzipHandler(DefaultHandler(startpageTpl, startpagetext, assets)))
	http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(gopherTpl, robotsdata, assets, robotsdebug)))
	http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(geminiTpl, robotsdata, assets, robotsdebug)))
	http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata)))
	http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata)))
	http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata)))
	http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata)))
	http.HandleFunc(fontwAsset, FontHandler(false, fontdataw))
	http.HandleFunc(fontw2Asset, FontHandler(true, fontdataw2))
	http.HandleFunc(propfontwAsset, FontHandler(false, propfontdataw))
	http.HandleFunc(propfontw2Asset, FontHandler(true, propfontdataw2))
	//http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
	return http.ListenAndServe(bind, nil)
}