package main

import (
	"crypto/sha256"
	"flag"
	"fmt"
	"hash"
	"log"
	"math/rand"
	"net"
	"net/http"
	"sort"
	"sync"
	"time"

	"golang.org/x/net/netutil"
)

func main() {
	maxConns := flag.Int("max-conns", 5, "Maximum number of concurrent TCP connections to serve")
	maxHandle := flag.Int("max-handle", 5, "Maximum number of concurrent HTTP requests to serve")
	flag.Parse()

	var handler http.Handler
	handler = &hashHandler{state: sha256.New()}
	handler = &concurrencyHandler{
		Handler: handler,
		Limit:   *maxHandle,
		Spillover: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			code := http.StatusServiceUnavailable
			http.Error(w, http.StatusText(code), code)
		}),
	}

	mux := http.NewServeMux()
	mux.Handle("/", handler)

	srv := http.Server{
		Handler: mux,
	}

	go func() {
		rng := rand.New(rand.NewSource(time.Now().Unix()))
		sleeps := make([]time.Duration, 3)
		for {
			delay := 5 * time.Second
			delay = time.Duration(rng.Int63n(int64(delay)))
			time.Sleep(delay)

			prev := time.Now()
			for i := range sleeps {
				time.Sleep(1 * time.Microsecond)
				now := time.Now()
				sleeps[i] = now.Sub(prev)
				prev = now
			}
			sort.Slice(sleeps, func(i, j int) bool { return sleeps[i] < sleeps[j] })
			median := sleeps[len(sleeps)/2]
			log.Printf("median-sleep=%dus", median.Microseconds())
		}
	}()

	l, err := net.Listen("tcp", "0.0.0.0:3386")
	if err != nil {
		log.Fatalf("net.Listen: %v", err)
	}
	defer l.Close()

	l = netutil.LimitListener(l, *maxConns)

	err = srv.Serve(l)
	if err != nil {
		log.Fatalf("Serve: %v", err)
	}
}

type hashHandler struct {
	mu    sync.Mutex
	state hash.Hash
}

func (h *hashHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	sum := func() []byte {
		h.mu.Lock()
		defer h.mu.Unlock()
		for i := 0; i < 100; i++ {
			fmt.Fprintf(h.state, "%q\n", r.RequestURI)
		}
		return h.state.Sum(nil)
	}()

	fmt.Fprintf(w, "%02x\n", sum)
}

type concurrencyHandler struct {
	Handler   http.Handler
	Spillover http.Handler
	Limit     int

	mu     sync.Mutex
	active int
}

func (h *concurrencyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	target := h.Spillover

	h.mu.Lock()
	if h.active < h.Limit {
		target = h.Handler
		h.active++
		defer func() {
			h.mu.Lock()
			h.active--
			h.mu.Unlock()
		}()
	}
	h.mu.Unlock()

	target.ServeHTTP(w, r)
}
