//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() {
	initRand()
	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() {
	task.Pause()
}

func goexit() {
	task.Exit()
}

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()
			timeout := ticksToNanoseconds(timerQueue.whenTicks() - now)
			timerQueueLock.Unlock()
			timerFutex.WaitUntil(val, uint64(timeout))
			continue
		}

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

		// Mark the timer as firing, so that a concurrent Stop or Reset (via
		// removeTimer) can prevent a periodic timer from re-adding itself in its
		// callback below.
		firingTimersAdd(tn)

		timerQueueLock.Unlock()

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

		// The callback has finished running. A periodic timer (a ticker) already
		// removed itself from the firing list in reAddTimer; a one-shot timer
		// isn't re-added, so remove it from the firing list here.
		timerQueueLock.Lock()
		if tn.timer.period == 0 {
			firingTimersRemove(tn)
		}
		timerQueueLock.Unlock()
	}
}

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

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

	timerQueueAdd(tim)

	timerFutex.Add(1)
	timerFutex.Wake()

	timerQueueLock.Unlock()
}

// reAddTimer advances and re-adds a periodic timer (a ticker) after its
// callback has run, unless it was stopped or reset while the callback was
// running (in which case it must not be re-added).
func reAddTimer(tn *timerNode) {
	timerQueueLock.Lock()

	// Remove the timer from the firing list before re-adding it to the queue,
	// so that another core popping it off the queue can't insert it into the
	// firing list a second time (which would corrupt the list).
	firingTimersRemove(tn)

	if tn.stopped {
		// The timer was stopped or reset while its callback was running. Don't
		// re-add it: a stopped ticker must stay stopped, and a reset ticker has
		// already been re-added by resetTimer.
		timerQueueLock.Unlock()
		return
	}

	tn.timer.when += tn.timer.period
	timerQueueAdd(tn)

	timerFutex.Add(1)
	timerFutex.Wake()

	timerQueueLock.Unlock()
}

func removeTimer(tim *timer) *timerNode {
	timerQueueLock.Lock()
	n := timerQueueRemove(tim)
	if n == nil {
		// The timer wasn't in the queue. It might be running its callback right
		// now; if so, mark it stopped so it won't be re-added.
		firingTimerStop(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()
}
