package main

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

type readOp struct {
	key int
	resp chan int
}

type writeOp struct {
	key int
	val int
	resp chan bool
}

func main() {

	const nReaders int = 100
	const nWriters int = 10

	var ops int64 = 0

	reads := make(chan *readOp)
	writes := make(chan *writeOp)

	monitorDone := make(chan bool)
	readerDone := make(chan int) // for reader work done notification
	writerDone := make(chan int) // for writer work done notification

	// as a monitor, receives:
	//  all read/write requests
	//  reader/writer work done notification
	// sends:
	//  monitor done after all readers and writers done
	go func() {
		var state = make(map[int]int) // all data now placed here
		var doneReaders int = 0
		var doneWriters int = 0

		for {
			select {
			case read := <-reads:
				read.resp <- state[read.key] // reply requests value
			case write := <-writes:
				state[write.key] = write.val
				write.resp <- true // reply write success
			case /*rid :=*/ <-readerDone:
				doneReaders += 1
				if doneReaders == nReaders && doneWriters == nWriters {
					fmt.Println("state:", state)
					monitorDone <- true
					break
				}
			case /*wid :=*/ <-writerDone:
				doneWriters += 1
				if doneReaders == nReaders && doneWriters == nWriters {
					fmt.Println("state:", state)
					monitorDone <- true
					break
				}
			}
		}
	}()


	// readers
	for r := 0; r < nReaders; r++ {
		go func() {
			for t := 0; t < 10000; t++ {
				read := &readOp {
					key: t % 5,  // rand.Intn(5)
					resp: make(chan int)}
				reads <- read
				<-read.resp
				atomic.AddInt64(&ops, 1)
			}
			readerDone <- r
		}()
	}


	// writers
	for w := 0; w < nWriters; w++ {
		go func() {
			for t := 0; t < 10000; t++ {
				write := &writeOp {
					key: t % 5,   // rand.Intn(5)
					val: t % 100, // rand.Intn(100)
					resp: make(chan bool)}
				writes <- write
				<-write.resp
				atomic.AddInt64(&ops, 1)
			}
			writerDone <- w
		}()
	}


	// wait monitor done
	<-monitorDone

	opsFinal := atomic.LoadInt64(&ops)
	fmt.Println("ops:", opsFinal)
}