//go:build scheduler.threads

package runtime

import (
	"internal/task"
	"runtime/interrupt"
)

const hasScheduler = false // not using the cooperative scheduler

// We use threads, so yes there is parallelism.
const hasParallelism = true

var (
	timerQueueLock    task.PMutex
	timerQueueStarted bool
	timerFutex        task.Futex
)

// Because we just use OS threads, we don't need to do anything special here. We
// can just initialize everything and run main.main on the main thread.
func run() {
	initHeap()
	task.Init(stackTop)
	initAll()
	callMain()
}

// Pause the current task for a given time.
//
//go:linkname sleep time.Sleep
func sleep(duration int64) {
	if duration <= 0 {
		return
	}

	sleepTicks(nanosecondsToTicks(duration))
}

func deadlock() {
	// TODO: exit the thread via pthread_exit.
	task.Pause()
}

func scheduleTask(t *task.Task) {
	t.Resume()
}

func Gosched() {
	// Each goroutine runs in a thread, so there's not much we can do here.
	// There is sched_yield but it's only really intended for realtime
	// operation, so is probably best not to use.
}

// NumCPU returns the number of logical CPUs usable by the current process.
func NumCPU() int {
	return task.NumCPU()
}

// Separate goroutine (thread) that runs timer callbacks when they expire.
func timerRunner() {
	for {
		timerQueueLock.Lock()

		if timerQueue == nil {
			// No timer in the queue, so wait until one becomes available.
			val := timerFutex.Load()
			timerQueueLock.Unlock()
			timerFutex.Wait(val)
			continue
		}

		now := ticks()
		if now < timerQueue.whenTicks() {
			// There is a timer in the queue, but we need to wait until it
			// expires.
			// Using a futex, so that the wait is exited early when adding a new
			// (sooner-to-expire) timer.
			val := timerFutex.Load()
			timerQueueLock.Unlock()
			timeout := ticksToNanoseconds(timerQueue.whenTicks() - now)
			timerFutex.WaitUntil(val, uint64(timeout))
			continue
		}

		// Pop timer from queue.
		tn := timerQueue
		timerQueue = tn.next
		tn.next = nil

		timerQueueLock.Unlock()

		// Run the callback stored in this timer node.
		delay := ticksToNanoseconds(now - tn.whenTicks())
		tn.callback(tn, delay)
	}
}

func addTimer(tim *timerNode) {
	timerQueueLock.Lock()

	if !timerQueueStarted {
		timerQueueStarted = true
		go timerRunner()
	}

	timerQueueAdd(tim)

	timerFutex.Add(1)
	timerFutex.Wake()

	timerQueueLock.Unlock()
}

func removeTimer(tim *timer) *timerNode {
	timerQueueLock.Lock()
	n := timerQueueRemove(tim)
	timerQueueLock.Unlock()
	return n
}

func schedulerRunQueue() *task.Queue {
	// This function is not actually used, it is only called when hasScheduler
	// is true. So we can just return nil here.
	return nil
}

func runqueueForGC() *task.Queue {
	// There is only a runqueue when using the cooperative scheduler.
	return nil
}

// Lock to make sure print calls do not interleave.
var printLock task.Mutex

func printlock() {
	printLock.Lock()
}

func printunlock() {
	printLock.Unlock()
}

// The atomics lock isn't used as a lock for actual atomics. It is used inside
// internal/task.Stack and internal/task.Queue to make sure their operations are
// actually atomic. (This might not actually be needed, since the use in
// sync.Cond doesn't need atomicity).

var atomicsLock task.Mutex

func lockAtomics() interrupt.State {
	atomicsLock.Lock()
	return 0
}

func unlockAtomics(mask interrupt.State) {
	atomicsLock.Unlock()
}
