package internal

import "fyne.io/fyne/v2"

// ClipStack keeps track of the areas that should be clipped when drawing a canvas.
// If no clips are present then adding one will be added as-is.
// Subsequent items pushed will be completely within the previous clip.
type ClipStack struct {
	clips []*ClipItem
}

// Pop removes the current top clip and returns it.
func (c *ClipStack) Pop() *ClipItem {
	if len(c.clips) == 0 {
		return nil
	}

	top := len(c.clips) - 1
	ret := c.clips[top]
	c.clips[top] = nil // release memory reference
	c.clips = c.clips[:top]
	return ret
}

// Length  returns the number of items in this clip stack. 0 means no clip.
func (c *ClipStack) Length() int {
	return len(c.clips)
}

// Push a new clip onto this stack at position and size specified.
// The returned clip item is the result of calculating the intersection of the requested clip and its parent.
func (c *ClipStack) Push(p fyne.Position, s fyne.Size) *ClipItem {
	outer := c.Top()
	inner := outer.Intersect(p, s)

	c.clips = append(c.clips, inner)
	return inner
}

// Top returns the current clip item - it will always be within the bounds of any parent clips.
func (c *ClipStack) Top() *ClipItem {
	if len(c.clips) == 0 {
		return nil
	}

	return c.clips[len(c.clips)-1]
}

// ClipItem represents a single clip in a clip stack, denoted by a size and position.
type ClipItem struct {
	pos  fyne.Position
	size fyne.Size
}

// Rect returns the position and size parameters of the clip.
func (i *ClipItem) Rect() (fyne.Position, fyne.Size) {
	return i.pos, i.size
}

// Intersect returns a new clip item that is the intersection of the requested parameters and this clip.
func (i *ClipItem) Intersect(p fyne.Position, s fyne.Size) *ClipItem {
	ret := &ClipItem{p, s}
	if i == nil {
		return ret
	}

	if ret.pos.X < i.pos.X {
		ret.pos.X = i.pos.X
		ret.size.Width -= i.pos.X - p.X
	}
	if ret.pos.Y < i.pos.Y {
		ret.pos.Y = i.pos.Y
		ret.size.Height -= i.pos.Y - p.Y
	}

	if p.X+s.Width > i.pos.X+i.size.Width {
		ret.size.Width = (i.pos.X + i.size.Width) - ret.pos.X
	}
	if p.Y+s.Height > i.pos.Y+i.size.Height {
		ret.size.Height = (i.pos.Y + i.size.Height) - ret.pos.Y
	}

	if ret.size.Width < 0 || ret.size.Height < 0 {
		ret.size = fyne.NewSize(0, 0)
		return ret
	}

	return ret
}
