package storage

import (
	"path"

	"github.com/docker/distribution"
	"github.com/docker/distribution/digest"
	storagedriver "github.com/docker/distribution/registry/storage/driver"
)

// tagStore provides methods to manage manifest tags in a backend storage driver.
type tagStore struct {
	*repository
}

// tags lists the manifest tags for the specified repository.
func (ts *tagStore) tags() ([]string, error) {
	p, err := ts.pm.path(manifestTagPathSpec{
		name: ts.name,
	})
	if err != nil {
		return nil, err
	}

	var tags []string
	entries, err := ts.driver.List(p)
	if err != nil {
		switch err := err.(type) {
		case storagedriver.PathNotFoundError:
			return nil, distribution.ErrRepositoryUnknown{Name: ts.name}
		default:
			return nil, err
		}
	}

	for _, entry := range entries {
		_, filename := path.Split(entry)

		tags = append(tags, filename)
	}

	return tags, nil
}

// exists returns true if the specified manifest tag exists in the repository.
func (ts *tagStore) exists(tag string) (bool, error) {
	tagPath, err := ts.pm.path(manifestTagCurrentPathSpec{
		name: ts.Name(),
		tag:  tag,
	})
	if err != nil {
		return false, err
	}

	exists, err := exists(ts.driver, tagPath)
	if err != nil {
		return false, err
	}

	return exists, nil
}

// tag tags the digest with the given tag, updating the the store to point at
// the current tag. The digest must point to a manifest.
func (ts *tagStore) tag(tag string, revision digest.Digest) error {
	indexEntryPath, err := ts.pm.path(manifestTagIndexEntryLinkPathSpec{
		name:     ts.Name(),
		tag:      tag,
		revision: revision,
	})

	if err != nil {
		return err
	}

	currentPath, err := ts.pm.path(manifestTagCurrentPathSpec{
		name: ts.Name(),
		tag:  tag,
	})

	if err != nil {
		return err
	}

	// Link into the index
	if err := ts.blobStore.link(indexEntryPath, revision); err != nil {
		return err
	}

	// Overwrite the current link
	return ts.blobStore.link(currentPath, revision)
}

// resolve the current revision for name and tag.
func (ts *tagStore) resolve(tag string) (digest.Digest, error) {
	currentPath, err := ts.pm.path(manifestTagCurrentPathSpec{
		name: ts.Name(),
		tag:  tag,
	})

	if err != nil {
		return "", err
	}

	if exists, err := exists(ts.driver, currentPath); err != nil {
		return "", err
	} else if !exists {
		return "", distribution.ErrManifestUnknown{Name: ts.Name(), Tag: tag}
	}

	revision, err := ts.blobStore.readlink(currentPath)
	if err != nil {
		return "", err
	}

	return revision, nil
}

// revisions returns all revisions with the specified name and tag.
func (ts *tagStore) revisions(tag string) ([]digest.Digest, error) {
	manifestTagIndexPath, err := ts.pm.path(manifestTagIndexPathSpec{
		name: ts.Name(),
		tag:  tag,
	})

	if err != nil {
		return nil, err
	}

	// TODO(stevvooe): Need to append digest alg to get listing of revisions.
	manifestTagIndexPath = path.Join(manifestTagIndexPath, "sha256")

	entries, err := ts.driver.List(manifestTagIndexPath)
	if err != nil {
		return nil, err
	}

	var revisions []digest.Digest
	for _, entry := range entries {
		revisions = append(revisions, digest.NewDigestFromHex("sha256", path.Base(entry)))
	}

	return revisions, nil
}

// delete removes the tag from repository, including the history of all
// revisions that have the specified tag.
func (ts *tagStore) delete(tag string) error {
	tagPath, err := ts.pm.path(manifestTagPathSpec{
		name: ts.Name(),
		tag:  tag,
	})
	if err != nil {
		return err
	}

	return ts.driver.Delete(tagPath)
}
