package drawing

import (
	"image"
	"image/color"

	"github.com/golang/freetype/truetype"
)

// StackGraphicContext is a context that does thngs.
type StackGraphicContext struct {
	current *ContextStack
}

// ContextStack is a graphic context implementation.
type ContextStack struct {
	Tr          Matrix
	Path        *Path
	LineWidth   float64
	Dash        []float64
	DashOffset  float64
	StrokeColor color.Color
	FillColor   color.Color
	FillRule    FillRule
	Cap         LineCap
	Join        LineJoin

	FontSizePoints float64
	Font           *truetype.Font

	Scale float64

	Previous *ContextStack
}

// NewStackGraphicContext Create a new Graphic context from an image
func NewStackGraphicContext() *StackGraphicContext {
	gc := &StackGraphicContext{}
	gc.current = new(ContextStack)
	gc.current.Tr = NewIdentityMatrix()
	gc.current.Path = new(Path)
	gc.current.LineWidth = 1.0
	gc.current.StrokeColor = image.Black
	gc.current.FillColor = image.White
	gc.current.Cap = RoundCap
	gc.current.FillRule = FillRuleEvenOdd
	gc.current.Join = RoundJoin
	gc.current.FontSizePoints = 10
	return gc
}

// GetMatrixTransform returns the matrix transform.
func (gc *StackGraphicContext) GetMatrixTransform() Matrix {
	return gc.current.Tr
}

// SetMatrixTransform sets the matrix transform.
func (gc *StackGraphicContext) SetMatrixTransform(tr Matrix) {
	gc.current.Tr = tr
}

// ComposeMatrixTransform composes a transform into the current transform.
func (gc *StackGraphicContext) ComposeMatrixTransform(tr Matrix) {
	gc.current.Tr.Compose(tr)
}

// Rotate rotates the matrix transform by an angle in degrees.
func (gc *StackGraphicContext) Rotate(angle float64) {
	gc.current.Tr.Rotate(angle)
}

// Translate translates a transform.
func (gc *StackGraphicContext) Translate(tx, ty float64) {
	gc.current.Tr.Translate(tx, ty)
}

// Scale scales a transform.
func (gc *StackGraphicContext) Scale(sx, sy float64) {
	gc.current.Tr.Scale(sx, sy)
}

// SetStrokeColor sets the stroke color.
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
	gc.current.StrokeColor = c
}

// SetFillColor sets the fill color.
func (gc *StackGraphicContext) SetFillColor(c color.Color) {
	gc.current.FillColor = c
}

// SetFillRule sets the fill rule.
func (gc *StackGraphicContext) SetFillRule(f FillRule) {
	gc.current.FillRule = f
}

// SetLineWidth sets the line width.
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
	gc.current.LineWidth = lineWidth
}

// SetLineCap sets the line cap.
func (gc *StackGraphicContext) SetLineCap(cap LineCap) {
	gc.current.Cap = cap
}

// SetLineJoin sets the line join.
func (gc *StackGraphicContext) SetLineJoin(join LineJoin) {
	gc.current.Join = join
}

// SetLineDash sets the line dash.
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
	gc.current.Dash = dash
	gc.current.DashOffset = dashOffset
}

// SetFontSize sets the font size.
func (gc *StackGraphicContext) SetFontSize(fontSizePoints float64) {
	gc.current.FontSizePoints = fontSizePoints
}

// GetFontSize gets the font size.
func (gc *StackGraphicContext) GetFontSize() float64 {
	return gc.current.FontSizePoints
}

// SetFont sets the current font.
func (gc *StackGraphicContext) SetFont(f *truetype.Font) {
	gc.current.Font = f
}

// GetFont returns the font.
func (gc *StackGraphicContext) GetFont() *truetype.Font {
	return gc.current.Font
}

// BeginPath starts a new path.
func (gc *StackGraphicContext) BeginPath() {
	gc.current.Path.Clear()
}

// IsEmpty returns if the path is empty.
func (gc *StackGraphicContext) IsEmpty() bool {
	return gc.current.Path.IsEmpty()
}

// LastPoint returns the last point on the path.
func (gc *StackGraphicContext) LastPoint() (x float64, y float64) {
	return gc.current.Path.LastPoint()
}

// MoveTo moves the cursor for a path.
func (gc *StackGraphicContext) MoveTo(x, y float64) {
	gc.current.Path.MoveTo(x, y)
}

// LineTo draws a line.
func (gc *StackGraphicContext) LineTo(x, y float64) {
	gc.current.Path.LineTo(x, y)
}

// QuadCurveTo draws a quad curve.
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
	gc.current.Path.QuadCurveTo(cx, cy, x, y)
}

// CubicCurveTo draws a cubic curve.
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
	gc.current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
}

// ArcTo draws an arc.
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, delta float64) {
	gc.current.Path.ArcTo(cx, cy, rx, ry, startAngle, delta)
}

// Close closes a path.
func (gc *StackGraphicContext) Close() {
	gc.current.Path.Close()
}

// Save pushes a context onto the stack.
func (gc *StackGraphicContext) Save() {
	context := new(ContextStack)
	context.FontSizePoints = gc.current.FontSizePoints
	context.Font = gc.current.Font
	context.LineWidth = gc.current.LineWidth
	context.StrokeColor = gc.current.StrokeColor
	context.FillColor = gc.current.FillColor
	context.FillRule = gc.current.FillRule
	context.Dash = gc.current.Dash
	context.DashOffset = gc.current.DashOffset
	context.Cap = gc.current.Cap
	context.Join = gc.current.Join
	context.Path = gc.current.Path.Copy()
	context.Font = gc.current.Font
	context.Scale = gc.current.Scale
	copy(context.Tr[:], gc.current.Tr[:])
	context.Previous = gc.current
	gc.current = context
}

// Restore restores the previous context.
func (gc *StackGraphicContext) Restore() {
	if gc.current.Previous != nil {
		oldContext := gc.current
		gc.current = gc.current.Previous
		oldContext.Previous = nil
	}
}