
const GUID{{.Name}} string = "{{.GUID}}"
const Signature{{.Name}} string = "{{.Signature}}"

type {{.Name}} struct {
	ole.IUnknown
	sync.Mutex
	refs uintptr
	IID  ole.GUID
}

type {{.Name}}Vtbl struct {
	ole.IUnknownVtbl
	Invoke uintptr
}

type {{.Name}}Callback func(instance *{{.Name}},{{- range .InParams -}}
	{{.GoVarName}} {{template "variabletype.tmpl" . }},
{{- end -}})

var callbacks{{.Name}} = &{{.Name | toLower}}Callbacks {
	mu:        &sync.Mutex{},
	callbacks: make(map[unsafe.Pointer]{{.Name}}Callback),
}

var releaseChannels{{.Name}} = &{{.Name | toLower}}ReleaseChannels {
	mu:        &sync.Mutex{},
	chans: make(map[unsafe.Pointer]chan struct{}),
}

func New{{.Name}}(iid *ole.GUID, callback {{.Name}}Callback) *{{.Name}} {
	// create type instance
	size := unsafe.Sizeof(*(*{{.Name}})(nil))
	instPtr := kernel32.Malloc(size)
	inst := (*{{.Name}})(instPtr)
	
	// get the callbacks for the VTable
	callbacks := delegate.RegisterCallbacks(instPtr, inst)

	// the VTable should also be allocated in the heap
	sizeVTable := unsafe.Sizeof(*(*{{.Name}}Vtbl)(nil))
	vTablePtr := kernel32.Malloc(sizeVTable)

	inst.RawVTable = (*interface{})(vTablePtr)
	
	vTable := (*{{.Name}}Vtbl)(vTablePtr)
	vTable.IUnknownVtbl = ole.IUnknownVtbl{
			QueryInterface: callbacks.QueryInterface,
			AddRef:         callbacks.AddRef,
			Release:        callbacks.Release,
	}
	vTable.Invoke = callbacks.Invoke

	// Initialize all properties: the malloc may contain garbage
	inst.IID = *iid // copy contents
	inst.Mutex = sync.Mutex{}
	inst.refs = 0

	callbacks{{.Name}}.add(unsafe.Pointer(inst), callback)

	// See the docs in the releaseChannels{{.Name}} struct
	releaseChannels{{.Name}}.acquire(unsafe.Pointer(inst))

	inst.addRef()
	return inst
}

func (r *{{.Name}}) GetIID() *ole.GUID {
	return &r.IID
}

// addRef increments the reference counter by one
func (r *{{.Name}}) addRef() uintptr {
	r.Lock()
	defer r.Unlock()
	r.refs++
	return r.refs
}

// removeRef decrements the reference counter by one. If it was already zero, it will just return zero.
func (r *{{.Name}}) removeRef() uintptr {
	r.Lock()
	defer r.Unlock()

	if r.refs > 0 {
		r.refs--
	}

	return r.refs
}

func (instance *{{.Name}}) Invoke(instancePtr, rawArgs0, rawArgs1, rawArgs2, rawArgs3, rawArgs4, rawArgs5, rawArgs6, rawArgs7, rawArgs8 unsafe.Pointer) uintptr {
	{{range $i, $arg := .InParams -}}
			{{- if $arg.Type.IsEnum -}}
					{{$arg.GoVarName}}Raw := ({{$arg.Type.UnderlyingEnumType}})(uintptr(rawArgs{{$i}}))
			{{- else -}}
					{{$arg.GoVarName}}Ptr := rawArgs{{$i}}
			{{- end}}
	{{end}} 

	// See the quote above.
	{{range .InParams -}}
			{{if .Type.IsEnum -}}
					{{.GoVarName}} := ({{template "variabletype.tmpl" . }})({{.GoVarName}}Raw)
			{{else -}}
					{{.GoVarName}} := ({{template "variabletype.tmpl" . }})({{.GoVarName}}Ptr)
			{{end -}}
	{{end -}}
	if callback, ok := callbacks{{.Name}}.get(instancePtr); ok {
		callback(instance, {{range .InParams}}{{.GoVarName}},{{end}})
	}
	return ole.S_OK
}

func (instance *{{.Name}}) AddRef() uintptr {
	return instance.addRef()
}

func (instance *{{.Name}}) Release() uintptr {
	rem := instance.removeRef()
	if rem == 0 {
		// We're done.
		instancePtr := unsafe.Pointer(instance)
		callbacks{{.Name}}.delete(instancePtr)

		// stop release channels used to avoid
		// https://github.com/golang/go/issues/55015
		releaseChannels{{.Name}}.release(instancePtr)

		kernel32.Free(unsafe.Pointer(instance.RawVTable))
		kernel32.Free(instancePtr)
	}
	return rem
}

type {{.Name | toLower}}Callbacks struct {
	mu        *sync.Mutex
	callbacks map[unsafe.Pointer]{{.Name}}Callback
}

func (m *{{.Name | toLower}}Callbacks) add(p unsafe.Pointer, v {{.Name}}Callback) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.callbacks[p] = v
}

func (m *{{.Name | toLower}}Callbacks) get(p unsafe.Pointer) ({{.Name}}Callback, bool) {
	m.mu.Lock()
	defer m.mu.Unlock()

	v, ok := m.callbacks[p]
	return v, ok
}

func (m *{{.Name | toLower}}Callbacks) delete(p unsafe.Pointer) {
	m.mu.Lock()
	defer m.mu.Unlock()

	delete(m.callbacks, p)
}

// typedEventHandlerReleaseChannels keeps a map with channels
// used to keep a goroutine alive during the lifecycle of this object.
// This is required to avoid causing a deadlock error.
// See this: https://github.com/golang/go/issues/55015
type {{.Name | toLower}}ReleaseChannels struct {
	mu        *sync.Mutex
	chans map[unsafe.Pointer]chan struct{}
}

func (m *{{.Name | toLower}}ReleaseChannels) acquire(p unsafe.Pointer) {
	m.mu.Lock()
	defer m.mu.Unlock()

	c := make(chan struct{})
	m.chans[p] = c

	go func() {
		// we need a timer to trick the go runtime into
		// thinking there's still something going on here
		// but we are only really interested in <-c
		t := time.NewTimer(time.Minute)
		for {
			select {
			case <-t.C:
				t.Reset(time.Minute)
			case <-c:
				t.Stop()
				return
			}
		}
	}()
}

func (m *{{.Name | toLower}}ReleaseChannels) release(p unsafe.Pointer) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if c, ok := m.chans[p]; ok {
		close(c)
		delete(m.chans, p)
	}
}
