package client

import (
	"encoding/json"
	"fmt"
	"text/template"

	"github.com/docker/docker/api/client/inspect"
	"github.com/docker/docker/api/client/lib"
	Cli "github.com/docker/docker/cli"
	flag "github.com/docker/docker/pkg/mflag"
)

var funcMap = template.FuncMap{
	"json": func(v interface{}) string {
		a, _ := json.Marshal(v)
		return string(a)
	},
}

// CmdInspect displays low-level information on one or more containers or images.
//
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
func (cli *DockerCli) CmdInspect(args ...string) error {
	cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
	inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
	size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
	cmd.Require(flag.Min, 1)

	cmd.ParseFlags(args, true)

	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
	}

	var elementSearcher inspectSearcher
	switch *inspectType {
	case "container":
		elementSearcher = cli.inspectContainers(*size)
	case "image":
		elementSearcher = cli.inspectImages(*size)
	default:
		elementSearcher = cli.inspectAll(*size)
	}

	return cli.inspectElements(*tmplStr, cmd.Args(), elementSearcher)
}

func (cli *DockerCli) inspectContainers(getSize bool) inspectSearcher {
	return func(ref string) (interface{}, []byte, error) {
		return cli.client.ContainerInspectWithRaw(ref, getSize)
	}
}

func (cli *DockerCli) inspectImages(getSize bool) inspectSearcher {
	return func(ref string) (interface{}, []byte, error) {
		return cli.client.ImageInspectWithRaw(ref, getSize)
	}
}

func (cli *DockerCli) inspectAll(getSize bool) inspectSearcher {
	return func(ref string) (interface{}, []byte, error) {
		c, rawContainer, err := cli.client.ContainerInspectWithRaw(ref, getSize)
		if err != nil {
			// Search for image with that id if a container doesn't exist.
			if lib.IsErrContainerNotFound(err) {
				i, rawImage, err := cli.client.ImageInspectWithRaw(ref, getSize)
				if err != nil {
					if lib.IsErrImageNotFound(err) {
						return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
					}
					return nil, nil, err
				}
				return i, rawImage, err
			}
			return nil, nil, err
		}
		return c, rawContainer, err
	}
}

type inspectSearcher func(ref string) (interface{}, []byte, error)

func (cli *DockerCli) inspectElements(tmplStr string, references []string, searchByReference inspectSearcher) error {
	elementInspector, err := cli.newInspectorWithTemplate(tmplStr)
	if err != nil {
		return Cli.StatusError{StatusCode: 64, Status: err.Error()}
	}

	var inspectErr error
	for _, ref := range references {
		element, raw, err := searchByReference(ref)
		if err != nil {
			inspectErr = err
			break
		}

		if err := elementInspector.Inspect(element, raw); err != nil {
			inspectErr = err
			break
		}
	}

	if err := elementInspector.Flush(); err != nil {
		cli.inspectErrorStatus(err)
	}

	if status := cli.inspectErrorStatus(inspectErr); status != 0 {
		return Cli.StatusError{StatusCode: status}
	}
	return nil
}

func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
	if err != nil {
		fmt.Fprintf(cli.err, "%s\n", err)
		status = 1
	}
	return
}

func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
	elementInspector := inspect.NewIndentedInspector(cli.out)
	if tmplStr != "" {
		tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
		if err != nil {
			return nil, fmt.Errorf("Template parsing error: %s", err)
		}
		elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
	}
	return elementInspector, nil
}