package main

import (
	"fmt"
	"os"
	"runtime"
	"sync"
	"time"
	"unsafe"

	"git.wow.st/gmp/nswrap/examples/strings/ns"
)

func incr() func(bool) (int, float64) {
	i := 0
	b := 0.0
	var mx sync.Mutex
	return func(bad bool) (int, float64) {
		mx.Lock()
		if bad {
			b++
			defer mx.Unlock()
		} else {
			defer func() { i++; mx.Unlock() }()
		}
		if b == 0 {
			return i, 0.0
		} else {
			return i, (b / float64(i)) * 100
		}
	}
}

type tracker struct {
	add, drop func(*ns.Id)
	check     func()
	i         func(bool) (int, float64)
}

type record struct {
	ptr   unsafe.Pointer
	goPtr *ns.Id
	when  time.Time
}

func newTracker() (func(*ns.Id), func(*ns.Id), func()) {
	addch := make(chan *ns.Id)
	dropch := make(chan *ns.Id)
	data := []record{}
	var mux sync.Mutex

	go func() {
		for {
			select {
			case x := <-addch:
				mux.Lock()
				data = append(data, record{
					x.Ptr(),
					x,
					time.Now(),
				})
				mux.Unlock()
			case x := <-dropch:
				mux.Lock()
				data = append(data, record{
					nil,
					x,
					time.Now(),
				})
				mux.Unlock()
			}
		}
	}()

	add := func(x *ns.Id) {
		addch <- x
	}
	drop := func(x *ns.Id) {
		dropch <- x
	}
	check := func() {
		live := map[unsafe.Pointer]*ns.Id{}
		bad := false
		mux.Lock()
		for _, r := range data {
			if r.ptr != nil {
				if live[r.ptr] != nil {
					fmt.Printf("COLLISION: %p & %p -> %p\n", r.goPtr, live[r.ptr], r.ptr)
					bad = true
				}
				live[r.ptr] = r.goPtr
			} else {
				delete(live, r.ptr)
			}
		}
		fmt.Printf("Checked %d records -- ", len(data))
		if bad {
			fmt.Printf("failed\n")
		} else {
			fmt.Printf("ok\n")
		}
		mux.Unlock()
	}
	return add, drop, check
}

func mkstrings(t tracker) {
	for {
		//fmt.Printf("main thread: %t\n",ns.NSThreadIsMainThread())
		x, b := t.i(false)
		str := fmt.Sprintf("string %d", x)
		s := ns.NSStringWithGoString(str)
		//t.add(&s.Id)
		for j := 0; j < 10; j++ {
			sout := s.String()
			if str != sout {
				_, b = t.i(true)
				fmt.Printf("%3.2f%% -- %d: '%s' '%s'\n", b, x, str, sout)
			}
			time.Sleep(time.Second / 1000)
		}
		if x%1000 == 0 {
			fmt.Printf("%3.2f%% -- %s\n", b, time.Now().Format("03:04:05.000"))
		}
		//t.drop(&s.Id)
	}
}

func main() {
	runtime.GOMAXPROCS(4)
	fmt.Printf("Starting\n")
	//ns.NSThreadNew().Start()
	fmt.Printf("multithreaded: %t\n", ns.NSThreadIsMultiThreaded())
	//pool := ns.NSAutoreleasePoolAlloc()
	add, drop, check := newTracker()
	i := tracker{add, drop, check, incr()}
	go mkstrings(i)
	go mkstrings(i)
	go mkstrings(i)
	go mkstrings(i)
	go mkstrings(i)
	go mkstrings(i)
	go mkstrings(i)
	go mkstrings(i)
	go func() {
		for {
			runtime.GC()
			time.Sleep(time.Second / 100)
		}
	}()

	time.Sleep(time.Second * 600)
	i.check()

	//pool.Drain()
	os.Exit(0)
}
