package daemon

import (
	"fmt"

	"github.com/sirupsen/logrus"
	"github.com/docker/distribution/digest"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/layer"
	"github.com/docker/docker/pkg/directory"
	"github.com/docker/docker/volume"
)

func (daemon *Daemon) getLayerRefs() map[layer.ChainID]int {
	tmpImages := daemon.imageStore.Map()
	layerRefs := map[layer.ChainID]int{}
	for id, img := range tmpImages {
		dgst := digest.Digest(id)
		if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
			continue
		}

		rootFS := *img.RootFS
		rootFS.DiffIDs = nil
		for _, id := range img.RootFS.DiffIDs {
			rootFS.Append(id)
			chid := rootFS.ChainID()
			layerRefs[chid]++
		}
	}

	return layerRefs
}

// SystemDiskUsage returns information about the daemon data disk usage
func (daemon *Daemon) SystemDiskUsage() (*types.DiskUsage, error) {
	// Retrieve container list
	allContainers, err := daemon.Containers(&types.ContainerListOptions{
		Size: true,
		All:  true,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve container list: %v", err)
	}

	// Get all top images with extra attributes
	allImages, err := daemon.Images(filters.NewArgs(), false, true)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve image list: %v", err)
	}

	// Get all local volumes
	allVolumes := []*types.Volume{}
	getLocalVols := func(v volume.Volume) error {
		name := v.Name()
		refs := daemon.volumes.Refs(v)

		tv := volumeToAPIType(v)
		sz, err := directory.Size(v.Path())
		if err != nil {
			logrus.Warnf("failed to determine size of volume %v", name)
			sz = -1
		}
		tv.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(len(refs))}
		allVolumes = append(allVolumes, tv)

		return nil
	}

	err = daemon.traverseLocalVolumes(getLocalVols)
	if err != nil {
		return nil, err
	}

	// Get total layers size on disk
	layerRefs := daemon.getLayerRefs()
	allLayers := daemon.layerStore.Map()
	var allLayersSize int64
	for _, l := range allLayers {
		size, err := l.DiffSize()
		if err == nil {
			if _, ok := layerRefs[l.ChainID()]; ok {
				allLayersSize += size
			} else {
				logrus.Warnf("found leaked image layer %v", l.ChainID())
			}
		} else {
			logrus.Warnf("failed to get diff size for layer %v", l.ChainID())
		}

	}

	return &types.DiskUsage{
		LayersSize: allLayersSize,
		Containers: allContainers,
		Volumes:    allVolumes,
		Images:     allImages,
	}, nil
}
