// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Configuration and functions for the event server web interface.

package web

import (
	"fmt"
	"github.com/Team254/cheesy-arena-lite/field"
	"github.com/Team254/cheesy-arena-lite/model"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	sessionTokenCookie = "session_token"
	adminUser          = "admin"
)

type Web struct {
	arena           *field.Arena
	templateHelpers template.FuncMap
}

func NewWeb(arena *field.Arena) *Web {
	web := &Web{arena: arena}

	// Helper functions that can be used inside templates.
	web.templateHelpers = template.FuncMap{
		// Allows sub-templates to be invoked with multiple arguments.
		"dict": func(values ...interface{}) (map[string]interface{}, error) {
			if len(values)%2 != 0 {
				return nil, fmt.Errorf("Invalid dict call.")
			}
			dict := make(map[string]interface{}, len(values)/2)
			for i := 0; i < len(values); i += 2 {
				key, ok := values[i].(string)
				if !ok {
					return nil, fmt.Errorf("Dict keys must be strings.")
				}
				dict[key] = values[i+1]
			}
			return dict, nil
		},
		"add": func(a, b int) int {
			return a + b
		},
		"multiply": func(a, b int) int {
			return a * b
		},
		"seq": func(count int) []int {
			seq := make([]int, count)
			for i := 0; i < count; i++ {
				seq[i] = i + 1
			}
			return seq
		},
		"toUpper": func(str string) string {
			return strings.ToUpper(str)
		},
	}

	return web
}

// Starts the webserver and blocks, waiting on requests. Does not return until the application exits.
func (web *Web) ServeWebInterface(port int) {
	http.Handle("/static/", http.StripPrefix("/static/", addNoCacheHeader(http.FileServer(http.Dir("static/")))))
	http.Handle("/", web.newHandler())
	log.Printf("Serving HTTP requests on port %d", port)

	// Start Server
	http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

// Serves the root page of Cheesy Arena.
func (web *Web) indexHandler(w http.ResponseWriter, r *http.Request) {
	template, err := web.parseFiles("templates/index.html", "templates/base.html")
	if err != nil {
		handleWebErr(w, err)
		return
	}
	data := struct {
		*model.EventSettings
	}{web.arena.EventSettings}
	err = template.ExecuteTemplate(w, "base", data)
	if err != nil {
		handleWebErr(w, err)
		return
	}
}

// Adds a "Cache-Control: no-cache" header to the given handler to force browser validation of last modified time.
func addNoCacheHeader(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Cache-Control", "no-cache")
		handler.ServeHTTP(w, r)
	})
}

// Sets up the mapping between URLs and handlers.
func (web *Web) newHandler() http.Handler {
	router := mux.NewRouter()
	router.HandleFunc("/", web.indexHandler).Methods("GET")
	router.HandleFunc("/alliance_selection", web.allianceSelectionGetHandler).Methods("GET")
	router.HandleFunc("/alliance_selection", web.allianceSelectionPostHandler).Methods("POST")
	router.HandleFunc("/alliance_selection/finalize", web.allianceSelectionFinalizeHandler).Methods("POST")
	router.HandleFunc("/alliance_selection/publish", web.allianceSelectionPublishHandler).Methods("POST")
	router.HandleFunc("/alliance_selection/reset", web.allianceSelectionResetHandler).Methods("POST")
	router.HandleFunc("/alliance_selection/start", web.allianceSelectionStartHandler).Methods("POST")
	router.HandleFunc("/api/alliances", web.alliancesApiHandler).Methods("GET")
	router.HandleFunc("/api/arena/websocket", web.arenaWebsocketApiHandler).Methods("GET")
	router.HandleFunc("/api/matches/{type}", web.matchesApiHandler).Methods("GET")
	router.HandleFunc("/api/rankings", web.rankingsApiHandler).Methods("GET")
	router.HandleFunc("/api/scores", web.getScoresHandler).Methods("GET")
	router.HandleFunc("/api/scores", web.setScoresHandler).Methods("PATCH", "PUT")
	router.HandleFunc("/api/sponsor_slides", web.sponsorSlidesApiHandler).Methods("GET")
	router.HandleFunc("/api/teams/{teamId}/avatar", web.teamAvatarsApiHandler).Methods("GET")
	router.HandleFunc("/display", web.placeholderDisplayHandler).Methods("GET")
	router.HandleFunc("/display/websocket", web.placeholderDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/alliance_station", web.allianceStationDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/alliance_station/websocket", web.allianceStationDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/announcer", web.announcerDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/announcer/websocket", web.announcerDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/audience", web.audienceDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/audience/websocket", web.audienceDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/field_monitor", web.fieldMonitorDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/field_monitor/websocket", web.fieldMonitorDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/pit", web.pitDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/pit/websocket", web.pitDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/queueing", web.queueingDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/queueing/websocket", web.queueingDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/displays/twitch", web.twitchDisplayHandler).Methods("GET")
	router.HandleFunc("/displays/twitch/websocket", web.twitchDisplayWebsocketHandler).Methods("GET")
	router.HandleFunc("/login", web.loginHandler).Methods("GET")
	router.HandleFunc("/login", web.loginPostHandler).Methods("POST")
	router.HandleFunc("/match_play", web.matchPlayHandler).Methods("GET")
	router.HandleFunc("/match_play/{matchId}/load", web.matchPlayLoadHandler).Methods("GET")
	router.HandleFunc("/match_play/{matchId}/show_result", web.matchPlayShowResultHandler).Methods("GET")
	router.HandleFunc("/match_play/websocket", web.matchPlayWebsocketHandler).Methods("GET")
	router.HandleFunc("/match_review", web.matchReviewHandler).Methods("GET")
	router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditGetHandler).Methods("GET")
	router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditPostHandler).Methods("POST")
	router.HandleFunc("/reports/csv/rankings", web.rankingsCsvReportHandler).Methods("GET")
	router.HandleFunc("/reports/pdf/rankings", web.rankingsPdfReportHandler).Methods("GET")
	router.HandleFunc("/reports/csv/backups", web.backupTeamsCsvReportHandler).Methods("GET")
	router.HandleFunc("/reports/pdf/backups", web.backupsPdfReportHandler).Methods("GET")
	router.HandleFunc("/reports/pdf/coupons", web.couponsPdfReportHandler).Methods("GET")
	router.HandleFunc("/reports/csv/schedule/{type}", web.scheduleCsvReportHandler).Methods("GET")
	router.HandleFunc("/reports/pdf/schedule/{type}", web.schedulePdfReportHandler).Methods("GET")
	router.HandleFunc("/reports/csv/teams", web.teamsCsvReportHandler).Methods("GET")
	router.HandleFunc("/reports/pdf/teams", web.teamsPdfReportHandler).Methods("GET")
	router.HandleFunc("/reports/csv/wpa_keys", web.wpaKeysCsvReportHandler).Methods("GET")
	router.HandleFunc("/setup/awards", web.awardsGetHandler).Methods("GET")
	router.HandleFunc("/setup/awards", web.awardsPostHandler).Methods("POST")
	router.HandleFunc("/setup/awards/publish", web.awardsPublishHandler).Methods("POST")
	router.HandleFunc("/setup/db/clear", web.clearDbHandler).Methods("POST")
	router.HandleFunc("/setup/db/restore", web.restoreDbHandler).Methods("POST")
	router.HandleFunc("/setup/db/save", web.saveDbHandler).Methods("GET")
	router.HandleFunc("/setup/displays", web.displaysGetHandler).Methods("GET")
	router.HandleFunc("/setup/displays/websocket", web.displaysWebsocketHandler).Methods("GET")
	router.HandleFunc("/setup/field_testing", web.fieldTestingGetHandler).Methods("GET")
	router.HandleFunc("/setup/field_testing/websocket", web.fieldTestingWebsocketHandler).Methods("GET")
	router.HandleFunc("/setup/lower_thirds", web.lowerThirdsGetHandler).Methods("GET")
	router.HandleFunc("/setup/lower_thirds/websocket", web.lowerThirdsWebsocketHandler).Methods("GET")
	router.HandleFunc("/setup/schedule", web.scheduleGetHandler).Methods("GET")
	router.HandleFunc("/setup/schedule/generate", web.scheduleGeneratePostHandler).Methods("POST")
	router.HandleFunc("/setup/schedule/republish", web.scheduleRepublishPostHandler).Methods("POST")
	router.HandleFunc("/setup/schedule/save", web.scheduleSavePostHandler).Methods("POST")
	router.HandleFunc("/setup/settings", web.settingsGetHandler).Methods("GET")
	router.HandleFunc("/setup/settings", web.settingsPostHandler).Methods("POST")
	router.HandleFunc("/setup/sponsor_slides", web.sponsorSlidesGetHandler).Methods("GET")
	router.HandleFunc("/setup/sponsor_slides", web.sponsorSlidesPostHandler).Methods("POST")
	router.HandleFunc("/setup/teams", web.teamsGetHandler).Methods("GET")
	router.HandleFunc("/setup/teams", web.teamsPostHandler).Methods("POST")
	router.HandleFunc("/setup/teams/{id}/delete", web.teamDeletePostHandler).Methods("POST")
	router.HandleFunc("/setup/teams/{id}/edit", web.teamEditGetHandler).Methods("GET")
	router.HandleFunc("/setup/teams/{id}/edit", web.teamEditPostHandler).Methods("POST")
	router.HandleFunc("/setup/teams/clear", web.teamsClearHandler).Methods("POST")
	router.HandleFunc("/setup/teams/generate_wpa_keys", web.teamsGenerateWpaKeysHandler).Methods("GET")
	router.HandleFunc("/setup/teams/publish", web.teamsPublishHandler).Methods("POST")
	router.HandleFunc("/setup/teams/refresh", web.teamsRefreshHandler).Methods("GET")
	return router
}

// Writes the given error out as plain text with a status code of 500.
func handleWebErr(w http.ResponseWriter, err error) {
	log.Printf("HTTP request error: %v", err)
	http.Error(w, "Internal server error: "+err.Error(), 500)
}

// Prepends the base directory to the template filenames.
func (web *Web) parseFiles(filenames ...string) (*template.Template, error) {
	var paths []string
	for _, filename := range filenames {
		paths = append(paths, filepath.Join(model.BaseDir, filename))
	}

	template := template.New("").Funcs(web.templateHelpers)
	return template.ParseFiles(paths...)
}
