package artist

import "image"
import "image/color"

// Bordered is a pattern with a border and a fill.
type Bordered struct {
	Fill Pattern
	Stroke
}

// AtWhen satisfies the Pattern interface.
func (pattern Bordered) AtWhen (x, y, width, height int) (c color.RGBA) {
	outerBounds := image.Rectangle { Max: image.Point { width, height }}
	innerBounds := outerBounds.Inset(pattern.Weight)
	if (image.Point { x, y }).In (innerBounds) {
		return pattern.Fill.AtWhen (
			x - pattern.Weight,
			y - pattern.Weight,
			innerBounds.Dx(), innerBounds.Dy())
	} else {
		return pattern.Stroke.AtWhen(x, y, width, height)
	}
}

// Stroke represents a stoke that has a weight and a pattern.
type Stroke struct {
	Weight int
	Pattern
}

type borderInternal struct {
	weight int
	stroke Pattern
	bounds image.Rectangle
	dx, dy int
}

// MultiBordered is a pattern that allows multiple borders of different lengths
// to be inset within one another. The final border is treated as a fill color,
// and its weight does not matter.
type MultiBordered struct {
	borders []borderInternal
	lastWidth, lastHeight int
	maxBorder int
}

// NewMultiBordered creates a new MultiBordered pattern from the given list of
// borders.
func NewMultiBordered (borders ...Stroke) (multi *MultiBordered) {
	internalBorders := make([]borderInternal, len(borders))
	for index, border := range borders {
		internalBorders[index].weight = border.Weight
		internalBorders[index].stroke = border.Pattern
	}
	return &MultiBordered { borders: internalBorders }
}

// AtWhen satisfies the Pattern interface.
func (multi *MultiBordered) AtWhen (x, y, width, height int) (c color.RGBA) {
	if multi.lastWidth != width || multi.lastHeight != height {
		multi.recalculate(width, height)
	}
	point := image.Point { x, y }
	for index := multi.maxBorder; index >= 0; index -- {
		border := multi.borders[index]
		if point.In(border.bounds) {
			return border.stroke.AtWhen (
				point.X - border.bounds.Min.X,
				point.Y - border.bounds.Min.Y,
				border.dx, border.dy)
		}
	}
	return
}

func (multi *MultiBordered) recalculate (width, height int) {
	bounds := image.Rect (0, 0, width, height)
	multi.maxBorder = 0
	for index, border := range multi.borders {
		multi.maxBorder = index
		multi.borders[index].bounds = bounds
		multi.borders[index].dx = bounds.Dx()
		multi.borders[index].dy = bounds.Dy()
		bounds = bounds.Inset(border.weight)
		if bounds.Empty() { break }
	}
}

// Padded is a pattern that surrounds a central fill pattern with a border that
// can have a different width for each side.
type Padded struct {
	Fill   Pattern
	Stroke Pattern
	Sides []int
}

// AtWhen satisfies the Pattern interface.
func (pattern Padded) AtWhen (x, y, width, height int) (c color.RGBA) {
	innerBounds := image.Rect (
		pattern.Sides[3], pattern.Sides[0],
		width - pattern.Sides[1], height - pattern.Sides[2])
	if (image.Point { x, y }).In (innerBounds) {
		return pattern.Fill.AtWhen (
			x - pattern.Sides[3],
			y - pattern.Sides[0],
			innerBounds.Dx(), innerBounds.Dy())
	} else {
		return pattern.Stroke.AtWhen(x, y, width, height)
	}
}