// Copyright ©2016 The Gonum 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 plotter

import (
	"image/color"
	"math"

	"gonum.org/v1/plot"
	"gonum.org/v1/plot/vg"
	"gonum.org/v1/plot/vg/draw"
)

// Polygon implements the Plotter interface, drawing a polygon.
type Polygon struct {
	// XYs is a copy of the vertices of this polygon.
	// Each item in the array holds one ring in the
	// Polygon.
	XYs []XYs

	// LineStyle is the style of the line around the edge
	// of the polygon.
	draw.LineStyle

	// Color is the fill color of the polygon.
	Color color.Color
}

// NewPolygon returns a polygon that uses the default line style and
// no fill color, where xys are the rings of the polygon.
// Different backends may render overlapping rings and self-intersections
// differently, but all built-in backends treat inner rings
// with the opposite winding order from the outer ring as
// holes.
func NewPolygon(xys ...XYer) (*Polygon, error) {
	data := make([]XYs, len(xys))
	for i, d := range xys {
		var err error
		data[i], err = CopyXYs(d)
		if err != nil {
			return nil, err
		}
	}
	return &Polygon{
		XYs:       data,
		LineStyle: DefaultLineStyle,
	}, nil
}

// Plot draws the polygon, implementing the plot.Plotter
// interface.
func (pts *Polygon) Plot(c draw.Canvas, plt *plot.Plot) {
	trX, trY := plt.Transforms(&c)
	ps := make([][]vg.Point, len(pts.XYs))

	for i, ring := range pts.XYs {
		ps[i] = make([]vg.Point, len(ring))
		for j, p := range ring {
			ps[i][j].X = trX(p.X)
			ps[i][j].Y = trY(p.Y)
		}
		ps[i] = c.ClipPolygonXY(ps[i])
	}
	if pts.Color != nil && len(ps) > 0 {
		c.SetColor(pts.Color)
		var pa vg.Path
		for _, ring := range ps {
			if len(ring) == 0 {
				continue
			}
			pa.Move(ring[0])
			for _, p := range ring {
				pa.Line(p)
			}
			pa.Close()
		}
		c.Fill(pa)
	}

	for _, ring := range ps {
		if len(ring) > 0 && ring[len(ring)-1] != ring[0] {
			ring = append(ring, ring[0])
		}
		c.StrokeLines(pts.LineStyle, c.ClipLinesXY(ring)...)
	}
}

// DataRange returns the minimum and maximum
// x and y values, implementing the plot.DataRanger
// interface.
func (pts *Polygon) DataRange() (xmin, xmax, ymin, ymax float64) {
	xmin = math.Inf(1)
	xmax = math.Inf(-1)
	ymin = math.Inf(1)
	ymax = math.Inf(-1)
	for _, ring := range pts.XYs {
		xmini, xmaxi := Range(XValues{ring})
		ymini, ymaxi := Range(YValues{ring})
		xmin = math.Min(xmin, xmini)
		xmax = math.Max(xmax, xmaxi)
		ymin = math.Min(ymin, ymini)
		ymax = math.Max(ymax, ymaxi)
	}
	return
}

// Thumbnail creates the thumbnail for the Polygon,
// implementing the plot.Thumbnailer interface.
func (pts *Polygon) Thumbnail(c *draw.Canvas) {
	if pts.Color != nil {
		points := []vg.Point{
			{X: c.Min.X, Y: c.Min.Y},
			{X: c.Min.X, Y: c.Max.Y},
			{X: c.Max.X, Y: c.Max.Y},
			{X: c.Max.X, Y: c.Min.Y},
		}
		poly := c.ClipPolygonY(points)
		c.FillPolygon(pts.Color, poly)

		points = append(points, vg.Point{X: c.Min.X, Y: c.Min.Y})
		c.StrokeLines(pts.LineStyle, points)
	} else {
		y := c.Center().Y
		c.StrokeLine2(pts.LineStyle, c.Min.X, y, c.Max.X, y)
	}
}
