package main

import (
	"flag"
	"fmt"
	"math/rand"
	"os"
	"sync/atomic"
	"time"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
)

func handshake(stop chan struct {}, c chan [] uint64, data [] uint64, share bool) (bool, [] uint64) {
	var s [] uint64 = data
	if !share {
		s = nil
	}

	// send the data
	select {
	case <- stop:
		return true, nil
	case c <- s:
	}

	// get the new data chunk
	select {
	case <- stop:
		return true, nil
	case n := <- c:
		if share {
			return false, n
		}
		return false, data
	}
}

func local(result chan uint64, start chan struct{}, stop chan struct{}, size uint64, cnt uint64, channels []chan [] uint64, chan_cnt uint64, share bool) {
	var data [] uint64
	data = make([]uint64, size)
	for i := uint64(0); i < size; i++ {
		data[i] = 0
	}
	count := uint64(0)
	<- start
	for true {
		for i := uint64(0); i < cnt; i++ {
			data[rand.Uint64() % size] += 1
		}

		i := rand.Uint64() % chan_cnt
		var closed bool
		closed, data = handshake(stop, channels[i], data, share)
		count += 1

		if  closed { break }
		if !clock_mode && count >= stop_count { break }
	}

	atomic.AddInt64(&threads_left, -1);
	result <- count
}

func main() {

	work_sizeOpt := flag.Uint64("w", 2    , "Number of words (uint64) per threads")
	countOpt     := flag.Uint64("c", 2    , "Number of words (uint64) to touch")
	shareOpt     := flag.Bool  ("s", false, "Pass the work data to the next thread when blocking")

	bench_init()

	size  := *work_sizeOpt
	cnt   := *countOpt
	share := *shareOpt

	if ! (nthreads > nprocs) {
		fmt.Fprintf(os.Stderr, "Must have more threads than procs\n")
		os.Exit(1)
	}

	barrierStart := make(chan struct{})
	barrierStop  := make(chan struct{})
	threads_left = int64(nthreads)
	result  := make(chan uint64)
	channels := make([]chan [] uint64, nthreads - nprocs)
	for i := range channels {
		channels[i] = make(chan [] uint64, 1)
	}

	for i := 0; i < nthreads; i++ {
		go local(result, barrierStart, barrierStop, size, cnt, channels, uint64(nthreads - nprocs), share)
	}
	fmt.Printf("Starting\n");

	start := time.Now()
	close(barrierStart)

	wait(start, true);

	close(barrierStop)
	end := time.Now()
	delta := end.Sub(start)

	fmt.Printf("\nDone\n")

	global_counter := uint64(0)
	for i := 0; i < nthreads; i++ {
		global_counter += <- result
	}

	p := message.NewPrinter(language.English)
	p.Printf("Duration (ms)          : %f\n", delta.Seconds());
	p.Printf("Number of processors   : %d\n", nprocs);
	p.Printf("Number of threads      : %d\n", nthreads);
	p.Printf("Work size (64bit words): %d\n", size);
	p.Printf("Total Operations(ops)  : %15d\n", global_counter)
	p.Printf("Ops per second         : %18.2f\n", float64(global_counter) / delta.Seconds())
	p.Printf("ns per ops             : %18.2f\n", float64(delta.Nanoseconds()) / float64(global_counter))
	p.Printf("Ops per threads        : %15d\n", global_counter / uint64(nthreads))
	p.Printf("Ops per procs          : %15d\n", global_counter / uint64(nprocs))
	p.Printf("Ops/sec/procs          : %18.2f\n", (float64(global_counter) / float64(nprocs)) / delta.Seconds())
	p.Printf("ns per ops/procs       : %18.2f\n", float64(delta.Nanoseconds()) / (float64(global_counter) / float64(nprocs)))
}