// +build ignore,OMIT

package main

import (
	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	F           = 2
	N           = 5
	ReadQuorum  = F + 1
	WriteQuorum = N - F
)

var delay = false

type Server struct {
	mu   sync.Mutex
	data map[string]*Data
}

type Data struct {
	Key   string
	Value string
	Time  time.Time
}

func (srv *Server) Delay() {
	if delay == false {
		return
	}
	time.Sleep(time.Duration(math.Abs(rand.NormFloat64()*1e9 + 0.1e9)))
}

func (srv *Server) Write(req *Data) {
	t0 := time.Now()
	defer func() {
		if delay {
			fmt.Printf("write took %.3f seconds\n", time.Since(t0).Seconds())
		}
	}()

	srv.mu.Lock()
	defer srv.mu.Unlock()
	srv.Delay()

	if srv.data == nil {
		srv.data = make(map[string]*Data)
	}
	if d := srv.data[req.Key]; d == nil || d.Time.Before(req.Time) {
		srv.data[req.Key] = req
	}
}

func (srv *Server) Read(key string) *Data {
	t0 := time.Now()
	defer func() {
		fmt.Printf("read took %.3f seconds\n", time.Since(t0).Seconds())
	}()

	srv.mu.Lock()
	defer srv.mu.Unlock()
	srv.Delay()

	return srv.data[key]
}

func better(x, y *Data) *Data {
	if x == nil {
		return y
	}
	if y == nil || y.Time.Before(x.Time) {
		return x
	}
	return y
}

func Write(req *Data) {
	t0 := time.Now()
	done := make(chan bool, len(servers))

	for _, srv := range servers {
		go func(srv *Server) {
			srv.Write(req)
			done <- true
		}(srv)
	}

	for n := 0; n < WriteQuorum; n++ {
		<-done
	}

	if delay {
		fmt.Printf("write committed at %.3f seconds\n", time.Since(t0).Seconds())
	}
	for n := WriteQuorum; n < N; n++ {
		<-done
	}
	if delay {
		fmt.Printf("all replicas written at %.3f seconds\n", time.Since(t0).Seconds())
	}
}

func Read(key string) {
	t0 := time.Now()
	replies := make(chan *Data, len(servers))

	for _, srv := range servers {
		go func(srv *Server) {
			replies <- srv.Read(key)
		}(srv)
	}

	var d *Data
	for n := 0; n < ReadQuorum; n++ {
		d = better(d, <-replies)
	}

	if delay {
		fmt.Printf("read committed at %.3f seconds\n", time.Since(t0).Seconds())
	}

	for n := ReadQuorum; n < N; n++ {
		<-replies
	}
	if delay {
		fmt.Printf("all replicas read at %.3f seconds\n", time.Since(t0).Seconds())
	}
}

var servers []*Server

func main() {
	servers = make([]*Server, N)
	for i := range servers {
		servers[i] = new(Server)
	}

	rand.Seed(time.Now().UnixNano())

	delay = false
	Write(&Data{"hello", "there", time.Now()})
	time.Sleep(1 * time.Millisecond)

	delay = true
	Write(&Data{"hello", "world", time.Now()})

	//	Read("hello")
}
