package containerd

import (
	"io"

	"github.com/docker/docker/libcontainerd"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
)

// ExitHandler represents an object that is called when the exit event is received from containerd
type ExitHandler interface {
	HandleExitEvent(id string) error
}

// New creates a new containerd plugin executor
func New(remote libcontainerd.Remote, exitHandler ExitHandler) (*Executor, error) {
	e := &Executor{exitHandler: exitHandler}
	client, err := remote.Client(e)
	if err != nil {
		return nil, errors.Wrap(err, "error creating containerd exec client")
	}
	e.client = client
	return e, nil
}

// Executor is the containerd client implementation of a plugin executor
type Executor struct {
	client      libcontainerd.Client
	exitHandler ExitHandler
}

// Create creates a new container
func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
	return e.client.Create(id, "", "", spec, attachStreamsFunc(stdout, stderr))
}

// Restore restores a container
func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) error {
	return e.client.Restore(id, attachStreamsFunc(stdout, stderr))
}

// IsRunning returns if the container with the given id is running
func (e *Executor) IsRunning(id string) (bool, error) {
	pids, err := e.client.GetPidsForContainer(id)
	return len(pids) > 0, err
}

// Signal sends the specified signal to the container
func (e *Executor) Signal(id string, signal int) error {
	return e.client.Signal(id, signal)
}

// StateChanged handles state changes from containerd
// All events are ignored except the exit event, which is sent of to the stored handler
func (e *Executor) StateChanged(id string, event libcontainerd.StateInfo) error {
	switch event.State {
	case libcontainerd.StateExit:
		return e.exitHandler.HandleExitEvent(id)
	}
	return nil
}

func attachStreamsFunc(stdout, stderr io.WriteCloser) func(libcontainerd.IOPipe) error {
	return func(iop libcontainerd.IOPipe) error {
		iop.Stdin.Close()
		go func() {
			io.Copy(stdout, iop.Stdout)
			stdout.Close()
		}()
		go func() {
			io.Copy(stderr, iop.Stderr)
			stderr.Close()
		}()
		return nil
	}
}