package cbconvert

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

	"github.com/anthonynsimon/bild/adjust"
	"github.com/anthonynsimon/bild/transform"
)

// Resample filters.
const (
	// NearestNeighbor is the fastest resampling filter, no antialiasing.
	nearestNeighbor int = iota
	// Box filter (averaging pixels).
	box
	// Linear is the bilinear filter, smooth and reasonably fast.
	linear
	// MitchellNetravali is a smooth bicubic filter.
	mitchellNetravali
	// CatmullRom is a sharp bicubic filter.
	catmullRom
	// Gaussian is a blurring filter, which uses gaussian function, useful for noise removal.
	gaussian
	// Lanczos is a high-quality resampling filter, it's slower than cubic filters.
	lanczos
)

var filters = map[int]transform.ResampleFilter{
	nearestNeighbor:   transform.NearestNeighbor,
	box:               transform.Box,
	linear:            transform.Linear,
	mitchellNetravali: transform.MitchellNetravali,
	catmullRom:        transform.CatmullRom,
	gaussian:          transform.Gaussian,
	lanczos:           transform.Lanczos,
}

// resampleFilter returns the resample filter for index i, falling back to Linear for an unknown index.
func resampleFilter(i int) transform.ResampleFilter {
	if f, ok := filters[i]; ok {
		return f
	}

	return filters[linear]
}

func resize(img image.Image, width, height int, filter transform.ResampleFilter) *image.RGBA {
	dstW, dstH := width, height

	srcW := img.Bounds().Dx()
	srcH := img.Bounds().Dy()

	if dstW == 0 {
		tmpW := float64(dstH) * float64(srcW) / float64(srcH)
		dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
	}
	if dstH == 0 {
		tmpH := float64(dstW) * float64(srcH) / float64(srcW)
		dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
	}

	if srcW == dstW && srcH == dstH {
		return imageToRGBA(img)
	}

	return transform.Resize(img, dstW, dstH, filter)
}

func fit(img image.Image, width, height int, filter transform.ResampleFilter) *image.RGBA {
	maxW, maxH := width, height

	b := img.Bounds()
	srcW := b.Dx()
	srcH := b.Dy()

	if srcW <= maxW && srcH <= maxH {
		return imageToRGBA(img)
	}

	srcAspectRatio := float64(srcW) / float64(srcH)
	maxAspectRatio := float64(maxW) / float64(maxH)

	var dstW, dstH int
	if srcAspectRatio > maxAspectRatio {
		dstW = maxW
		dstH = int(float64(dstW) / srcAspectRatio)
	} else {
		dstH = maxH
		dstW = int(float64(dstH) * srcAspectRatio)
	}

	return resize(img, dstW, dstH, filter)
}

// withinBounds reports whether img already fits within width by height; a zero dimension is unbounded.
func withinBounds(img image.Image, width, height int) bool {
	b := img.Bounds()

	return (width == 0 || b.Dx() <= width) && (height == 0 || b.Dy() <= height)
}

// resizeFit resizes img to the configured width/height, honoring Fit and NoUpscale.
func (c *Converter) resizeFit(img image.Image) image.Image {
	if c.Opts.Fit {
		return fit(img, c.Opts.Width, c.Opts.Height, resampleFilter(c.Opts.Filter))
	}

	if c.Opts.NoUpscale && withinBounds(img, c.Opts.Width, c.Opts.Height) {
		return img
	}

	return resize(img, c.Opts.Width, c.Opts.Height, resampleFilter(c.Opts.Filter))
}

func rotate(img image.Image, angle float64) *image.RGBA {
	return transform.Rotate(img, angle, &transform.RotationOptions{ResizeBounds: true, Pivot: &image.Point{}})
}

func brightness(img image.Image, change float64) *image.RGBA {
	return adjust.Brightness(img, change/100)
}

func contrast(img image.Image, change float64) *image.RGBA {
	return adjust.Contrast(img, change/100)
}

// imageToRGBA converts an image.Image to *image.RGBA.
func imageToRGBA(src image.Image) *image.RGBA {
	if dst, ok := src.(*image.RGBA); ok {
		return dst
	}

	b := src.Bounds()
	dst := image.NewRGBA(b)
	draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)

	return dst
}

// imageToGray converts an image.Image to *image.Gray.
func imageToGray(src image.Image) *image.Gray {
	if dst, ok := src.(*image.Gray); ok {
		return dst
	}

	b := src.Bounds()
	dst := image.NewGray(b)
	draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)

	return dst
}

// isGrayScale checks if the image is grayscale.
func isGrayScale(img image.Image) bool {
	model := img.ColorModel()
	if model == color.GrayModel || model == color.Gray16Model {
		return true
	}

	return false
}

var colors16 = []color.Color{
	color.RGBA{A: 255},
	color.RGBA{R: 17, G: 17, B: 17, A: 255},
	color.RGBA{R: 34, G: 34, B: 34, A: 255},
	color.RGBA{R: 51, G: 51, B: 51, A: 255},
	color.RGBA{R: 68, G: 68, B: 68, A: 255},
	color.RGBA{R: 85, G: 85, B: 85, A: 255},
	color.RGBA{R: 102, G: 102, B: 102, A: 255},
	color.RGBA{R: 119, G: 119, B: 119, A: 255},
	color.RGBA{R: 136, G: 136, B: 136, A: 255},
	color.RGBA{R: 153, G: 153, B: 153, A: 255},
	color.RGBA{R: 170, G: 170, B: 170, A: 255},
	color.RGBA{R: 187, G: 187, B: 187, A: 255},
	color.RGBA{R: 204, G: 204, B: 204, A: 255},
	color.RGBA{R: 221, G: 221, B: 221, A: 255},
	color.RGBA{R: 238, G: 238, B: 238, A: 255},
	color.RGBA{R: 255, G: 255, B: 255, A: 255},
}

// imageToPaletted converts an image.Image to *image.Paletted using 16-color palette.
func imageToPaletted(src image.Image) *image.Paletted {
	b := src.Bounds()
	dst := image.NewPaletted(b, colors16)
	draw.FloydSteinberg.Draw(dst, b, imageToGray(src), b.Min)

	return dst
}
