package top

import (
	"github.com/golang/glog"
	gonum "github.com/gonum/graph"

	"github.com/docker/distribution/digest"

	kapi "k8s.io/kubernetes/pkg/api"

	"github.com/openshift/origin/pkg/api/graph"
	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
	imageapi "github.com/openshift/origin/pkg/image/api"
	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)

const (
	ImageLayerEdgeKind               = "ImageLayer"
	ImageTopLayerEdgeKind            = "ImageTopLayer"
	ImageStreamImageEdgeKind         = "ImageStreamImage"
	HistoricImageStreamImageEdgeKind = "HistoricImageStreamImage"
	PodImageEdgeKind                 = "PodImage"
	ParentImageEdgeKind              = "ParentImage"

	// digest.DigestSha256EmptyTar is empty layer digest, whereas this is gzipped digest of empty layer
	digestSHA256GzippedEmptyTar = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
)

func getImageNodes(nodes []gonum.Node) []*imagegraph.ImageNode {
	ret := []*imagegraph.ImageNode{}
	for i := range nodes {
		if node, ok := nodes[i].(*imagegraph.ImageNode); ok {
			ret = append(ret, node)
		}
	}
	return ret
}

func addImagesToGraph(g graph.Graph, images *imageapi.ImageList) {
	for i := range images.Items {
		image := &images.Items[i]

		glog.V(4).Infof("Adding image %q to graph", image.Name)
		imageNode := imagegraph.EnsureImageNode(g, image)

		topLayerAdded := false
		// We're looking through layers in reversed order since we need to
		// find first layer (from top) which is not an empty layer, we're omitting
		// empty layers because every image has those and they're giving us
		// false positives about parents. This applies only to schema v1 images
		// schema v2 does not have that problem.
		for i := len(image.DockerImageLayers) - 1; i >= 0; i-- {
			layer := image.DockerImageLayers[i]
			layerNode := imagegraph.EnsureImageLayerNode(g, layer.Name)
			edgeKind := ImageLayerEdgeKind
			if !topLayerAdded && layer.Name != digest.DigestSha256EmptyTar && layer.Name != digestSHA256GzippedEmptyTar {
				edgeKind = ImageTopLayerEdgeKind
				topLayerAdded = true
			}
			g.AddEdge(imageNode, layerNode, edgeKind)
			glog.V(4).Infof("Adding image layer %q to graph (%q)", layer.Name, edgeKind)
		}
	}
}

func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList) {
	for i := range streams.Items {
		stream := &streams.Items[i]
		glog.V(4).Infof("Adding ImageStream %s/%s to graph", stream.Namespace, stream.Name)
		isNode := imagegraph.EnsureImageStreamNode(g, stream)
		imageStreamNode := isNode.(*imagegraph.ImageStreamNode)

		// connect IS with underlying images
		for tag, history := range stream.Status.Tags {
			for i := range history.Items {
				image := history.Items[i]
				n := imagegraph.FindImage(g, image.Image)
				if n == nil {
					glog.V(2).Infof("Unable to find image %q in graph (from tag=%q, dockerImageReference=%s)",
						history.Items[i].Image, tag, image.DockerImageReference)
					continue
				}
				imageNode := n.(*imagegraph.ImageNode)
				glog.V(4).Infof("Adding edge from %q to %q", imageStreamNode.UniqueName(), imageNode.UniqueName())
				edgeKind := ImageStreamImageEdgeKind
				if i > 1 {
					edgeKind = HistoricImageStreamImageEdgeKind
				}
				g.AddEdge(imageStreamNode, imageNode, edgeKind)
			}
		}
	}
}

func addPodsToGraph(g graph.Graph, pods *kapi.PodList) {
	for i := range pods.Items {
		pod := &pods.Items[i]
		if pod.Status.Phase != kapi.PodRunning && pod.Status.Phase != kapi.PodPending {
			glog.V(4).Infof("Pod %s/%s is not running nor pending - skipping", pod.Namespace, pod.Name)
			continue
		}

		glog.V(4).Infof("Adding pod %s/%s to graph", pod.Namespace, pod.Name)
		podNode := kubegraph.EnsurePodNode(g, pod)
		addPodSpecToGraph(g, &pod.Spec, podNode)
	}
}

func addPodSpecToGraph(g graph.Graph, spec *kapi.PodSpec, predecessor gonum.Node) {
	for j := range spec.Containers {
		container := spec.Containers[j]

		glog.V(4).Infof("Examining container image %q", container.Image)
		ref, err := imageapi.ParseDockerImageReference(container.Image)
		if err != nil {
			glog.V(2).Infof("Unable to parse DockerImageReference %q: %v - skipping", container.Image, err)
			continue
		}

		if len(ref.ID) == 0 {
			// ignore not managed images
			continue
		}

		imageNode := imagegraph.FindImage(g, ref.ID)
		if imageNode == nil {
			glog.V(1).Infof("Unable to find image %q in the graph", ref.ID)
			continue
		}

		glog.V(4).Infof("Adding edge from %v to %v", predecessor, imageNode)
		g.AddEdge(predecessor, imageNode, PodImageEdgeKind)
	}
}

func markParentsInGraph(g graph.Graph) {
	imageNodes := getImageNodes(g.Nodes())
	for _, in := range imageNodes {
		// find image's top layer, should be just one
		for _, e := range g.OutboundEdges(in, ImageTopLayerEdgeKind) {
			layerNode, _ := e.To().(*imagegraph.ImageLayerNode)
			// find image's containing this layer but not being their top layer
			for _, ed := range g.InboundEdges(layerNode, ImageLayerEdgeKind) {
				childNode, _ := ed.From().(*imagegraph.ImageNode)
				if in.ID() == childNode.ID() {
					// don't add self edge, otherwise gonum/graph will panic
					continue
				}
				g.AddEdge(in, childNode, ParentImageEdgeKind)
			}
			// TODO: Find image's containing THIS layer being their top layer,
			// this happens when image contents is not being changed.

			// TODO: If two layers have exactly the same contents the current
			// mechanism might trip over that as well. We should check for
			// a series of layers when checking for parents.
		}
	}
}
