package process

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"time"
)

type Process struct {
	Label      string
	ExecPath   string
	Args       []string
	Pid        int
	StartTime  time.Time
	EndTime    time.Time
	Cmd        *exec.Cmd        `json:"-"`
	ExitState  *os.ProcessState `json:"-"`
	InputFile  io.Reader        `json:"-"`
	OutputFile io.WriteCloser   `json:"-"`
	WaitCh     chan struct{}    `json:"-"`
}

// execPath: command name
// args: args to command. (should not include name)
func Create(label string, execPath string, args []string, inFile io.Reader, outFile io.WriteCloser) (*Process, error) {
	cmd := exec.Command(execPath, args...)
	cmd.Stdout = outFile
	cmd.Stderr = outFile
	cmd.Stdin = inFile
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	proc := &Process{
		Label:      label,
		ExecPath:   execPath,
		Args:       args,
		Pid:        cmd.Process.Pid,
		StartTime:  time.Now(),
		Cmd:        cmd,
		ExitState:  nil,
		InputFile:  inFile,
		OutputFile: outFile,
		WaitCh:     make(chan struct{}),
	}
	go func() {
		err := proc.Cmd.Wait()
		if err != nil {
			fmt.Printf("Process exit: %v\n", err)
			if exitError, ok := err.(*exec.ExitError); ok {
				proc.ExitState = exitError.ProcessState
			}
		}
		proc.ExitState = proc.Cmd.ProcessState
		proc.EndTime = time.Now() // TODO make this goroutine-safe
		err = proc.OutputFile.Close()
		if err != nil {
			fmt.Printf("Error closing output file for %v: %v\n", proc.Label, err)
		}
		close(proc.WaitCh)
	}()
	return proc, nil
}

func Stop(proc *Process, kill bool) error {
	defer proc.OutputFile.Close()
	if kill {
		fmt.Printf("Killing process %v\n", proc.Cmd.Process)
		return proc.Cmd.Process.Kill()
	} else {
		fmt.Printf("Stopping process %v\n", proc.Cmd.Process)
		return proc.Cmd.Process.Signal(os.Interrupt)
	}
}