package transform

import (
	"image"
	"math"

	"github.com/anthonynsimon/bild/clone"
	"github.com/anthonynsimon/bild/parallel"
)

// ShearH applies a shear linear transformation along the horizontal axis,
// the parameter angle is the shear angle to be applied.
// The transformation will be applied with the center of the image as the pivot.
func ShearH(img image.Image, angle float64) *image.RGBA {
	src := clone.AsShallowRGBA(img)
	srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()

	// Supersample, currently hard set to 2x
	srcW, srcH = srcW*2, srcH*2
	src = Resize(src, srcW, srcH, NearestNeighbor)

	// Calculate shear factor
	kx := math.Tan(angle * (math.Pi / 180))

	dstW, dstH := srcW+int(float64(srcH)*math.Abs(kx)), srcH
	dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))

	pivotX := float64(dstW) / 2
	pivotY := float64(dstH) / 2

	// Calculate offset since we are resizing the bounds to
	// fit the sheared image.
	dx := (dstW - srcW) / 2
	dy := (dstH - srcH) / 2

	parallel.Line(dstH, func(start, end int) {
		for y := start; y < end; y++ {
			for x := 0; x < dstW; x++ {
				// Move positions to revolve around pivot
				ix := x - int(pivotX) - dx
				iy := y - int(pivotY) - dy

				// Apply linear transformation
				ix = ix + int(float64(iy)*kx)

				// Move positions back to image coordinates
				ix += int(pivotX)
				iy += int(pivotY)

				if ix < 0 || ix >= srcW || iy < 0 || iy >= srcH {
					continue
				}

				srcPos := iy*src.Stride + ix*4
				dstPos := y*dst.Stride + x*4

				dst.Pix[dstPos+0] = src.Pix[srcPos+0]
				dst.Pix[dstPos+1] = src.Pix[srcPos+1]
				dst.Pix[dstPos+2] = src.Pix[srcPos+2]
				dst.Pix[dstPos+3] = src.Pix[srcPos+3]
			}
		}
	})

	// Downsample to original bounds as part of the Supersampling
	dst = Resize(dst, dstW/2, dstH/2, Linear)

	return dst
}

// ShearV applies a shear linear transformation along the vertical axis,
// the parameter angle is the shear angle to be applied.
// The transformation will be applied with the center of the image as the pivot.
func ShearV(img image.Image, angle float64) *image.RGBA {
	src := clone.AsRGBA(img)
	srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()

	// Supersample, currently hard set to 2x
	srcW, srcH = srcW*2, srcH*2
	src = Resize(src, srcW, srcH, NearestNeighbor)

	// Calculate shear factor
	ky := math.Tan(angle * (math.Pi / 180))

	dstW, dstH := srcW, srcH+int(float64(srcW)*math.Abs(ky))
	dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))

	pivotX := float64(dstW) / 2
	pivotY := float64(dstH) / 2

	// Calculate offset since we are resizing the bounds to
	// fit the sheared image.
	dx := (dstW - srcW) / 2
	dy := (dstH - srcH) / 2

	parallel.Line(dstH, func(start, end int) {
		for y := start; y < end; y++ {
			for x := 0; x < dstW; x++ {
				// Move positions to revolve around pivot
				ix := x - int(pivotX) - dx
				iy := y - int(pivotY) - dy

				// Apply linear transformation
				iy = iy + int(float64(ix)*ky)

				// Move positions back to image coordinates
				ix += int(pivotX)
				iy += int(pivotY)

				if ix < 0 || ix >= srcW || iy < 0 || iy >= srcH {
					continue
				}

				srcPos := iy*src.Stride + ix*4
				dstPos := y*dst.Stride + x*4

				dst.Pix[dstPos+0] = src.Pix[srcPos+0]
				dst.Pix[dstPos+1] = src.Pix[srcPos+1]
				dst.Pix[dstPos+2] = src.Pix[srcPos+2]
				dst.Pix[dstPos+3] = src.Pix[srcPos+3]
			}
		}
	})

	// Downsample to original bounds as part of the Supersampling
	dst = Resize(dst, dstW/2, dstH/2, Linear)

	return dst
}
