// +build varlink

package varlinkapi

import (
	"context"
	"strconv"
	"strings"
	"time"

	"github.com/containers/buildah"
	"github.com/containers/libpod/cmd/podman/shared"
	"github.com/containers/libpod/cmd/podman/varlink"
	"github.com/containers/libpod/libpod"
	"github.com/containers/libpod/libpod/define"
	"github.com/containers/libpod/pkg/channelwriter"
	"github.com/containers/storage/pkg/archive"
)

// getContext returns a non-nil, empty context
func getContext() context.Context {
	return context.TODO()
}

func makeListContainer(containerID string, batchInfo shared.BatchContainerStruct) iopodman.Container {
	var (
		mounts []iopodman.ContainerMount
		ports  []iopodman.ContainerPortMappings
	)
	ns := shared.GetNamespaces(batchInfo.Pid)

	for _, mount := range batchInfo.ConConfig.Spec.Mounts {
		m := iopodman.ContainerMount{
			Destination: mount.Destination,
			Type:        mount.Type,
			Source:      mount.Source,
			Options:     mount.Options,
		}
		mounts = append(mounts, m)
	}

	for _, pm := range batchInfo.ConConfig.PortMappings {
		p := iopodman.ContainerPortMappings{
			Host_port:      strconv.Itoa(int(pm.HostPort)),
			Host_ip:        pm.HostIP,
			Protocol:       pm.Protocol,
			Container_port: strconv.Itoa(int(pm.ContainerPort)),
		}
		ports = append(ports, p)

	}

	// If we find this needs to be done for other container endpoints, we should
	// convert this to a separate function or a generic map from struct function.
	namespace := iopodman.ContainerNameSpace{
		User:   ns.User,
		Uts:    ns.UTS,
		Pidns:  ns.PIDNS,
		Pid:    ns.PID,
		Cgroup: ns.Cgroup,
		Net:    ns.NET,
		Mnt:    ns.MNT,
		Ipc:    ns.IPC,
	}

	lc := iopodman.Container{
		Id:               containerID,
		Image:            batchInfo.ConConfig.RootfsImageName,
		Imageid:          batchInfo.ConConfig.RootfsImageID,
		Command:          batchInfo.ConConfig.Spec.Process.Args,
		Createdat:        batchInfo.ConConfig.CreatedTime.Format(time.RFC3339),
		Runningfor:       time.Since(batchInfo.ConConfig.CreatedTime).String(),
		Status:           batchInfo.ConState.String(),
		Ports:            ports,
		Names:            batchInfo.ConConfig.Name,
		Labels:           batchInfo.ConConfig.Labels,
		Mounts:           mounts,
		Containerrunning: batchInfo.ConState == define.ContainerStateRunning,
		Namespaces:       namespace,
	}
	if batchInfo.Size != nil {
		lc.Rootfssize = batchInfo.Size.RootFsSize
		lc.Rwsize = batchInfo.Size.RwSize
	}
	return lc
}

func makeListPodContainers(containerID string, batchInfo shared.BatchContainerStruct) iopodman.ListPodContainerInfo {
	lc := iopodman.ListPodContainerInfo{
		Id:     containerID,
		Status: batchInfo.ConState.String(),
		Name:   batchInfo.ConConfig.Name,
	}
	return lc
}

func makeListPod(pod *libpod.Pod, batchInfo shared.PsOptions) (iopodman.ListPodData, error) {
	var listPodsContainers []iopodman.ListPodContainerInfo
	var errPodData = iopodman.ListPodData{}
	status, err := shared.GetPodStatus(pod)
	if err != nil {
		return errPodData, err
	}
	containers, err := pod.AllContainers()
	if err != nil {
		return errPodData, err
	}
	for _, ctr := range containers {
		batchInfo, err := shared.BatchContainerOp(ctr, batchInfo)
		if err != nil {
			return errPodData, err
		}

		listPodsContainers = append(listPodsContainers, makeListPodContainers(ctr.ID(), batchInfo))
	}
	listPod := iopodman.ListPodData{
		Createdat:          pod.CreatedTime().Format(time.RFC3339),
		Id:                 pod.ID(),
		Name:               pod.Name(),
		Status:             status,
		Cgroup:             pod.CgroupParent(),
		Numberofcontainers: strconv.Itoa(len(listPodsContainers)),
		Containersinfo:     listPodsContainers,
	}
	return listPod, nil
}

func handlePodCall(call iopodman.VarlinkCall, pod *libpod.Pod, ctrErrs map[string]error, err error) error {
	if err != nil && ctrErrs == nil {
		return call.ReplyErrorOccurred(err.Error())
	}
	if ctrErrs != nil {
		containerErrs := make([]iopodman.PodContainerErrorData, len(ctrErrs))
		for ctr, reason := range ctrErrs {
			ctrErr := iopodman.PodContainerErrorData{Containerid: ctr, Reason: reason.Error()}
			containerErrs = append(containerErrs, ctrErr)
		}
		return call.ReplyPodContainerError(pod.ID(), containerErrs)
	}

	return nil
}

func stringCompressionToArchiveType(s string) archive.Compression {
	switch strings.ToUpper(s) {
	case "BZIP2":
		return archive.Bzip2
	case "GZIP":
		return archive.Gzip
	case "XZ":
		return archive.Xz
	}
	return archive.Uncompressed
}

func stringPullPolicyToType(s string) buildah.PullPolicy {
	switch strings.ToUpper(s) {
	case "PULLIFMISSING":
		return buildah.PullIfMissing
	case "PULLALWAYS":
		return buildah.PullAlways
	case "PULLNEVER":
		return buildah.PullNever
	}
	return buildah.PullIfMissing
}

func derefBool(inBool *bool) bool {
	if inBool == nil {
		return false
	}
	return *inBool
}

func derefString(in *string) string {
	if in == nil {
		return ""
	}
	return *in
}

func makePsOpts(inOpts iopodman.PsOpts) shared.PsOptions {
	last := 0
	if inOpts.Last != nil {
		lastT := *inOpts.Last
		last = int(lastT)
	}
	return shared.PsOptions{
		All:       inOpts.All,
		Last:      last,
		Latest:    derefBool(inOpts.Latest),
		NoTrunc:   derefBool(inOpts.NoTrunc),
		Pod:       derefBool(inOpts.Pod),
		Size:      derefBool(inOpts.Size),
		Sort:      derefString(inOpts.Sort),
		Namespace: true,
		Sync:      derefBool(inOpts.Sync),
	}
}

// forwardOutput is a helper method for varlink endpoints that employ both more and without
// more.  it is capable of sending updates as the output writer gets them or append them
// all to a log.  the chan error is the error from the libpod call so we can honor
// and error event in that case.
func forwardOutput(log []string, c chan error, wantsMore bool, output *channelwriter.Writer, reply func(br iopodman.MoreResponse) error) ([]string, error) {
	done := false
	for {
		select {
		// We need to check if the libpod func being called has returned an
		// error yet
		case err := <-c:
			if err != nil {
				return nil, err
			}
			done = true
		// if no error is found, we pull what we can from the log writer and
		// append it to log string slice
		case line := <-output.ByteChannel:
			log = append(log, string(line))
			// If the end point is being used in more mode, send what we have
			if wantsMore {
				br := iopodman.MoreResponse{
					Logs: log,
				}
				if err := reply(br); err != nil {
					return nil, err
				}
				// "reset" the log to empty because we are sending what we
				// get as we get it
				log = []string{}
			}
		}
		if done {
			break
		}
	}
	return log, nil
}
