// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package derpmap contains information about Tailscale.com's production DERP nodes.
package derpmap

import (
	"fmt"
)

// World is a set of DERP server.
type World struct {
	servers []*Server
	ids     []int
	byID    map[int]*Server
	stun4   []string
	stun6   []string
}

func (w *World) IDs() []int                { return w.ids }
func (w *World) STUN4() []string           { return w.stun4 }
func (w *World) STUN6() []string           { return w.stun6 }
func (w *World) ServerByID(id int) *Server { return w.byID[id] }

// LocationOfID returns the geographic name of a node, if present.
func (w *World) LocationOfID(id int) string {
	if s, ok := w.byID[id]; ok {
		return s.Geo
	}
	return ""
}

func (w *World) NodeIDOfSTUNServer(server string) int {
	// TODO: keep reverse map? Small enough to not matter for now.
	for _, s := range w.servers {
		if s.STUN4 == server || s.STUN6 == server {
			return s.ID
		}
	}
	return 0
}

// Prod returns the production DERP nodes.
func Prod() *World {
	return prod
}

func NewTestWorld(stun ...string) *World {
	w := &World{}
	for i, s := range stun {
		w.add(&Server{
			ID:    i + 1,
			Geo:   fmt.Sprintf("Testopolis-%d", i+1),
			STUN4: s,
		})
	}
	return w
}

func NewTestWorldWith(servers ...*Server) *World {
	w := &World{}
	for _, s := range servers {
		w.add(s)
	}
	return w
}

var prod = new(World) // ... a dazzling place I never knew

func addProd(id int, geo string) {
	prod.add(&Server{
		ID:        id,
		Geo:       geo,
		HostHTTPS: fmt.Sprintf("derp%v.tailscale.com", id),
		STUN4:     fmt.Sprintf("derp%v.tailscale.com:3478", id),
		STUN6:     fmt.Sprintf("derp%v-v6.tailscale.com:3478", id),
	})
}

func (w *World) add(s *Server) {
	if s.ID == 0 {
		panic("ID required")
	}
	if _, dup := w.byID[s.ID]; dup {
		panic("duplicate prod server")
	}
	if w.byID == nil {
		w.byID = make(map[int]*Server)
	}
	w.byID[s.ID] = s
	w.ids = append(w.ids, s.ID)
	w.servers = append(w.servers, s)
	if s.STUN4 != "" {
		w.stun4 = append(w.stun4, s.STUN4)
	}
	if s.STUN6 != "" {
		w.stun6 = append(w.stun6, s.STUN6)
	}
}

func init() {
	addProd(1, "New York")
	addProd(2, "San Francisco")
	addProd(3, "Singapore")
	addProd(4, "Frankfurt")
}

// Server is configuration for a DERP server.
type Server struct {
	ID int

	// HostHTTPS is the HTTPS hostname.
	HostHTTPS string

	// STUN4 is the host:port of the IPv4 STUN server on this DERP
	// node. Required.
	STUN4 string

	// STUN6 optionally provides the IPv6 host:port of the STUN
	// server on the DERP node.
	// It should be an IPv6-only address for now. (We currently make lazy
	// assumptions that the server names are unique.)
	STUN6 string

	// Geo is a human-readable geographic region name of this server.
	Geo string
}

func (s *Server) String() string {
	if s == nil {
		return "<nil *derpmap.Server>"
	}
	if s.Geo != "" {
		return fmt.Sprintf("%v (%v)", s.HostHTTPS, s.Geo)
	}
	return s.HostHTTPS
}