// +build windows

package distribution

import (
	"fmt"
	"net/http"
	"os"
	"runtime"
	"sort"
	"strings"

	"github.com/docker/distribution"
	"github.com/docker/distribution/context"
	"github.com/docker/distribution/manifest/manifestlist"
	"github.com/docker/distribution/manifest/schema2"
	"github.com/docker/distribution/registry/client/transport"
	"github.com/docker/docker/pkg/system"
	"github.com/sirupsen/logrus"
)

var _ distribution.Describable = &v2LayerDescriptor{}

func (ld *v2LayerDescriptor) Descriptor() distribution.Descriptor {
	if ld.src.MediaType == schema2.MediaTypeForeignLayer && len(ld.src.URLs) > 0 {
		return ld.src
	}
	return distribution.Descriptor{}
}

func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
	blobs := ld.repo.Blobs(ctx)
	rsc, err := blobs.Open(ctx, ld.digest)

	if len(ld.src.URLs) == 0 {
		return rsc, err
	}

	// We're done if the registry has this blob.
	if err == nil {
		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
		if _, err = rsc.Seek(0, os.SEEK_SET); err == nil {
			return rsc, nil
		}
		rsc.Close()
	}

	// Find the first URL that results in a 200 result code.
	for _, url := range ld.src.URLs {
		logrus.Debugf("Pulling %v from foreign URL %v", ld.digest, url)
		rsc = transport.NewHTTPReadSeeker(http.DefaultClient, url, nil)

		// Seek does an HTTP GET.  If it succeeds, the blob really is accessible.
		_, err = rsc.Seek(0, os.SEEK_SET)
		if err == nil {
			break
		}
		logrus.Debugf("Download for %v failed: %v", ld.digest, err)
		rsc.Close()
		rsc = nil
	}
	return rsc, err
}

func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
	version := system.GetOSVersion()

	// TODO @jhowardmsft LCOW Support: Need to remove the hard coding in LCOW mode.
	lookingForOS := runtime.GOOS
	osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
	if system.LCOWSupported() {
		lookingForOS = "linux"
		osVersion = ""
	}

	var matches []manifestlist.ManifestDescriptor
	for _, manifestDescriptor := range manifests {
		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == lookingForOS {
			if lookingForOS == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
				continue
			}
			matches = append(matches, manifestDescriptor)

			logrus.Debugf("found match for %s/%s with media type %s, digest %s", lookingForOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
		}
	}
	sort.Stable(manifestsByVersion(matches))
	return matches
}

func versionMatch(actual, expected string) bool {
	// Check whether actual and expected are equivalent, or whether
	// expected is a version prefix of actual.
	return actual == "" || expected == "" || actual == expected || strings.HasPrefix(actual, expected+".")
}

type manifestsByVersion []manifestlist.ManifestDescriptor

func (mbv manifestsByVersion) Less(i, j int) bool {
	if mbv[i].Platform.OSVersion == "" {
		return false
	}
	if mbv[j].Platform.OSVersion == "" {
		return true
	}
	// TODO: Split version by parts and compare
	// TODO: Prefer versions which have a greater version number
	return false
}

func (mbv manifestsByVersion) Len() int {
	return len(mbv)
}

func (mbv manifestsByVersion) Swap(i, j int) {
	mbv[i], mbv[j] = mbv[j], mbv[i]
}