package sys_exec

import (
	"bufio"
	"github.com/crawlab-team/crawlab/trace"
	"github.com/shirou/gopsutil/process"
	"os/exec"
	"time"
)

type KillProcessOptions struct {
	Timeout time.Duration
	Force   bool
}

func KillProcess(cmd *exec.Cmd, opts *KillProcessOptions) error {
	// process
	p, err := process.NewProcess(int32(cmd.Process.Pid))
	if err != nil {
		return err
	}

	// kill function
	killFunc := func(p *process.Process) error {
		return killProcessRecursive(p, opts.Force)
	}

	if opts.Timeout != 0 {
		// with timeout
		return killProcessWithTimeout(p, opts.Timeout, killFunc)
	} else {
		// without timeout
		return killFunc(p)
	}
}

func killProcessWithTimeout(p *process.Process, timeout time.Duration, killFunc func(*process.Process) error) error {
	go func() {
		if err := killFunc(p); err != nil {
			trace.PrintError(err)
		}
	}()
	for i := 0; i < int(timeout.Seconds()); i++ {
		ok, err := process.PidExists(p.Pid)
		if err == nil && !ok {
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return killProcess(p, true)
}

func killProcessRecursive(p *process.Process, force bool) (err error) {
	// children processes
	cps, err := p.Children()
	if err != nil {
		return killProcess(p, force)
	}

	// iterate children processes
	for _, cp := range cps {
		if err := killProcessRecursive(cp, force); err != nil {
			return err
		}
	}

	return nil
}

func killProcess(p *process.Process, force bool) (err error) {
	if force {
		err = p.Kill()
	} else {
		err = p.Terminate()
	}
	if err != nil {
		return trace.TraceError(err)
	}
	return nil
}

func ConfigureCmdLogging(cmd *exec.Cmd, fn func(scanner *bufio.Scanner)) {
	stdout, _ := (*cmd).StdoutPipe()
	stderr, _ := (*cmd).StderrPipe()
	scannerStdout := bufio.NewScanner(stdout)
	scannerStderr := bufio.NewScanner(stderr)
	go fn(scannerStdout)
	go fn(scannerStderr)
}
