package hcsshim

import (
	"context"
	"io"
	"sync"
	"time"

	"github.com/Microsoft/hcsshim/internal/hcs"
)

// ContainerError is an error encountered in HCS
type process struct {
	p        *hcs.Process
	waitOnce sync.Once
	waitCh   chan struct{}
	waitErr  error
}

// Pid returns the process ID of the process within the container.
func (process *process) Pid() int {
	return process.p.Pid()
}

// Kill signals the process to terminate but does not wait for it to finish terminating.
func (process *process) Kill() error {
	found, err := process.p.Kill(context.Background())
	if err != nil {
		return convertProcessError(err, process)
	}
	if !found {
		return &ProcessError{Process: process, Err: ErrElementNotFound, Operation: "hcsshim::Process::Kill"}
	}
	return nil
}

// Wait waits for the process to exit.
func (process *process) Wait() error {
	return convertProcessError(process.p.Wait(), process)
}

// WaitTimeout waits for the process to exit or the duration to elapse. It returns
// false if timeout occurs.
func (process *process) WaitTimeout(timeout time.Duration) error {
	process.waitOnce.Do(func() {
		process.waitCh = make(chan struct{})
		go func() {
			process.waitErr = process.Wait()
			close(process.waitCh)
		}()
	})
	t := time.NewTimer(timeout)
	defer t.Stop()
	select {
	case <-t.C:
		return &ProcessError{Process: process, Err: ErrTimeout, Operation: "hcsshim::Process::Wait"}
	case <-process.waitCh:
		return process.waitErr
	}
}

// ExitCode returns the exit code of the process. The process must have
// already terminated.
func (process *process) ExitCode() (int, error) {
	code, err := process.p.ExitCode()
	if err != nil {
		err = convertProcessError(err, process)
	}
	return code, err
}

// ResizeConsole resizes the console of the process.
func (process *process) ResizeConsole(width, height uint16) error {
	return convertProcessError(process.p.ResizeConsole(context.Background(), width, height), process)
}

// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
// these pipes does not close the underlying pipes; it should be possible to
// call this multiple times to get multiple interfaces.
func (process *process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) {
	stdin, stdout, stderr, err := process.p.StdioLegacy()
	if err != nil {
		err = convertProcessError(err, process)
	}
	return stdin, stdout, stderr, err
}

// CloseStdin closes the write side of the stdin pipe so that the process is
// notified on the read side that there is no more data in stdin.
func (process *process) CloseStdin() error {
	return convertProcessError(process.p.CloseStdin(context.Background()), process)
}

// Close cleans up any state associated with the process but does not kill
// or wait on it.
func (process *process) Close() error {
	return convertProcessError(process.p.Close(), process)
}
