package main

//go:generate esc -o assets.go assets templates

import (
	"bufio"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/gorilla/handlers"
	"github.com/gorilla/sessions"
)

const AppName = "gosscms"
const DbName = AppName + ".db"

type siteData struct {
	Title       string
	Port        int
	SessionName string
	ServerDir   string
	DevMode     bool
}

type pageData struct {
	Site          *siteData
	Title         string
	SubTitle      string
	Stylesheets   []string
	HeaderScripts []string
	Scripts       []string
	FlashMessage  string
	FlashClass    string
	LoggedIn      bool
	Menu          []menuItem
	BottomMenu    []menuItem
	session       *pageSession

	TemplateData interface{}
}

type menuItem struct {
	Label    string
	Location string
	Icon     string
}

type Routes map[string]http.HandlerFunc

var sessionSecret = "JCOP5e8ohkTcOzcSMe74"
var sessionStore = sessions.NewCookieStore([]byte(sessionSecret))
var site *siteData

func main() {
	loadConfig()
	dbSaveSiteConfig(site)
	initialize()

	if site.DevMode {
		fmt.Println("Operating in Development Mode")
	}

	routes := Routes{
		"/":             PublicHandler,
		"/admin":        AdminHandler,
		"/admin/assets": AdminAssetHandler,
	}
	mux := http.DefaultServeMux
	loggingHandler := NewApacheLoggingHandler(mux, os.Stderr)
	for route, fn := range routes {
		mux.Handle(route, fn)
	}

	// Admin Subrouter
	/*
		admin := r.PathPrefix("/admin").Subrouter()
		admin.Handle("/assets", http.FileServer(FS(site.DevMode)))
		admin.HandleFunc("/", handleAdmin)
		admin.HandleFunc("/dologin", handleAdminDoLogin)
		admin.HandleFunc("/dologout", handleAdminDoLogout)
		admin.HandleFunc("/{category}", handleAdmin)
		admin.HandleFunc("/{category}/{id}", handleAdmin)
		admin.HandleFunc("/{category}/{id}/{function}", handleAdmin)
		admin.HandleFunc("/{category}/{id}/{function}/{subid}", handleAdmin)

		http.Handle("/", r)

	*/
	server := &http.Server{Addr: "127.0.0.1:" + strconv.Itoa(site.Port), Handler: loggingHandler}

	fmt.Printf("Listening on port %d\n", site.Port)
	log.Fatal(server.ListenAndServe())
}

func PublicHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.URL.Path)
	http.ServeFile(w, r, "./public")
}

func loadConfig() {
	site = dbGetSiteConfig()

	if len(os.Args) > 1 {
		for _, v := range os.Args {
			key := v
			val := ""
			eqInd := strings.Index(v, "=")
			if eqInd > 0 {
				// It's a key/val argument
				key = v[:eqInd]
				val = v[eqInd+1:]
			}
			switch key {
			case "-title":
				site.Title = val
				fmt.Print("Set site title: ", site.Title, "\n")
			case "-port":
				var tryPort int
				var err error
				if tryPort, err = strconv.Atoi(val); err != nil {
					fmt.Print("Invalid port given: ", val, " (Must be an integer)\n")
					tryPort = site.Port
				}
				// TODO: Make sure a valid port number is given
				site.Port = tryPort
			case "-session-name":
				site.SessionName = val
			case "-server-dir":
				// TODO: Probably check if the given directory is valid
				site.ServerDir = val
			case "-help", "-h", "-?":
				printHelp()
				done()
			case "-dev":
				site.DevMode = true
			case "-reset-defaults":
				resetToDefaults()
				done()
			}
		}
	}
}

func initialize() {
	// Check if the database has been created
	assertError(initDatabase())

	if !dbHasUser() {
		reader := bufio.NewReader(os.Stdin)
		fmt.Println("Create new Admin user")
		fmt.Print("Email: ")
		email, _ := reader.ReadString('\n')
		email = strings.TrimSpace(email)
		var pw1, pw2 []byte
		for string(pw1) != string(pw2) || string(pw1) == "" {
			fmt.Print("Password: ")
			pw1, _ = terminal.ReadPassword(0)
			fmt.Println("")
			fmt.Print("Repeat Password: ")
			pw2, _ = terminal.ReadPassword(0)
			fmt.Println("")
			if string(pw1) != string(pw2) {
				fmt.Println("Entered Passwords don't match!")
			}
		}
		assertError(dbUpdateUserPassword(email, string(pw1)))
	}
}

func loggingHandler(h http.Handler) http.Handler {
	return handlers.LoggingHandler(os.Stdout, h)
}

func resetToDefaults() {
	def := GetDefaultSiteConfig()
	fmt.Println("Reset settings to defaults?")
	fmt.Print(site.Title, " -> ", def.Title, "\n")
	fmt.Print(site.Port, " -> ", def.Port, "\n")
	fmt.Print(site.SessionName, " -> ", def.SessionName, "\n")
	fmt.Print(site.ServerDir, " -> ", def.ServerDir, "\n")
	fmt.Println("Are you sure? (y/N): ")
	reader := bufio.NewReader(os.Stdin)
	conf, _ := reader.ReadString('\n')
	conf = strings.ToUpper(strings.TrimSpace(conf))
	if strings.HasPrefix(conf, "Y") {
		if dbSaveSiteConfig(def) != nil {
			errorExit("Error resetting to defaults")
		}
		fmt.Println("Reset to defaults")
	}
}

func printHelp() {
	help := []string{
		"Game Jam Voting Help",
		"  -help, -h, -?            Print this message",
		"  -dev                     Development mode, load assets from file system",
		"  -port=<port num>         Set the site port",
		"  -session-name=<session>  Set the name of the session to be used",
		"  -server-dir=<directory>  Set the server directory",
		"                           This designates where the database will be saved",
		"                           and where the app will look for files if you're",
		"                           operating in 'development' mode (-dev)",
		"  -title=<title>           Set the site title",
		"  -reset-defaults          Reset all configuration options to defaults",
		"",
	}
	for _, v := range help {
		fmt.Println(v)
	}
}

func done() {
	os.Exit(0)
}

func errorExit(msg string) {
	fmt.Println(msg)
	os.Exit(1)
}

func assertError(err error) {
	if err != nil {
		panic(err)
	}
}

// redirect can be used only for GET redirects
func redirect(url string, w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, url, 303)
}

// outputTemplate
// Spit out a template
func outputTemplate(tmplName string, tmplData interface{}, w http.ResponseWriter) error {
	fmt.Println(tmplName)
	n := "/templates/" + tmplName
	l := template.Must(template.New("layout").Parse(FSMustString(site.DevMode, n)))
	t := template.Must(l.Parse(FSMustString(site.DevMode, n)))
	return t.Execute(w, tmplData)
}
