package barertc

import (
	"bytes"
	"image"
	"image/jpeg"
	"image/png"
	"io"

	"git.kirsle.net/apps/barertc/pkg/config"
	"git.kirsle.net/apps/barertc/pkg/log"
	"github.com/edwvee/exiffix"
	"golang.org/x/image/draw"
)

var (
	// TODO: configurable
	MaxPhotoWidth = 1280
)

// ProcessImage treats user uploaded images:
//
// - Scales them down to a reasonable size
// - Strips EXIF metadata
//
// and returns the modified image again as bytes.
//
// Also returns the suggested preview width, height to draw the image
// at. This may be smaller than its true width x height.
//
// Filetype should be image/jpeg, image/gif or image/png.
func ProcessImage(fileType string, data []byte) ([]byte, int, int) {
	reader := bytes.NewReader(data)

	// Strip EXIF data.
	origImage, _, err := exiffix.Decode(reader)
	if err != nil {
		log.Error("ProcessImage: exiffix: %s", err)
		return data, config.Current.PreviewImageWidth, config.Current.PreviewImageWidth
	}

	reader.Seek(0, io.SeekStart)
	var width, height = origImage.Bounds().Max.X, origImage.Bounds().Max.Y

	log.Info("ProcessImage: taking a %dx%d image", width, height)

	// Compute what size we should scale the width/height to,
	// and the even smaller preview size for front-end.
	var (
		previewWidth  = config.Current.PreviewImageWidth
		previewHeight = previewWidth
	)
	if width >= height {
		log.Debug("Its width(%d) is >= its height (%d)", width, height)
		if width > config.Current.MaxImageWidth {
			newWidth := config.Current.MaxImageWidth
			log.Debug("\tnewWidth=%d", newWidth)
			log.Debug("\tnewHeight=(%d / %d) * %d", width, height, newWidth)
			height = int((float64(height) / float64(width)) * float64(newWidth))
			width = newWidth
			log.Debug("Its longest is width, scale to %dx%d", width, height)
		}

		// Compute the preview width.
		if width > config.Current.PreviewImageWidth {
			newWidth := config.Current.PreviewImageWidth
			previewHeight = int((float64(height) / float64(width)) * float64(newWidth))
			previewWidth = newWidth
		}
	} else {
		if height > config.Current.MaxImageWidth {
			newHeight := config.Current.MaxImageWidth
			width = int((float64(width) / float64(height)) * float64(newHeight))
			height = newHeight
			log.Debug("Its longest is height, scale to %dx%d", width, height)
		}

		// Compute the preview height.
		if height > config.Current.PreviewImageWidth {
			newHeight := config.Current.PreviewImageWidth
			previewWidth = int((float64(width) / float64(height)) * float64(newHeight))
			previewHeight = newHeight
			log.Debug("Its longest is height, scale to %dx%d", previewWidth, previewHeight)
		}
	}

	// Scale the image.
	scaledImg := Scale(origImage, image.Rect(0, 0, width, height), draw.ApproxBiLinear)

	// Return the new bytes.
	var buf = bytes.NewBuffer([]byte{})
	switch fileType {
	case "image/jpeg":
		jpeg.Encode(buf, scaledImg, &jpeg.Options{
			Quality: 90,
		})
	case "image/gif":
		// Return the original data - we will only break it.
		return data, width, height
	case "image/png":
		png.Encode(buf, scaledImg)
	default:
		return data, config.Current.PreviewImageWidth, config.Current.PreviewImageWidth
	}

	return buf.Bytes(), previewWidth, previewHeight
}

// Scale down an image. Example:
//
// scaled := Scale(src, image.Rect(0, 0, 200, 200), draw.ApproxBiLinear)
func Scale(src image.Image, rect image.Rectangle, scale draw.Scaler) image.Image {
	dst := image.NewRGBA(rect)
	copyRect := image.Rect(
		rect.Min.X,
		rect.Min.Y,
		rect.Min.X+rect.Max.X,
		rect.Min.Y+rect.Max.Y,
	)
	scale.Scale(dst, copyRect, src, src.Bounds(), draw.Over, nil)
	return dst
}