package migration

import (
	"github.com/docker/distribution"
	"github.com/docker/distribution/context"
	"github.com/docker/distribution/reference"
	"github.com/palantir/stacktrace"

	log "github.com/Sirupsen/logrus"
)

type Enumerator interface {
	EnumerateRepo(ctx context.Context, reg distribution.Namespace, repoName string) error
}

// NewEnumerator returns an enumerator which provides functions to iterate over
// a repository's tags, calling the given tagEnumerator function for each tag.
func NewEnumerator(onGetTag tagEnumerator) Enumerator {
	return &enumerator{onGetTag}
}

// tagEnumerator is a function signature for handling a specific repository's tag
// on each tieration
type tagEnumerator func(ctx context.Context, repo distribution.Repository, tagName string, tag distribution.Descriptor) error

// enumerator handles iterating over a repository's tags, calling `onGetTag` on
// each tag
type enumerator struct {
	onGetTag tagEnumerator
}

// EnumerateRepo iterates over a given repository's tags, calling `EnumerateTag`
// on each tag. The repository is specified as a string via the `repoName`
// argument.
// A context and registry (distribution.Namespace) must be supplied with valid,
// instantiated drivers.
func (e *enumerator) EnumerateRepo(ctx context.Context, reg distribution.Namespace, repoName string) error {
	named, err := reference.ParseNamed(repoName)
	if err != nil {
		log.WithField("error", err).Errorf("failed to parse repo name %s", repoName)
		return nil
	}

	repo, err := reg.Repository(ctx, named)
	if err != nil {
		log.WithField("error", err).Errorf("failed to construct repository %s", repoName)
		return nil
	}

	// enumerate all repository tags
	tags, err := repo.Tags(ctx).All(ctx)
	if err != nil {
		log.WithField("error", err).Errorf("failed to return all tags for repository %s", repoName)
		return nil
	}

	for _, t := range tags {
		if err = e.EnumerateTags(ctx, repo, t); err != nil {
			log.WithField("error", err).Errorf("error processing tag during enumeration %s", t)
		}
	}

	return nil
}

// EnumerateTags is called with a tag name as a string, loads the tag's
// descriptor and delegates to `enumerator.onGetTag` with the tag name
// and descriptor for further processing.
//
// This allows us to pass custom functions for migration and consistency
// checking whilst leveraging the same enumeration code.
func (e *enumerator) EnumerateTags(ctx context.Context, repo distribution.Repository, tagName string) error {
	// TagService.All returns a slice of strings instead of a concrete
	// distribution.Descriptor. Here we transform the tag name into a
	// descriptor and call the supplied onGetTag function.
	desc, err := repo.Tags(ctx).Get(ctx, tagName)
	if err != nil {
		return stacktrace.NewError("failed retrieving tag descriptor for tag %s: %s", tagName, err)
	}

	return e.onGetTag(ctx, repo, tagName, desc)
}