package server

import (
	"sync"
	"time"
)

// timeoutManager represents a discrete encapsulation of timeout functionality.
// this struct expose 3 functions:
//   - SetTimeout
//   - StartTimeout
//   - StopTimeout
type timeoutManager struct {
	// timeout number of milliseconds the timeout operation will run before executing the `terminate` func()
	// 0 represents an inactive timeout
	timeout uint

	// exitQueue handles the cancel signal channels that circumvent timeout operations and prevent the
	// execution of any `terminate` func()
	exitQueue *exitQueueManager
}

// newTimeoutManager returns a fully qualified and initialised timeoutManager
func newTimeoutManager() *timeoutManager {
	return &timeoutManager{
		exitQueue: &exitQueueManager{queue: []chan struct{}{}},
	}
}

// SetTimeout sets the value of the timeoutManager.timeout
func (t *timeoutManager) SetTimeout(milliseconds uint) {
	t.timeout = milliseconds
}

// StartTimeout starts a timeout operation based on the set timeoutManager.timeout value
// the given terminate func() will be executed once the timeout duration has passed
func (t *timeoutManager) StartTimeout(terminate func()) {
	if t.timeout == 0 {
		return
	}
	t.StopTimeout()

	exit := make(chan struct{}, 1)
	t.exitQueue.add(exit)
	go t.run(terminate, exit)
}

// StopTimeout terminates a timeout operation and exits gracefully
func (t *timeoutManager) StopTimeout() {
	if t.timeout == 0 {
		return
	}
	t.exitQueue.empty()
}

// run inits the main timeout run function that awaits for the exit command to be triggered or for the
// timeout duration to elapse and trigger the parameter terminate function.
func (t *timeoutManager) run(terminate func(), exit chan struct{}) {
	select {
	case <-exit:
		return
	case <-time.After(time.Duration(t.timeout) * time.Millisecond):
		terminate()
		// TODO fire signal to let UI know
		//  https://github.com/status-im/status-go/issues/3305
		return
	}
}

// exitQueueManager
type exitQueueManager struct {
	queue     []chan struct{}
	queueLock sync.Mutex
}

// add handles new exit channels adding them to the exit queue
func (e *exitQueueManager) add(exit chan struct{}) {
	e.queueLock.Lock()
	defer e.queueLock.Unlock()

	e.queue = append(e.queue, exit)
}

// empty sends a signal to every exit channel in the queue and then resets the queue
func (e *exitQueueManager) empty() {
	e.queueLock.Lock()
	defer e.queueLock.Unlock()

	for i := range e.queue {
		e.queue[i] <- struct{}{}
	}

	e.queue = []chan struct{}{}
}