// +build linux

package linux

import (
	"context"

	"github.com/containerd/containerd/api/services/shim"
	"github.com/containerd/containerd/api/types/task"
	"github.com/containerd/containerd/plugin"
	protobuf "github.com/gogo/protobuf/types"
	specs "github.com/opencontainers/runtime-spec/specs-go"
)

type State struct {
	pid    uint32
	status plugin.Status
}

func (s State) Pid() uint32 {
	return s.pid
}

func (s State) Status() plugin.Status {
	return s.status
}

type Task struct {
	containerID string
	spec        []byte
	shim        shim.ShimClient
}

func newTask(id string, spec []byte, shim shim.ShimClient) *Task {
	return &Task{
		containerID: id,
		shim:        shim,
		spec:        spec,
	}
}

func (c *Task) Info() plugin.TaskInfo {
	return plugin.TaskInfo{
		ID:          c.containerID,
		ContainerID: c.containerID,
		Runtime:     runtimeName,
		Spec:        c.spec,
	}
}

func (c *Task) Start(ctx context.Context) error {
	_, err := c.shim.Start(ctx, &shim.StartRequest{})
	return err
}

func (c *Task) State(ctx context.Context) (plugin.State, error) {
	response, err := c.shim.State(ctx, &shim.StateRequest{})
	if err != nil {
		return nil, err
	}
	var status plugin.Status
	switch response.Status {
	case task.StatusCreated:
		status = plugin.CreatedStatus
	case task.StatusRunning:
		status = plugin.RunningStatus
	case task.StatusStopped:
		status = plugin.StoppedStatus
	case task.StatusPaused:
		status = plugin.PausedStatus
		// TODO: containerd.DeletedStatus
	}
	return &State{
		pid:    response.Pid,
		status: status,
	}, nil
}

func (c *Task) Pause(ctx context.Context) error {
	_, err := c.shim.Pause(ctx, &shim.PauseRequest{})
	return err
}

func (c *Task) Resume(ctx context.Context) error {
	_, err := c.shim.Resume(ctx, &shim.ResumeRequest{})
	return err
}

func (c *Task) Kill(ctx context.Context, signal uint32, pid uint32, all bool) error {
	_, err := c.shim.Kill(ctx, &shim.KillRequest{
		Signal: signal,
		Pid:    pid,
		All:    all,
	})
	return err
}

func (c *Task) Exec(ctx context.Context, opts plugin.ExecOpts) (plugin.Process, error) {
	request := &shim.ExecRequest{
		Stdin:    opts.IO.Stdin,
		Stdout:   opts.IO.Stdout,
		Stderr:   opts.IO.Stderr,
		Terminal: opts.IO.Terminal,
		Spec: &protobuf.Any{
			TypeUrl: specs.Version,
			Value:   opts.Spec,
		},
	}
	resp, err := c.shim.Exec(ctx, request)
	if err != nil {
		return nil, err

	}
	return &Process{
		pid: int(resp.Pid),
		c:   c,
	}, nil
}

func (c *Task) Processes(ctx context.Context) ([]uint32, error) {
	resp, err := c.shim.Processes(ctx, &shim.ProcessesRequest{
		ID: c.containerID,
	})

	if err != nil {
		return nil, err
	}

	pids := make([]uint32, 0, len(resp.Processes))

	for _, ps := range resp.Processes {
		pids = append(pids, ps.Pid)
	}

	return pids, nil
}

func (c *Task) Pty(ctx context.Context, pid uint32, size plugin.ConsoleSize) error {
	_, err := c.shim.Pty(ctx, &shim.PtyRequest{
		Pid:    pid,
		Width:  size.Width,
		Height: size.Height,
	})
	return err
}

func (c *Task) CloseStdin(ctx context.Context, pid uint32) error {
	_, err := c.shim.CloseStdin(ctx, &shim.CloseStdinRequest{
		Pid: pid,
	})
	return err
}

func (c *Task) Checkpoint(ctx context.Context, opts plugin.CheckpointOpts) error {
	_, err := c.shim.Checkpoint(ctx, &shim.CheckpointRequest{
		Exit:             opts.Exit,
		AllowTcp:         opts.AllowTCP,
		AllowUnixSockets: opts.AllowUnixSockets,
		AllowTerminal:    opts.AllowTerminal,
		FileLocks:        opts.FileLocks,
		EmptyNamespaces:  opts.EmptyNamespaces,
		CheckpointPath:   opts.Path,
	})
	return err
}

type Process struct {
	pid int
	c   *Task
}

func (p *Process) Kill(ctx context.Context, signal uint32, _ bool) error {
	_, err := p.c.shim.Kill(ctx, &shim.KillRequest{
		Signal: signal,
		Pid:    uint32(p.pid),
	})
	return err
}

func (p *Process) State(ctx context.Context) (plugin.State, error) {
	// use the container status for the status of the process
	state, err := p.c.State(ctx)
	if err != nil {
		return nil, err
	}
	state.(*State).pid = uint32(p.pid)
	return state, nil
}
