package canvas

import (
	"image"
	"image/color"
	"image/draw"

	"fyne.io/fyne"
)

// Declare conformity with CanvasObject interface
var _ fyne.CanvasObject = (*Raster)(nil)

// Raster describes a raster image area that can render in a Fyne canvas
type Raster struct {
	baseObject

	Generator func(w, h int) image.Image // Render the raster image from code

	Translucency float64 // Set a translucency value > 0.0 to fade the raster

	img draw.Image // internal cache for pixel based generator functions
}

// Alpha is a convenience function that returns the alpha value for a raster
// based on its Translucency value. The result is 1.0 - Translucency.
func (r *Raster) Alpha() float64 {
	return 1.0 - r.Translucency
}

// Refresh causes this object to be redrawn in it's current state
func (r *Raster) Refresh() {
	Refresh(r)
}

// NewRaster returns a new Image instance that is rendered dynamically using
// the specified generate function.
// Images returned from this method should draw dynamically to fill the width
// and height parameters passed to pixelColor.
func NewRaster(generate func(w, h int) image.Image) *Raster {
	return &Raster{Generator: generate}
}

type pixelRaster struct {
	r *Raster

	img draw.Image
}

// NewRasterWithPixels returns a new Image instance that is rendered dynamically
// by iterating over the specified pixelColor function for each x, y pixel.
// Images returned from this method should draw dynamically to fill the width
// and height parameters passed to pixelColor.
func NewRasterWithPixels(pixelColor func(x, y, w, h int) color.Color) *Raster {
	pix := &pixelRaster{}
	pix.r = &Raster{
		Generator: func(w, h int) image.Image {
			if pix.img == nil || pix.img.Bounds().Size().X != w || pix.img.Bounds().Size().Y != h {
				// raster first pixel, figure out color type
				var dst draw.Image
				rect := image.Rect(0, 0, w, h)
				switch pixelColor(0, 0, w, h).(type) {
				case color.Alpha:
					dst = image.NewAlpha(rect)
				case color.Alpha16:
					dst = image.NewAlpha16(rect)
				case color.CMYK:
					dst = image.NewCMYK(rect)
				case color.Gray:
					dst = image.NewGray(rect)
				case color.Gray16:
					dst = image.NewGray16(rect)
				case color.NRGBA:
					dst = image.NewNRGBA(rect)
				case color.NRGBA64:
					dst = image.NewNRGBA64(rect)
				case color.RGBA:
					dst = image.NewRGBA(rect)
				case color.RGBA64:
					dst = image.NewRGBA64(rect)
				default:
					dst = image.NewRGBA(rect)
				}
				pix.img = dst
			}

			for x := 0; x < w; x++ {
				for y := 0; y < h; y++ {
					pix.img.Set(x, y, pixelColor(x, y, w, h))
				}
			}

			return pix.img
		},
	}
	return pix.r
}

type subImg interface {
	SubImage(r image.Rectangle) image.Image
}

// NewRasterFromImage returns a new Raster instance that is rendered from the Go
// image.Image passed in.
// Rasters returned from this method will map pixel for pixel to the screen
// starting img.Bounds().Min pixels from the top left of the canvas object.
// Truncates rather than scales the image.
// If smaller than the target space, the image will be padded with zero-pixels to the target size.
func NewRasterFromImage(img image.Image) *Raster {
	return &Raster{
		Generator: func(w int, h int) image.Image {
			bounds := img.Bounds()

			rect := image.Rect(0, 0, w, h)

			switch {
			case w == bounds.Max.X && h == bounds.Max.Y:
				return img
			case w >= bounds.Max.X && h >= bounds.Max.Y:
				// try quickly truncating
				if sub, ok := img.(subImg); ok {
					return sub.SubImage(image.Rectangle{
						Min: bounds.Min,
						Max: image.Point{
							X: bounds.Min.X + w,
							Y: bounds.Min.Y + h,
						},
					})
				}
			default:
				if !rect.Overlaps(bounds) {
					return image.NewUniform(color.RGBA{})
				}
				bounds = bounds.Intersect(rect)
			}

			// respect the user's pixel format (if possible)
			var dst draw.Image
			switch i := img.(type) {
			case (*image.Alpha):
				dst = image.NewAlpha(rect)
			case (*image.Alpha16):
				dst = image.NewAlpha16(rect)
			case (*image.CMYK):
				dst = image.NewCMYK(rect)
			case (*image.Gray):
				dst = image.NewGray(rect)
			case (*image.Gray16):
				dst = image.NewGray16(rect)
			case (*image.NRGBA):
				dst = image.NewNRGBA(rect)
			case (*image.NRGBA64):
				dst = image.NewNRGBA64(rect)
			case (*image.Paletted):
				dst = image.NewPaletted(rect, i.Palette)
			case (*image.RGBA):
				dst = image.NewRGBA(rect)
			case (*image.RGBA64):
				dst = image.NewRGBA64(rect)
			default:
				dst = image.NewRGBA(rect)
			}

			draw.Draw(dst, bounds, img, bounds.Min, draw.Over)
			return dst
		},
	}
}
