package daemon

import (
	"sync/atomic"
	"syscall"
	"time"

	"github.com/juju/errors"
)

// Signal defines the types of signal which is sent to daemon to impact process running
type Signal int

// Enum values of the Signal type
const (
	SignalAlrm Signal = iota
	SignalCont
	SignalDown
	SignalHup
	SignalRestart
	SignalInterrupt
	SignalTtin
	SignalKill
	SignalTtou
	SignalStop
	SignalQuit
	SignalUp
	SignalUsr1
	SignalUsr2
	SignalWinch
)

func (s Signal) String() string {
	switch s {
	case SignalAlrm:
		return "ALRM"
	case SignalCont:
		return "CONT"
	case SignalDown:
		return "DOWN"
	case SignalHup:
		return "HUP"
	case SignalRestart:
		return "RESTART"
	case SignalInterrupt:
		return "INTERRUPT"
	case SignalTtin:
		return "TTIN"
	case SignalKill:
		return "KILL"
	case SignalTtou:
		return "TTOU"
	case SignalStop:
		return "STOP"
	case SignalQuit:
		return "QUIT"
	case SignalUp:
		return "UP"
	case SignalUsr1:
		return "USR1"
	case SignalUsr2:
		return "USR2"
	case SignalWinch:
		return "WINCH"
	default:
		return "UNKNOWN"
	}
}

// SignalNumToSignal returns signal from signal num
func SignalNumToSignal(num int) (Signal, error) {
	switch num {
	case 1:
		return SignalHup, nil
	case 2:
		return SignalInterrupt, nil
	case 3:
		return SignalQuit, nil
	case 9:
		return SignalKill, nil
	case 10:
		return SignalUsr1, nil
	case 12:
		return SignalUsr2, nil
	case 14:
		return SignalAlrm, nil
	case 15:
		return SignalDown, nil
	case 18:
		return SignalCont, nil
	case 19:
		return SignalStop, nil
	case 21:
		return SignalTtin, nil
	case 22:
		return SignalTtou, nil
	}
	return 0, errors.NotSupportedf("signal num %d", num)
}

// SignalRequest includes the specific signal value and a response channel to send back result
type SignalRequest struct {
	signal Signal
	respc  chan error
}

// Signal sends a given signal, and waiting for the daemon return
func (d *Daemon) Signal(s Signal) error {
	rc := make(chan error, 1)
	d.sigch <- SignalRequest{
		signal: s,
		respc:  rc,
	}
	select {
	case err := <-rc:
		if err != nil {
			return errors.Annotatef(err, "handling signal [%v] return error", s)
		}
	case <-time.After(30 * time.Second):
		return errors.Errorf("waiting timeout 30s for signal [%v]", s)
	}
	return nil
}

func (d *Daemon) handleSignal(r SignalRequest) {
	var err error

	switch r.signal {
	case SignalAlrm:
		err = d.proc.Signal(syscall.SIGALRM)
	case SignalCont:
		s := d.ProcessState()
		switch s {
		case ProcStatPaused:
			err = d.proc.Signal(syscall.SIGCONT)
			if err == nil {
				d.changeToState(ProcStatRunning)
			}
		default:
			err = errors.Errorf("can't resume process from state [%v]", s)
		}
	case SignalDown:
		s := d.ProcessState()
		switch s {
		case ProcStatRunning, ProcStatPaused:
			d.changeToState(ProcStatStopping)
			d.lockOnce = 1
			err = d.proc.Kill()
		default:
			err = errors.Errorf("can't stop process from state [%v]", s)
		}
	case SignalHup:
		err = d.proc.Signal(syscall.SIGHUP)
	case SignalRestart:
		s := d.ProcessState()
		switch s {
		case ProcStatRunning, ProcStatPaused:
			d.changeToState(ProcStatRestarting)
			d.lockOnce = 0
			err = d.proc.Kill()
		default:
			err = errors.Errorf("can't restart process from state [%v]", s)
		}
	case SignalInterrupt:
		err = d.proc.Signal(syscall.SIGINT)
	case SignalTtin:
		err = d.proc.Signal(syscall.SIGTTIN)
	case SignalKill:
		s := d.ProcessState()
		switch s {
		case ProcStatRunning, ProcStatStopping, ProcStatRestarting, ProcStatPaused:
			d.changeToState(ProcStatKilling)
			d.lockOnce = 1
			err = d.proc.Signal(syscall.SIGKILL)
		default:
			err = errors.Errorf("can't kill process from state [%v]", s)
		}
	case SignalTtou:
		err = d.proc.Signal(syscall.SIGTTOU)
	case SignalStop:
		s := d.ProcessState()
		switch s {
		case ProcStatRunning, ProcStatPaused:
			err = d.proc.Signal(syscall.SIGSTOP)
			if err == nil {
				d.changeToState(ProcStatPaused)
			}
		default:
			err = errors.Errorf("can't pause process from state [%v]", s)
		}
	case SignalQuit:
		s := d.ProcessState()
		switch s {
		case ProcStatRunning, ProcStatStopping, ProcStatRestarting, ProcStatPaused:
			d.changeToState(ProcStatKilling)
			d.lockOnce = 1
			err = d.proc.Signal(syscall.SIGQUIT)
		default:
			err = errors.Errorf("can't kill process from state [%v]", s)
		}
	case SignalUp:
		s := d.ProcessState()
		switch s {
		case ProcStatStopped, ProcStatKilled, ProcStatNormalExited, ProcStatErrorExited:
			d.changeToState(ProcStatStarting)
			d.lockOnce = 0
			atomic.StoreUint32(&d.lock, d.lockOnce)
			d.runch <- struct{}{}
		default:
			err = errors.Errorf("can't start process from state [%v]", s)
		}
	case SignalUsr1:
		err = d.proc.Signal(syscall.SIGUSR1)
	case SignalUsr2:
		err = d.proc.Signal(syscall.SIGUSR2)
	case SignalWinch:
		err = d.proc.Signal(syscall.SIGWINCH)
	default:
		err = errors.Errorf("unknown signal")
	}

	r.respc <- err
}
