package nori

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

	"git.tuxpa.in/a/zlog/log"
	"github.com/disintegration/imaging"
	"github.com/phrozen/blend"
	"gitlab.com/gfxlabs/gfximg/apng"
)

func copyImage(img image.Image) image.Image {
	copied := image.NewNRGBA(img.Bounds())
	draw.Draw(copied, img.Bounds(), img, img.Bounds().Min, draw.Over)
	return copied
}

type RenderFunc = func(img image.Image, delay int)

func RenderAnimation(n *Nori, num int, f RenderFunc) error {
	g := n.Gawi
	if len(n.Animations[num].Frames) == 0 {
		return fmt.Errorf("no frames found for animation")
	}
	images := make([]image.Image, 0, len(n.Animations[num].Frames))
	durations := make([]uint32, 0, len(n.Animations[num].Frames))
	for _, frame := range n.Animations[num].Frames {
		planes := frame.Planes
		var canvasRect image.Rectangle
		for i, plane := range planes {
			if int(plane.BitmapId) >= len(g.Images) {
				log.Printf("could not find bitmap %d, only have %d", plane.BitmapId, len(g.Images))
			}
			bitmap := g.Images[plane.BitmapId]
			if len(bitmap.SubImages) == 0 {
				return fmt.Errorf("no subimages in bitmap")
			}
			pt := image.Pt(int(plane.PlaneX), int(plane.PlaneY)) // where to put the point
			rc := bitmap.SubImages[0].Img.Bounds().Add(pt)       // translate rectangle to put in the global canvas
			if i == 0 {
				canvasRect = rc
			} else {
				canvasRect = canvasRect.Union(rc)
			}
		}
		img := image.NewNRGBA(canvasRect)
		for _, plane := range planes {
			bitmap := g.Images[plane.BitmapId]
			drawBitmap := bitmap.SubImages[0].Img
			transparent := false
			//flipx
			if plane.RenderFlag&1 != 0 {
				if bitmap.SubImages[0].Img == drawBitmap {
					drawBitmap = copyImage(bitmap.SubImages[0].Img)
				}
				drawBitmap = imaging.FlipH(drawBitmap)
			}
			//flipy
			if plane.RenderFlag&2 != 0 {
				if bitmap.SubImages[0].Img == drawBitmap {
					drawBitmap = copyImage(bitmap.SubImages[0].Img)
				}
				drawBitmap = imaging.FlipV(drawBitmap)
			}
			// is transparent
			if plane.RenderFlag&0x20 != 0 {
				transparent = true
			}
			_ = transparent
			pt := image.Pt(int(plane.PlaneX), int(plane.PlaneY)) // where to put the point
			rc := drawBitmap.Bounds().Add(pt)
			src := drawBitmap
			switch plane.Blend {
			case BlendMode_InvertMul, BlendMode_InvertMul5:
				img = imaging.Invert(img)
				fallthrough
			case BlendMode_Alpha:
			case BlendMode_Mul, BlendMode_Mul7:
				blend.BlendImage(img, drawBitmap, blend.Multiply)
			case BlendMode_Add, BlendMode_Add8:
				blend.BlendImage(img, drawBitmap, blend.Add)
			case BlendMode_None:
			default:
				return fmt.Errorf("unknown blend mode: %d", plane.Blend)
			}

			mask := (image.Image)(nil)
			if transparent {
				mask = &image.Uniform{C: color.RGBA{A: 160}}
			}

			draw.DrawMask(
				img,
				rc,
				src,
				src.Bounds().Min,
				mask,
				src.Bounds().Min,
				draw.Over,
			)
		}
		images = append(images, img)
		durations = append(durations, frame.Duration)
	}
	or := images[0].Bounds()
	for _, realFrame := range images {
		or = realFrame.Bounds().Union(or)
	}
	if or.Size().X == 0 || or.Size().Y == 0 {
		return fmt.Errorf("no frames found for animation")
	}
	for i, realFrame := range images {
		//log.Info().Int("frame", num).Interface("rect", or).Interface("frame", realFrame.Bounds()).Msg("")
		src := realFrame
		big := image.NewNRGBA64(or)
		draw.Draw(
			big,
			src.Bounds(),
			src,
			src.Bounds().Min,
			draw.Src,
		)
		fr := apng.Frame{
			Image:            big,
			DelayDenominator: 1000,
			DelayNumerator:   uint16(durations[i]),
		}
		f(big, int(fr.DelayNumerator))
	}
	return nil

}
