package main

import (
	"net"
	"net/http"
	"net/http/cgi"
	"sync"
	"time"
)

type server struct {
	http.Server
	Addr     string // host:port where the server is listening.
	err      error
	cond     *sync.Cond
	running  bool
	listener *net.TCPListener
	wantDown bool
}

func (srv *server) Start() error {
	gitHandler := &cgi.Handler{
		Path: theConfig.GitCommand,
		Dir:  theConfig.Root,
		Env: []string{
			"GIT_PROJECT_ROOT=" + theConfig.Root,
			"GIT_HTTP_EXPORT_ALL=",
		},
		InheritEnv: []string{"PATH"},
		Args:       []string{"http-backend"},
	}

	// The rest of the work here is essentially
	// http.ListenAndServe() with two more features: (1) whoever
	// called Start() can discover which address:port we end up
	// listening to -- which makes listening on ":0" useful in
	// test suites -- and (2) the server can be shut down without
	// killing the process -- which is useful in test cases, and
	// makes it possible to shut down gracefully on SIGTERM
	// without killing active connections.

	addr, err := net.ResolveTCPAddr("tcp", theConfig.Addr)
	if err != nil {
		return err
	}
	srv.listener, err = net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}
	srv.Addr = srv.listener.Addr().String()
	mux := http.NewServeMux()
	mux.Handle("/", &authHandler{gitHandler})
	srv.Handler = mux

	mutex := &sync.RWMutex{}
	srv.cond = sync.NewCond(mutex.RLocker())
	srv.running = true
	go func() {
		err = srv.Serve(tcpKeepAliveListener{srv.listener})
		if !srv.wantDown {
			srv.err = err
		}
		mutex.Lock()
		srv.running = false
		srv.cond.Broadcast()
		mutex.Unlock()
	}()
	return nil
}

// Wait returns when the server has shut down.
func (srv *server) Wait() error {
	if srv.cond == nil {
		return nil
	}
	srv.cond.L.Lock()
	defer srv.cond.L.Unlock()
	for srv.running {
		srv.cond.Wait()
	}
	return srv.err
}

// Close shuts down the server and returns when it has stopped.
func (srv *server) Close() error {
	srv.wantDown = true
	srv.listener.Close()
	return srv.Wait()
}

// tcpKeepAliveListener is copied from net/http because not exported.
//
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}