package main

import (
	"context"
	"fmt"
	"os"
	"sort"
	"text/tabwriter"
	"text/template"
	"time"

	"github.com/alibaba/pouch/apis/types"
	"github.com/alibaba/pouch/cli/formatter"
	"github.com/alibaba/pouch/pkg/utils/filters"

	"github.com/spf13/cobra"
)

// psDescription is used to describe ps command in detail and auto generate command doc.
var psDescription = "\nList Containers with container name, ID, status, creation time, image reference and runtime."
var psDefaultFormat = "table {{.Names}}\t{{.ID}}\t{{.Status}}\t{{.RunningFor}}\t{{.Image}}\t{{.Runtime}}\n"

// containerList is used to save the container list.
type containerList []*types.Container

// PsCommand is used to implement 'ps' command.
type PsCommand struct {
	baseCommand

	// flags for ps command
	flagAll     bool
	flagQuiet   bool
	flagNoTrunc bool
	flagFilter  []string
	flagFormat  string
}

// Init initializes PsCommand command.
func (p *PsCommand) Init(c *Cli) {
	p.cli = c
	p.cmd = &cobra.Command{
		Use:   "ps [OPTIONS]",
		Short: "List containers",
		Long:  psDescription,
		Args:  cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return p.runPs(args)
		},
		Example: psExample(),
	}
	p.addFlags()
}

// addFlags adds flags for specific command.
func (p *PsCommand) addFlags() {
	flagSet := p.cmd.Flags()
	flagSet.BoolVarP(&p.flagAll, "all", "a", false, "Show all containers (default shows just running)")
	flagSet.BoolVarP(&p.flagQuiet, "quiet", "q", false, "Only show numeric IDs")
	flagSet.BoolVar(&p.flagNoTrunc, "no-trunc", false, "Do not truncate output")
	flagSet.StringVarP(&p.flagFormat, "format", "", "", "intelligent-print containers based on Go template")
	flagSet.StringSliceVarP(&p.flagFilter, "filter", "f", nil, "Filter output based on given conditions, support filter key [ id label name status ]")
}

// runPs is the entry of PsCommand command.
func (p *PsCommand) runPs(args []string) error {
	ctx := context.Background()
	apiClient := p.cli.Client()

	filter, err := filters.Parse(p.flagFilter)
	if err != nil {
		return err
	}

	var containers containerList

	option := types.ContainerListOptions{
		All:    p.flagAll,
		Filter: filter,
	}
	containers, err = apiClient.ContainerList(ctx, option)
	if err != nil {
		return fmt.Errorf("failed to get container list: %v", err)
	}

	sort.Sort(containers)

	if p.flagQuiet {
		for _, c := range containers {
			id := c.ID[:6]
			if p.flagNoTrunc {
				id = c.ID
			}
			fmt.Println(id)
		}
		return nil
	}
	// add to format the output with go template
	format := p.flagFormat
	tmplH := template.New("ps_head")
	w := tabwriter.NewWriter(os.Stdout, 0, 0, p.cli.padding, ' ', 0)
	if len(format) == 0 {
		format = psDefaultFormat
	}
	// true is table,false is raw
	tableOrRaw := formatter.IsTable(format)
	format = formatter.PreFormat(format)
	if tableOrRaw {
		containerHeader := formatter.ContainerHeader
		err = p.cli.FormatDisplay(format, tmplH, containerHeader, w)
		if err != nil {
			_, _ = fmt.Fprintf(os.Stderr, "format display error:%+v\n", err)
		}
	}
	for _, c := range containers {
		containerContext, err := formatter.NewContainerContext(c, p.flagNoTrunc)
		if err != nil {
			return err
		}
		tmplD := template.New("ps_detail")
		err = p.cli.FormatDisplay(format, tmplD, containerContext, w)
		if err != nil {
			_, _ = fmt.Fprintf(os.Stderr, "format display error:%+v\n", err)

		}
	}
	_ = w.Flush()
	return nil
}

// psExample shows examples in ps command, and is used in auto-generated cli docs.
func psExample() string {
	return `$ pouch ps
Name   ID       Status          Created          Image                              Runtime
2      e42c68   Up 15 minutes   16 minutes ago   docker.io/library/busybox:latest   runc
1      a8c2ea   Up 16 minutes   17 minutes ago   docker.io/library/busybox:latest   runc

$ pouch ps -a
Name   ID       Status          Created          Image                              Runtime
3      faf132   created         16 seconds ago   docker.io/library/busybox:latest   runc
2      e42c68   Up 16 minutes   16 minutes ago   docker.io/library/busybox:latest   runc
1      a8c2ea   Up 17 minutes   18 minutes ago   docker.io/library/busybox:latest   runc

$ pouch ps -q
e42c68
a8c2ea

$ pouch ps -a -q
faf132
e42c68
a8c2ea

$ pouch ps --no-trunc
Name   ID                                                                 Status        Created        Image                            Runtime
foo2   692c77587b38f60bbd91d986ec3703848d72aea5030e320d4988eb02aa3f9d48   Up 1 minute   1 minute ago   docker.io/library/redis:alpine   runc
foo    18592900006405ee64788bd108ef1de3d24dc3add73725891f4787d0f8e036f5   Up 1 minute   1 minute ago   docker.io/library/redis:alpine   runc

$ pouch ps --no-trunc -q
692c77587b38f60bbd91d986ec3703848d72aea5030e320d4988eb02aa3f9d48
18592900006405ee64788bd108ef1de3d24dc3add73725891f4787d0f8e036f5

$ pouch ps --no-trunc -a
Name   ID                                                                 Status         Created         Image                            Runtime
foo3   63fd6371f3d614bb1ecad2780972d5975ca1ab534ec280c5f7d8f4c7b2e9989d   created        2 minutes ago   docker.io/library/redis:alpine   runc
foo2   692c77587b38f60bbd91d986ec3703848d72aea5030e320d4988eb02aa3f9d48   Up 2 minutes   2 minutes ago   docker.io/library/redis:alpine   runc
foo    18592900006405ee64788bd108ef1de3d24dc3add73725891f4787d0f8e036f5   Up 2 minutes   2 minutes ago   docker.io/library/redis:alpine   runc

$ pouch ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.RunningFor}}\t{{.Ports}}\t{{.Status}}\t{{.Size}}\t{{.Labels}}\t{{.Mounts}}\t{{.LocalVolumes}}\t{{.Networks}}\t{{.Runtime}}\t{{.ImageID}}"
ID       Name     Image                                          Command   CreatedAt                                Created         Ports              Status         Size   Labels   Mounts        Volumes   Networks   Runtime   ImageID
869433   test   registry.hub.docker.com/library/busybox:1.28   sh        2019-05-29 05:40:46.64617376 +0000 UTC   6 seconds ago   3333/tcp->:3333;   Up 6 seconds   0B     a = b;   /root/test;   0         bridge     runc      sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
`
}

// Len implements the sort interface.
func (c containerList) Len() int {
	return len(c)
}

// Swap implements the sort interface.
func (c containerList) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}

// Less implements the sort interface.
func (c containerList) Less(i, j int) bool {
	iValue := time.Unix(c[i].Created, 0)
	jValue := time.Unix(c[j].Created, 0)
	return iValue.After(jValue)
}
