// Package image consolidates knowledge about various container image formats
// (as opposed to image storage mechanisms, which are handled by types.ImageSource)
// and exposes all of them using an unified interface.
package image

import (
	"context"

	"github.com/containers/image/v5/types"
)

// imageCloser implements types.ImageCloser, perhaps allowing simple users
// to use a single object without having keep a reference to a types.ImageSource
// only to call types.ImageSource.Close().
type imageCloser struct {
	types.Image
	src types.ImageSource
}

// FromSource returns a types.ImageCloser implementation for the default instance of source.
// If source is a manifest list, .Manifest() still returns the manifest list,
// but other methods transparently return data from an appropriate image instance.
//
// The caller must call .Close() on the returned ImageCloser.
//
// FromSource “takes ownership” of the input ImageSource and will call src.Close()
// when the image is closed.  (This does not prevent callers from using both the
// Image and ImageSource objects simultaneously, but it means that they only need to
// the Image.)
//
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage instead of calling this function.
func FromSource(ctx context.Context, sys *types.SystemContext, src types.ImageSource) (types.ImageCloser, error) {
	img, err := FromUnparsedImage(ctx, sys, UnparsedInstance(src, nil))
	if err != nil {
		return nil, err
	}
	return &imageCloser{
		Image: img,
		src:   src,
	}, nil
}

func (ic *imageCloser) Close() error {
	return ic.src.Close()
}

// sourcedImage is a general set of utilities for working with container images,
// whatever is their underlying location (i.e. dockerImageSource-independent).
// Note the existence of skopeo/docker.Image: some instances of a `types.Image`
// may not be a `sourcedImage` directly. However, most users of `types.Image`
// do not care, and those who care about `skopeo/docker.Image` know they do.
type sourcedImage struct {
	*UnparsedImage
	manifestBlob     []byte
	manifestMIMEType string
	// genericManifest contains data corresponding to manifestBlob.
	// NOTE: The manifest may have been modified in the process; DO NOT reserialize and store genericManifest
	// if you want to preserve the original manifest; use manifestBlob directly.
	genericManifest
}

// FromUnparsedImage returns a types.Image implementation for unparsed.
// If unparsed represents a manifest list, .Manifest() still returns the manifest list,
// but other methods transparently return data from an appropriate single image.
//
// The Image must not be used after the underlying ImageSource is Close()d.
func FromUnparsedImage(ctx context.Context, sys *types.SystemContext, unparsed *UnparsedImage) (types.Image, error) {
	// Note that the input parameter above is specifically *image.UnparsedImage, not types.UnparsedImage:
	// we want to be able to use unparsed.src.  We could make that an explicit interface, but, well,
	// this is the only UnparsedImage implementation around, anyway.

	// NOTE: It is essential for signature verification that all parsing done in this object happens on the same manifest which is returned by unparsed.Manifest().
	manifestBlob, manifestMIMEType, err := unparsed.Manifest(ctx)
	if err != nil {
		return nil, err
	}

	parsedManifest, err := manifestInstanceFromBlob(ctx, sys, unparsed.src, manifestBlob, manifestMIMEType)
	if err != nil {
		return nil, err
	}

	return &sourcedImage{
		UnparsedImage:    unparsed,
		manifestBlob:     manifestBlob,
		manifestMIMEType: manifestMIMEType,
		genericManifest:  parsedManifest,
	}, nil
}

// Size returns the size of the image as stored, if it's known, or -1 if it isn't.
func (i *sourcedImage) Size() (int64, error) {
	return -1, nil
}

// Manifest overrides the UnparsedImage.Manifest to always use the fields which we have already fetched.
func (i *sourcedImage) Manifest(ctx context.Context) ([]byte, string, error) {
	return i.manifestBlob, i.manifestMIMEType, nil
}

func (i *sourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
	return i.UnparsedImage.src.LayerInfosForCopy(ctx, i.UnparsedImage.instanceDigest)
}
