package thumb

import (
	"context"
	"fmt"
	"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource"
	"github.com/cloudreve/Cloudreve/v4/pkg/setting"
	"github.com/cloudreve/Cloudreve/v4/pkg/util"
	"github.com/gofrs/uuid"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"path/filepath"
	//"github.com/nfnt/resize"
	"golang.org/x/image/draw"
)

const thumbTempFolder = "thumb"

// Thumb 缩略图
type Thumb struct {
	src image.Image
	ext string
}

// NewThumbFromFile 从文件数据获取新的Thumb对象，
// 尝试通过文件名name解码图像
func NewThumbFromFile(file io.Reader, ext string) (*Thumb, error) {
	// 无扩展名时
	if ext == "" {
		return nil, fmt.Errorf("unknown image format: %w", ErrPassThrough)
	}

	var err error
	var img image.Image
	switch ext {
	case "jpg", "jpeg":
		img, err = jpeg.Decode(file)
	case "gif":
		img, err = gif.Decode(file)
	case "png":
		img, err = png.Decode(file)
	default:
		return nil, fmt.Errorf("unknown image format %q: %w", ext, ErrPassThrough)
	}
	if err != nil {
		return nil, fmt.Errorf("failed to parse image: %w (%w)", err, ErrPassThrough)
	}

	return &Thumb{
		src: img,
		ext: ext[1:],
	}, nil
}

// GetThumb 生成给定最大尺寸的缩略图
func (image *Thumb) GetThumb(width, height uint) {
	//image.src = resize.Thumbnail(width, height, image.src, resize.Lanczos3)
	image.src = Thumbnail(width, height, image.src)
}

// GetSize 获取图像尺寸
func (image *Thumb) GetSize() (int, int) {
	b := image.src.Bounds()
	return b.Max.X, b.Max.Y
}

// Save 保存图像到给定路径
func (image *Thumb) Save(w io.Writer, encodeSetting *setting.ThumbEncode) (err error) {
	switch encodeSetting.Format {
	case "png":
		err = png.Encode(w, image.src)
	default:
		err = jpeg.Encode(w, image.src, &jpeg.Options{Quality: encodeSetting.Quality})
	}

	return err

}

// Thumbnail will downscale provided image to max width and height preserving
// original aspect ratio and using the interpolation function interp.
// It will return original image, without processing it, if original sizes
// are already smaller than provided constraints.
func Thumbnail(maxWidth, maxHeight uint, img image.Image) image.Image {
	origBounds := img.Bounds()
	origWidth := uint(origBounds.Dx())
	origHeight := uint(origBounds.Dy())
	newWidth, newHeight := origWidth, origHeight

	// Return original image if it have same or smaller size as constraints
	if maxWidth >= origWidth && maxHeight >= origHeight {
		return img
	}

	// Preserve aspect ratio
	if origWidth > maxWidth {
		newHeight = uint(origHeight * maxWidth / origWidth)
		if newHeight < 1 {
			newHeight = 1
		}
		newWidth = maxWidth
	}

	if newHeight > maxHeight {
		newWidth = uint(newWidth * maxHeight / newHeight)
		if newWidth < 1 {
			newWidth = 1
		}
		newHeight = maxHeight
	}
	return Resize(newWidth, newHeight, img)
}

func Resize(newWidth, newHeight uint, img image.Image) image.Image {
	// Set the expected size that you want:
	dst := image.NewRGBA(image.Rect(0, 0, int(newWidth), int(newHeight)))
	// Resize:
	draw.BiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Src, nil)
	return dst
}

// CreateAvatar 创建头像
func (image *Thumb) CreateAvatar(width int) {
	image.src = Resize(uint(width), uint(width), image.src)
}

type Builtin struct {
	settings setting.Provider
}

func NewBuiltinGenerator(settings setting.Provider) *Builtin {
	return &Builtin{
		settings: settings,
	}
}

func (b Builtin) Generate(ctx context.Context, es entitysource.EntitySource, ext string, previous *Result) (*Result, error) {
	if es.Entity().Size() > b.settings.BuiltinThumbMaxSize(ctx) {
		return nil, fmt.Errorf("file is too big: %w", ErrPassThrough)
	}

	img, err := NewThumbFromFile(es, ext)
	if err != nil {
		return nil, err
	}

	w, h := b.settings.ThumbSize(ctx)
	img.GetThumb(uint(w), uint(h))
	tempPath := filepath.Join(
		util.DataPath(b.settings.TempPath(ctx)),
		thumbTempFolder,
		fmt.Sprintf("thumb_%s", uuid.Must(uuid.NewV4()).String()),
	)

	thumbFile, err := util.CreatNestedFile(tempPath)
	if err != nil {
		return nil, fmt.Errorf("failed to create temp file: %w", err)
	}

	defer thumbFile.Close()
	if err := img.Save(thumbFile, b.settings.ThumbEncode(ctx)); err != nil {
		return &Result{Path: tempPath}, err
	}

	return &Result{Path: tempPath}, nil
}

func (b Builtin) Priority() int {
	return 300
}

func (b Builtin) Enabled(ctx context.Context) bool {
	return b.settings.BuiltinThumbGeneratorEnabled(ctx)
}
