// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package resources

import (
	"image"
	"io"

	"github.com/gohugoio/hugo/common/hugio"
	"github.com/gohugoio/hugo/resources/images"

	"github.com/gohugoio/hugo/cache/dynacache"
	"github.com/gohugoio/hugo/cache/filecache"
	"github.com/gohugoio/hugo/helpers"
)

// ImageCache is a cache for image resources. The backing caches are shared between all sites.
type ImageCache struct {
	pathSpec *helpers.PathSpec

	fcache *filecache.Cache
	mcache *dynacache.Partition[string, *resourceAdapter]
}

func (c *ImageCache) getOrCreate(
	parent *imageResource, conf images.ImageConfig,
	createImage func() (*imageResource, image.Image, error),
) (*resourceAdapter, error) {
	relTarget := parent.relTargetPathFromConfig(conf, parent.getSpec().Imaging.Cfg.SourceHash)
	relTargetPath := relTarget.TargetPath()
	memKey := relTargetPath

	// For multihost sites, we duplicate language versions of the same resource,
	// so we need to include the language in the key.
	// Note that we don't need to include the language in the file cache key,
	// as the hash will take care of any different content.
	if c.pathSpec.Cfg.IsMultihost() {
		memKey = c.pathSpec.Lang() + memKey
	}
	memKey = dynacache.CleanKey(memKey)

	v, err := c.mcache.GetOrCreate(memKey, func(key string) (*resourceAdapter, error) {
		var img *imageResource

		// These funcs are protected by a named lock.
		// read clones the parent to its new name and copies
		// the content to the destinations.
		read := func(info filecache.ItemInfo, r io.ReadSeeker) error {
			img = parent.clone(nil)
			targetPath := img.getResourcePaths()
			targetPath.File = relTarget.File
			img.setTargetPath(targetPath)
			img.setOpenSource(func() (hugio.ReadSeekCloser, error) {
				return c.fcache.Fs.Open(info.Name)
			})
			img.setSourceFilenameIsHash(true)
			img.setMediaType(conf.TargetFormat.MediaType())
			img.Format = conf.TargetFormat

			if err := img.InitConfig(r); err != nil {
				return err
			}

			return nil
		}

		// create creates the image and encodes it to the cache (w).
		create := func(info filecache.ItemInfo, w io.WriteCloser) (err error) {
			defer w.Close()

			var conv image.Image
			img, conv, err = createImage()
			if err != nil {
				return
			}
			targetPath := img.getResourcePaths()
			targetPath.File = relTarget.File
			img.setTargetPath(targetPath)
			img.setOpenSource(func() (hugio.ReadSeekCloser, error) {
				return c.fcache.Fs.Open(info.Name)
			})
			return img.EncodeTo(conf, conv, w)
		}

		// Now look in the file cache.

		// The definition of this counter is not that we have processed that amount
		// (e.g. resized etc.), it can be fetched from file cache,
		//  but the count of processed image variations for this site.
		c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages)

		_, err := c.fcache.ReadOrCreate(relTargetPath, read, create)
		if err != nil {
			return nil, err
		}

		imgAdapter := newResourceAdapter(parent.getSpec(), true, img)

		return imgAdapter, nil
	})

	return v, err
}

func newImageCache(fileCache *filecache.Cache, memCache *dynacache.Cache, ps *helpers.PathSpec) *ImageCache {
	return &ImageCache{
		fcache: fileCache,
		mcache: dynacache.GetOrCreatePartition[string, *resourceAdapter](
			memCache,
			"/imgs",
			dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 70},
		),
		pathSpec: ps,
	}
}
