package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
)

var tftpDir string

func main() {
	var studentsFile string

	var bind = flag.String("bind", ":8081", "Bind port/socket")
	flag.StringVar(&studentsFile, "students", "./students.csv", "Path to a CSV file containing students list")
	flag.StringVar(&ARPTable, "arp", ARPTable, "Path to ARP table")
	flag.StringVar(&tftpDir, "tftpdir", "/var/tftp/", "Path to TFTPd directory")
	flag.StringVar(&loginSalt, "loginsalt", "adelina", "secret used in login HMAC")

	var auth = flag.String("auth", "none", "Auth method: none, fwd, ldap, krb5")

	var fwdURI = flag.String("fwduri", "https://srs.epita.fr:443/", "URI to forward auth requests")

	var krb5Realm = flag.String("krb5realm", "CRI.EPITA.FR", "Kerberos Realm")

	var ldapAddr = flag.String("ldaphost", "auth.cri.epita.fr", "LDAP host")
	var ldapPort = flag.Int("ldapport", 636, "LDAP port")
	var ldaptls = flag.Bool("ldaptls", false, "Is LDAP connection LDAPS?")
	var ldapbase = flag.String("ldapbase", "dc=epita,dc=net", "LDAP base")
	var ldapbindusername = flag.String("ldapbindusername", "", "LDAP user to use in order to perform bind (optional if search can be made anonymously)")
	var ldapbindpassword = flag.String("ldapbindpassword", "", "Password for the bind user")
	flag.Parse()

	var err error

	// Sanitize options
	log.Println("Checking paths...")
	if tftpDir, err = filepath.Abs(tftpDir); err != nil {
		log.Fatal(err)
	}

	var lc loginChecker

	if auth != nil && *auth == "ldap" {
		log.Printf("Auth method: LDAP(%s@%s:%d?%s)", *ldapbindusername, *ldapAddr, *ldapPort, *ldapbase)
		lc.authMethod = LDAPAuth{
			Addr:         *ldapAddr,
			Port:         *ldapPort,
			IsTLS:        *ldaptls,
			Base:         *ldapbase,
			BindUsername: *ldapbindusername,
			BindPassword: *ldapbindpassword,
		}
	} else if auth != nil && *auth == "krb5" && krb5Realm != nil {
		log.Printf("Auth method: KRB5(%s)", *krb5Realm)
		lc.authMethod = &Krb5Auth{
			Realm: *krb5Realm,
		}
	} else if auth != nil && *auth == "fwd" && fwdURI != nil {
		if uri, err := url.Parse(*fwdURI); err != nil {
			log.Fatal("Unable to parse FWD URL:", err)
		} else {
			log.Printf("Auth method: HTTP_FWD(%s)", uri)
			lc.authMethod = FWDAuth{
				URI: uri,
			}
		}
	} else {
		log.Println("No auth method selected: all access will be granted")
		lc.authMethod = NoAuth{}
	}

	lc.students, err = readStudentsList(studentsFile)
	if err != nil {
		log.Fatal(err)
	}

	// Prepare graceful shutdown
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP)
	signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
	signal.Notify(interrupt, os.Interrupt, syscall.SIGINT)

	srv := &http.Server{
		Addr: *bind,
	}

	log.Println("Registering handlers...")
	mux := http.NewServeMux()
	mux.Handle("/login", lc)
	mux.HandleFunc("/logout", logout)
	http.HandleFunc("/", mux.ServeHTTP)

	// Serve content
	go func() {
		log.Fatal(srv.ListenAndServe())
	}()
	log.Println(fmt.Sprintf("Ready, listening on %s", *bind))

	// Wait shutdown signal
mloop:
	for {
		switch <-interrupt {
		case syscall.SIGHUP:
			log.Println("Reloading students files...")
			if lc.students, err = readStudentsList(studentsFile); err != nil {
				log.Println("Error during students.csv reload:", err)
			}
		case syscall.SIGTERM, syscall.SIGINT:
			break mloop
		}
	}

	log.Print("The service is shutting down...")
	srv.Shutdown(context.Background())
	log.Println("done")
}
