// SPDX-License-Identifier: Unlicense OR MIT

package clip

import (
	"encoding/binary"
	"math"

	"gioui.org/internal/opconst"
	"gioui.org/op"
)

// Stroke represents a stroked path.
type Stroke struct {
	Path  PathSpec
	Style StrokeStyle

	// Dashes specify the dashes of the stroke.
	// The empty value denotes no dashes.
	Dashes DashSpec
}

// Op returns a clip operation representing the stroke.
func (s Stroke) Op() Op {
	return Op{
		path:   s.Path,
		stroke: s.Style,
		dashes: s.Dashes,
	}
}

// StrokeStyle describes how a path should be stroked.
type StrokeStyle struct {
	Width float32 // Width of the stroked path.

	// Miter is the limit to apply to a miter joint.
	// The zero Miter disables the miter joint; setting Miter to +∞
	// unconditionally enables the miter joint.
	Miter float32
	Cap   StrokeCap  // Cap describes the head or tail of a stroked path.
	Join  StrokeJoin // Join describes how stroked paths are collated.
}

// StrokeCap describes the head or tail of a stroked path.
type StrokeCap uint8

const (
	// FlatCap caps stroked paths with a flat cap, joining the right-hand
	// and left-hand sides of a stroked path with a straight line.
	FlatCap StrokeCap = iota

	// SquareCap caps stroked paths with a square cap, joining the right-hand
	// and left-hand sides of a stroked path with a half square of length
	// the stroked path's width.
	SquareCap

	// RoundCap caps stroked paths with a round cap, joining the right-hand and
	// left-hand sides of a stroked path with a half disc of diameter the
	// stroked path's width.
	RoundCap
)

// StrokeJoin describes how stroked paths are collated.
type StrokeJoin uint8

const (
	// BevelJoin joins path segments with sharp bevels.
	BevelJoin StrokeJoin = iota

	// RoundJoin joins path segments with a round segment.
	RoundJoin
)

// Dash records dashes' lengths and phase for a stroked path.
type Dash struct {
	ops   *op.Ops
	macro op.MacroOp
	phase float32
	size  uint8 // size of the pattern
}

func (d *Dash) Begin(ops *op.Ops) {
	d.ops = ops
	d.macro = op.Record(ops)
	// Write the TypeAux opcode
	data := ops.Write(opconst.TypeAuxLen)
	data[0] = byte(opconst.TypeAux)
}

func (d *Dash) Phase(v float32) {
	d.phase = v
}

func (d *Dash) Dash(length float32) {
	if d.size == math.MaxUint8 {
		panic("clip: too large dash pattern")
	}
	data := d.ops.Write(4)
	bo := binary.LittleEndian
	bo.PutUint32(data[0:], math.Float32bits(length))
	d.size++
}

func (d *Dash) End() DashSpec {
	c := d.macro.Stop()
	return DashSpec{
		spec:  c,
		phase: d.phase,
		size:  d.size,
	}
}

// DashSpec describes a dashed pattern.
type DashSpec struct {
	spec  op.CallOp
	phase float32
	size  uint8 // size of the pattern
}
