package lotsa

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"time"
)

// Output is used to print elased time and ops/sec
var Output io.Writer

// MemUsage is used to output the memory usage
var MemUsage bool

// Ops executed a number of operations over a multiple goroutines.
// count is the number of operations.
// threads is the number goroutines.
// op is the operation function
func Ops(count, threads int, op func(i, thread int)) {
	var start time.Time
	var wg sync.WaitGroup
	wg.Add(threads)
	var ms1 runtime.MemStats
	output := Output
	if output != nil {
		if MemUsage {
			runtime.GC()
			runtime.ReadMemStats(&ms1)
		}
		start = time.Now()
	}
	for i := 0; i < threads; i++ {
		s, e := count/threads*i, count/threads*(i+1)
		if i == threads-1 {
			e = count
		}
		go func(i, s, e int) {
			defer wg.Done()
			for j := s; j < e; j++ {
				op(j, i)
			}
		}(i, s, e)
	}
	wg.Wait()

	if output != nil {
		dur := time.Since(start)
		var alloc uint64
		if MemUsage {
			runtime.GC()
			var ms2 runtime.MemStats
			runtime.ReadMemStats(&ms2)
			if ms1.HeapAlloc > ms2.HeapAlloc {
				alloc = 0
			} else {
				alloc = ms2.HeapAlloc - ms1.HeapAlloc
			}
		}
		WriteOutput(output, count, threads, dur, alloc)
	}
}

func commaize(n int) string {
	s1, s2 := fmt.Sprintf("%d", n), ""
	for i, j := len(s1)-1, 0; i >= 0; i, j = i-1, j+1 {
		if j%3 == 0 && j != 0 {
			s2 = "," + s2
		}
		s2 = string(s1[i]) + s2
	}
	return s2
}

func memstr(alloc uint64) string {
	switch {
	case alloc <= 1024:
		return fmt.Sprintf("%d bytes", alloc)
	case alloc <= 1024*1024:
		return fmt.Sprintf("%.1f KB", float64(alloc)/1024)
	case alloc <= 1024*1024*1024:
		return fmt.Sprintf("%.1f MB", float64(alloc)/1024/1024)
	default:
		return fmt.Sprintf("%.1f GB", float64(alloc)/1024/1024/1024)
	}
}

// WriteOutput writes an output line to the specified writer
func WriteOutput(w io.Writer, count, threads int, elapsed time.Duration, alloc uint64) {
	var ss string
	if threads != 1 {
		ss = fmt.Sprintf("over %d threads ", threads)
	}
	var nsop int
	if count > 0 {
		nsop = int(elapsed / time.Duration(count))
	}
	var allocstr string
	if alloc > 0 {
		var bops uint64
		if count > 0 {
			bops = alloc / uint64(count)
		}
		allocstr = fmt.Sprintf(", %s, %d bytes/op", memstr(alloc), bops)
	}
	fmt.Fprintf(w, "%s ops %sin %.0fms, %s/sec, %d ns/op%s\n",
		commaize(count), ss, elapsed.Seconds()*1000,
		commaize(int(float64(count)/elapsed.Seconds())),
		nsop, allocstr,
	)
}
