// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package portable implements a sprite Engine using the image package.
//
// It is intended to serve as a reference implementation for testing
// other sprite Engines written against OpenGL, or other more exotic
// modern hardware interfaces.
package portable

import (
	"image"
	"image/draw"

	xdraw "golang.org/x/image/draw"
	"golang.org/x/image/math/f64"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/exp/f32"
	"golang.org/x/mobile/exp/sprite"
	"golang.org/x/mobile/exp/sprite/clock"
)

// Engine builds a sprite Engine that renders onto dst.
func Engine(dst *image.RGBA) sprite.Engine {
	return &engine{
		dst:   dst,
		nodes: []*node{nil},
	}
}

type node struct {
	// TODO: move this into package sprite as Node.EngineFields.RelTransform??
	relTransform f32.Affine
}

type texture struct {
	m *image.RGBA
}

func (t *texture) Bounds() (w, h int) {
	b := t.m.Bounds()
	return b.Dx(), b.Dy()
}

func (t *texture) Download(r image.Rectangle, dst draw.Image) {
	draw.Draw(dst, r, t.m, t.m.Bounds().Min, draw.Src)
}

func (t *texture) Upload(r image.Rectangle, src image.Image) {
	draw.Draw(t.m, r, src, src.Bounds().Min, draw.Src)
}

func (t *texture) Release() {}

type engine struct {
	dst           *image.RGBA
	nodes         []*node
	absTransforms []f32.Affine
}

func (e *engine) Register(n *sprite.Node) {
	if n.EngineFields.Index != 0 {
		panic("portable: sprite.Node already registered")
	}

	o := &node{}
	o.relTransform.Identity()

	e.nodes = append(e.nodes, o)
	n.EngineFields.Index = int32(len(e.nodes) - 1)
}

func (e *engine) Unregister(n *sprite.Node) {
	panic("todo")
}

func (e *engine) LoadTexture(m image.Image) (sprite.Texture, error) {
	b := m.Bounds()
	w, h := b.Dx(), b.Dy()

	t := &texture{m: image.NewRGBA(image.Rect(0, 0, w, h))}
	t.Upload(b, m)
	return t, nil
}

func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
	n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
	n.EngineFields.SubTex = x
}

func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
	n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
	e.nodes[n.EngineFields.Index].relTransform = m
}

func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) {
	// Affine transforms are done in geom.Pt. When finally drawing
	// the geom.Pt onto an image.Image we need to convert to system
	// pixels. We scale by sz.PixelsPerPt to do this.
	e.absTransforms = append(e.absTransforms[:0], f32.Affine{
		{sz.PixelsPerPt, 0, 0},
		{0, sz.PixelsPerPt, 0},
	})
	e.render(scene, t)
}

func (e *engine) render(n *sprite.Node, t clock.Time) {
	if n.EngineFields.Index == 0 {
		panic("portable: sprite.Node not registered")
	}
	if n.Arranger != nil {
		n.Arranger.Arrange(e, n, t)
	}

	// Push absTransforms.
	// TODO: cache absolute transforms and use EngineFields.Dirty?
	rel := &e.nodes[n.EngineFields.Index].relTransform
	m := f32.Affine{}
	m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
	e.absTransforms = append(e.absTransforms, m)

	if x := n.EngineFields.SubTex; x.T != nil {
		// Affine transforms work in geom.Pt, which is entirely
		// independent of the number of pixels in a texture. A texture
		// of any image.Rectangle bounds rendered with
		//
		//	Affine{{1, 0, 0}, {0, 1, 0}}
		//
		// should have the dimensions (1pt, 1pt). To do this we divide
		// by the pixel width and height, reducing the texture to
		// (1px, 1px) of the destination image. Multiplying by
		// sz.PixelsPerPt, done in Render above, makes it (1pt, 1pt).
		dx, dy := x.R.Dx(), x.R.Dy()
		if dx > 0 && dy > 0 {
			m.Scale(&m, 1/float32(dx), 1/float32(dy))
			// TODO(nigeltao): delete the double-inverse: one here and one
			// inside func affine.
			m.Inverse(&m) // See the documentation on the affine function.
			affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over)
		}
	}

	for c := n.FirstChild; c != nil; c = c.NextSibling {
		e.render(c, t)
	}

	// Pop absTransforms.
	e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
}

func (e *engine) Release() {}

// affine draws each pixel of dst using bilinear interpolation of the
// affine-transformed position in src. This is equivalent to:
//
//	for each (x,y) in dst:
//	        dst(x,y) = bilinear interpolation of src(a*(x,y))
//
// While this is the simpler implementation, it can be counter-
// intuitive as an affine transformation is usually described in terms
// of the source, not the destination. For example, a scale transform
//
//	Affine{{2, 0, 0}, {0, 2, 0}}
//
// will produce a dst that is half the size of src. To perform a
// traditional affine transform, use the inverse of the affine matrix.
func affine(dst *image.RGBA, src image.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) {
	// For legacy compatibility reasons, the matrix a transforms from dst-space
	// to src-space. The golang.org/x/image/draw package's matrices transform
	// from src-space to dst-space, so we invert (and adjust for different
	// origins).
	i := *a
	i[0][2] += float32(srcb.Min.X)
	i[1][2] += float32(srcb.Min.Y)
	i.Inverse(&i)
	i[0][2] += float32(dst.Rect.Min.X)
	i[1][2] += float32(dst.Rect.Min.Y)
	m := f64.Aff3{
		float64(i[0][0]),
		float64(i[0][1]),
		float64(i[0][2]),
		float64(i[1][0]),
		float64(i[1][1]),
		float64(i[1][2]),
	}
	// TODO(nigeltao): is the caller or callee responsible for detecting
	// transforms that are simple copies or scales, for which there are faster
	// implementations in the xdraw package.
	xdraw.ApproxBiLinear.Transform(dst, m, src, srcb, op, &xdraw.Options{
		SrcMask: mask,
	})
}
