package server

import (
	"fmt"

	"github.com/docker/distribution"
	"github.com/docker/distribution/context"
	"github.com/docker/distribution/manifest/schema1"
	"github.com/docker/distribution/manifest/schema2"

	imageapi "github.com/openshift/origin/pkg/image/api"
)

// A ManifestHandler defines a common set of operations on all versions of manifest schema.
type ManifestHandler interface {
	// FillImageMetadata fills a given image with metadata parsed from manifest. It also corrects layer sizes
	// with blob sizes. Newer Docker client versions don't set layer sizes in the manifest schema 1 at all.
	// Origin master needs correct layer sizes for proper image quota support. That's why we need to fill the
	// metadata in the registry.
	FillImageMetadata(ctx context.Context, image *imageapi.Image) error

	// Manifest returns a deserialized manifest object.
	Manifest() distribution.Manifest

	// Payload returns manifest's media type, complete payload with signatures and canonical payload without
	// signatures or an error if the information could not be fetched.
	Payload() (mediaType string, payload []byte, canonical []byte, err error)

	// Verify returns an error if the contained manifest is not valid or has missing dependencies.
	Verify(ctx context.Context, skipDependencyVerification bool) error
}

// NewManifestHandler creates a manifest handler for the given manifest.
func NewManifestHandler(repo *repository, manifest distribution.Manifest) (ManifestHandler, error) {
	switch t := manifest.(type) {
	case *schema1.SignedManifest:
		return &manifestSchema1Handler{repo: repo, manifest: t}, nil
	case *schema2.DeserializedManifest:
		return &manifestSchema2Handler{repo: repo, manifest: t}, nil
	default:
		return nil, fmt.Errorf("unsupported manifest type %T", manifest)
	}
}

// NewManifestHandlerFromImage creates a new manifest handler for a manifest stored in the given image.
func NewManifestHandlerFromImage(repo *repository, image *imageapi.Image) (ManifestHandler, error) {
	var (
		manifest distribution.Manifest
		err      error
	)

	switch image.DockerImageManifestMediaType {
	case "", schema1.MediaTypeManifest:
		manifest, err = unmarshalManifestSchema1([]byte(image.DockerImageManifest), image.DockerImageSignatures)
	case schema2.MediaTypeManifest:
		manifest, err = unmarshalManifestSchema2([]byte(image.DockerImageManifest))
	default:
		return nil, fmt.Errorf("unsupported manifest media type %s", image.DockerImageManifestMediaType)
	}

	if err != nil {
		return nil, err
	}

	return NewManifestHandler(repo, manifest)
}