package main

import "encoding/json"
import "errors"
import "fmt"
import "log"
import "net/http"
import "net/url"
import "os"
import "slices"
import "strconv"
import "time"

// JSON unmarshaling types for departure monitor API

type DMResponse struct {
	DepartureList []DMDeparture `json:"departureList"`
}

type DMDeparture struct {
	StopName      string        `json:"stopName"`
	Platform      string        `json:"platform"`
	Countdown     string        `json:"countdown"`
	DateTime      DMDateTime    `json:"dateTime"`
	RealDateTime  *DMDateTime   `json:"realDateTime"`
	ServingLine   DMServingLine `json:"servingLine"`
	OnwardStopSeq []DMStop      `json:"onwardStopSeq"`
}

type DMServingLine struct {
	Symbol    string `json:"symbol"`
	Direction string `json:"direction"`
}

type DMStop struct {
	PlaceID string `json:"placeID"`
	Place   string `json:"place"`
	NameWO  string `json:"nameWO"`
}

type DMDateTime struct {
	Hour   string `json:"hour"`
	Minute string `json:"minute"`
}

func FetchDepartures(stopId string) (DMResponse, error) {
	// Create request object
	req, err := http.NewRequest("GET", "https://www.vrn.de/mngvrn/XML_DM_REQUEST", nil)
	if err != nil {
		return DMResponse{}, err
	}

	// Configure our request
	query := url.Values{}
	query.Set("coordOutputFormat", "EPSG:4326")
	query.Set("depType", "stopEvents")
	query.Set("includeCompleteStopSeq", "1")
	query.Set("limit", "10")
	query.Set("locationServerActive", "0")
	query.Set("mode", "direct")
	query.Set("name_dm", stopId)
	query.Set("outputFormat", "json")
	query.Set("type_dm", "stop")
	query.Set("useOnlyStops", "1")
	query.Set("useRealtime", "1")
	req.URL.RawQuery = query.Encode()

	// Send the request, wait max 10 seconds
	client := http.Client{
		Timeout: 10 * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		return DMResponse{}, err
	}

	defer res.Body.Close()
	var dmResponse DMResponse
	err = json.NewDecoder(res.Body).Decode(&dmResponse)
	if err != nil {
		return DMResponse{}, err
	}

	return dmResponse, nil
}

// Types for JSON marshaling

type Departures struct {
	Departures []Departure `json:"departures"`
}

type Departure struct {
	Symbol    string `json:"symbol"`
	Direction string `json:"direction"`
	Leaving   string `json:"leaving"`
}

func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures, error) {
	var ds []Departure

	for _, d := range response.DepartureList {
		direction := d.ServingLine.Direction
		if len(d.OnwardStopSeq) != 0 {
			last := d.OnwardStopSeq[len(d.OnwardStopSeq)-1]
			if slices.Contains([]string{"5", "6"}, last.PlaceID) {
				direction = last.NameWO
			}
		}

		leaving := fmt.Sprintf("%s min", d.Countdown)
		countdown, err := strconv.Atoi(d.Countdown)
		if err != nil {
			return Departures{}, errors.New("could not parse countdown")
		}

		if countdown == 0 {
			leaving = "sofort"
		}
		if countdown > 20 {
			dt := d.DateTime
			if d.RealDateTime != nil {
				dt = *d.RealDateTime
			}
			leaving = fmt.Sprintf("%02s:%02s", dt.Hour, dt.Minute)
		}

		if allowedPlatform != nil && d.Platform != *allowedPlatform {
			continue
		}

		ds = append(ds, Departure{
			d.ServingLine.Symbol,
			direction,
			leaving,
		})
	}

	return Departures{ds}, nil
}

func main() {
	password := os.Getenv("VRNP_PASSWORD")
	if len(password) == 0 {
		panic("Required environment variable VRNP_PASSWORD is not set")
	}

	http.HandleFunc("/departures", func(w http.ResponseWriter, r *http.Request) {
		user, pass, ok := r.BasicAuth()
		if !(ok && user == "admin" && pass == password) {
			w.Header().Set("WWW-Authenticate", "Basic realm=\"Access to departure API\"")
			http.Error(w, "You shall not pass", http.StatusUnauthorized)
			return
		}

		query := r.URL.Query()

		if query["stop_id"] == nil || len(query["stop_id"]) < 1 {
			http.Error(w, "Missing query parameter: stop_id", http.StatusBadRequest)
			return
		}
		stopId := query["stop_id"][0]

		var platform *string = nil
		if query["platform"] != nil && len(query["platform"]) != 0 {
			platform = &query["platform"][0]
		}

		ds, err := FetchDepartures(stopId)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		departures, err := ParseDepartures(ds, platform)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// Does not handle multiple media types
		if r.Header.Get("Accept") == "application/json" {
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(departures)
		} else {
			// plain text
			for _, d := range departures.Departures {
				fmt.Fprintf(w, "%2s %-11s %6s\n",
					d.Symbol,
					d.Direction[:min(11, len(d.Direction))],
					d.Leaving,
				)
			}
		}
	})
	log.Fatal(http.ListenAndServe(":8000", nil))
}