package eval

import (
	"errors"
	"os"
	"os/signal"
	"syscall"
)

// Interrupts returns a channel that is closed when an interrupt signal comes.
func (fm *Frame) Interrupts() <-chan struct{} {
	return fm.intCh
}

// ErrInterrupted is thrown when the execution is interrupted by a signal.
var ErrInterrupted = errors.New("interrupted")

// IsInterrupted reports whether there has been an interrupt.
func (fm *Frame) IsInterrupted() bool {
	select {
	case <-fm.Interrupts():
		return true
	default:
		return false
	}
}

// ListenInterrupts returns a channel that is closed when SIGINT or SIGQUIT
// has been received by the process. It also returns a function that should be
// called when the channel is no longer needed.
func ListenInterrupts() (<-chan struct{}, func()) {
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT)
	// Channel to return, closed after receiving the first SIGINT or SIGQUIT.
	intCh := make(chan struct{})

	// Closed in the cleanup function to request the relaying goroutine to stop.
	stop := make(chan struct{})
	// Closed in the relaying goroutine to signal that it has stopped.
	stopped := make(chan struct{})

	go func() {
		closed := false
	loop:
		for {
			select {
			case <-sigCh:
				if !closed {
					close(intCh)
					closed = true
				}
			case <-stop:
				break loop
			}
		}
		signal.Stop(sigCh)
		close(stopped)
	}()

	return intCh, func() {
		close(stop)
		<-stopped
	}
}
