package graph

import (
	"fmt"
	"strings"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/image"
	"github.com/docker/docker/utils"
)

// WalkHistory calls the handler function for each image in the
// provided images lineage starting from immediate parent.
func (graph *Graph) WalkHistory(img *image.Image, handler func(image.Image) error) (err error) {
	currentImg := img
	for currentImg != nil {
		if handler != nil {
			if err := handler(*currentImg); err != nil {
				return err
			}
		}
		currentImg, err = graph.GetParent(currentImg)
		if err != nil {
			return fmt.Errorf("Error while getting parent image: %v", err)
		}
	}
	return nil
}

// depth returns the number of parents for the current image
func (graph *Graph) depth(img *image.Image) (int, error) {
	var (
		count  = 0
		parent = img
		err    error
	)

	for parent != nil {
		count++
		if parent, err = graph.GetParent(parent); err != nil {
			return -1, err
		}
	}
	return count, nil
}

// Set the max depth to the aufs default that most
// kernels are compiled with
// For more information see: http://sourceforge.net/p/aufs/aufs3-standalone/ci/aufs3.12/tree/config.mk
const MaxImageDepth = 127

// CheckDepth returns an error if the depth of an image, as returned
// by ImageDepth, is too large to support creating a container from it
// on this daemon.
func (graph *Graph) CheckDepth(img *image.Image) error {
	// We add 2 layers to the depth because the container's rw and
	// init layer add to the restriction
	depth, err := graph.depth(img)
	if err != nil {
		return err
	}
	if depth+2 >= MaxImageDepth {
		return fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth)
	}
	return nil
}

// History returns a slice of ImageHistory structures for the specified image
// name by walking the image lineage.
func (s *TagStore) History(name string) ([]*types.ImageHistory, error) {
	foundImage, err := s.LookupImage(name)
	if err != nil {
		return nil, err
	}

	lookupMap := make(map[string][]string)
	for name, repository := range s.Repositories {
		for tag, id := range repository {
			// If the ID already has a reverse lookup, do not update it unless for "latest"
			if _, exists := lookupMap[id]; !exists {
				lookupMap[id] = []string{}
			}
			lookupMap[id] = append(lookupMap[id], utils.ImageReference(name, tag))
		}
	}

	history := []*types.ImageHistory{}

	err = s.graph.WalkHistory(foundImage, func(img image.Image) error {
		history = append(history, &types.ImageHistory{
			ID:        img.ID,
			Created:   img.Created.Unix(),
			CreatedBy: strings.Join(img.ContainerConfig.Cmd.Slice(), " "),
			Tags:      lookupMap[img.ID],
			Size:      img.Size,
			Comment:   img.Comment,
		})
		return nil
	})

	return history, err
}

// GetParent returns the parent image for the specified image.
func (graph *Graph) GetParent(img *image.Image) (*image.Image, error) {
	if img.Parent == "" {
		return nil, nil
	}
	return graph.Get(img.Parent)
}

// GetParentsSize returns the combined size of all parent images. If there is
// no parent image or it's unavailable, it returns 0.
func (graph *Graph) GetParentsSize(img *image.Image) int64 {
	parentImage, err := graph.GetParent(img)
	if err != nil || parentImage == nil {
		return 0
	}
	return parentImage.Size + graph.GetParentsSize(parentImage)
}