package d2scene

import (
	
	
)

type FillRule uint8

const (
	NonZero FillRule = iota
	EvenOdd
)

type PathCommandKind uint8

const (
	MoveCommand PathCommandKind = iota
	LineCommand
	QuadraticCommand
	CubicCommand
	ArcCommand
	CloseCommand
)

// PathCommand stores absolute, typed path geometry. P1 is the endpoint for a
// move or line; P1/P2 are control/end for a quadratic; P1/P2/P3 are the two
// controls/end for a cubic; and P1 is the endpoint for an arc. Arc rotation is
// in radians.
type PathCommand struct {
	Kind PathCommandKind
	P1   Point
	P2   Point
	P3   Point

	RadiusX  float64
	RadiusY  float64
	Rotation float64
	LargeArc bool
	Sweep    bool
}

func (,  float64) PathCommand {
	return PathCommand{Kind: MoveCommand, P1: Point{X: , Y: }}
}

func (,  float64) PathCommand {
	return PathCommand{Kind: LineCommand, P1: Point{X: , Y: }}
}

func (, , ,  float64) PathCommand {
	return PathCommand{
		Kind: QuadraticCommand,
		P1:   Point{X: , Y: },
		P2:   Point{X: , Y: },
	}
}

func (, , , , ,  float64) PathCommand {
	return PathCommand{
		Kind: CubicCommand,
		P1:   Point{X: , Y: },
		P2:   Point{X: , Y: },
		P3:   Point{X: , Y: },
	}
}

func (, ,  float64, ,  bool, ,  float64) PathCommand {
	return PathCommand{
		Kind:     ArcCommand,
		P1:       Point{X: , Y: },
		RadiusX:  ,
		RadiusY:  ,
		Rotation: ,
		LargeArc: ,
		Sweep:    ,
	}
}

func () PathCommand {
	return PathCommand{Kind: CloseCommand}
}

// Path is both typed path geometry and a paintable scene primitive.
type Path struct {
	Commands []PathCommand
	FillRule FillRule
	Fill     Paint
	Stroke   *Stroke
}

func (Path) () {}

// GeometryBounds computes exact analytic bounds of the centerline geometry.
func ( Path) () (Bounds, error) {
	return .transformedGeometryBounds(Identity())
}

func ( Path) ( Matrix) (Bounds, error) {
	if !.IsFinite() {
		return Bounds{}, fmt.Errorf("d2scene: non-finite path transform")
	}

	var  Bounds
	var  Point
	var  Point
	 := false
	for ,  := range .Commands {
		if  := .validate();  != nil {
			return Bounds{}, fmt.Errorf("d2scene: path command %d: %w", , )
		}
		switch .Kind {
		case MoveCommand:
			 = .P1
			 = 
			 = true
		case LineCommand:
			if ! {
				return Bounds{}, fmt.Errorf("d2scene: path command %d: line before move", )
			}
			 = .Union(BoundsFromPoints(.Point(), .Point(.P1)))
			 = .P1
		case QuadraticCommand:
			if ! {
				return Bounds{}, fmt.Errorf("d2scene: path command %d: quadratic before move", )
			}
			 = .Union(quadraticBounds(.Point(), .Point(.P1), .Point(.P2)))
			 = .P2
		case CubicCommand:
			if ! {
				return Bounds{}, fmt.Errorf("d2scene: path command %d: cubic before move", )
			}
			 = .Union(cubicBounds(.Point(), .Point(.P1), .Point(.P2), .Point(.P3)))
			 = .P3
		case ArcCommand:
			if ! {
				return Bounds{}, fmt.Errorf("d2scene: path command %d: arc before move", )
			}
			,  := arcBounds(, , )
			if  != nil {
				return Bounds{}, fmt.Errorf("d2scene: path command %d: %w", , )
			}
			 = .Union()
			 = .P1
		case CloseCommand:
			if ! {
				return Bounds{}, fmt.Errorf("d2scene: path command %d: close before move", )
			}
			 = .Union(BoundsFromPoints(.Point(), .Point()))
			 = 
		default:
			return Bounds{}, fmt.Errorf("d2scene: path command %d: unknown kind %d", , .Kind)
		}
	}
	if !.IsFinite() {
		return Bounds{}, fmt.Errorf("d2scene: non-finite path bounds")
	}
	return , nil
}

func ( PathCommand) () error {
	 := func( Point) error {
		if !finitePoint() {
			return fmt.Errorf("non-finite point")
		}
		return nil
	}
	switch .Kind {
	case MoveCommand, LineCommand:
		return (.P1)
	case QuadraticCommand:
		if  := (.P1);  != nil {
			return 
		}
		return (.P2)
	case CubicCommand:
		if  := (.P1);  != nil {
			return 
		}
		if  := (.P2);  != nil {
			return 
		}
		return (.P3)
	case ArcCommand:
		if  := (.P1);  != nil {
			return 
		}
		if !finite(.RadiusX) || !finite(.RadiusY) || !finite(.Rotation) {
			return fmt.Errorf("non-finite arc")
		}
		return nil
	case CloseCommand:
		return nil
	default:
		return fmt.Errorf("unknown kind %d", .Kind)
	}
}

func quadraticBounds(, ,  Point) Bounds {
	 := BoundsFromPoints(, )
	if ,  := quadraticExtremum(.X, .X, .X);  {
		 = .Include(quadraticPoint(, , , ))
	}
	if ,  := quadraticExtremum(.Y, .Y, .Y);  {
		 = .Include(quadraticPoint(, , , ))
	}
	return 
}

func quadraticExtremum(, ,  float64) (float64, bool) {
	 :=  - 2* + 
	if  == 0 {
		return 0, false
	}
	 := ( - ) / 
	return ,  > 0 &&  < 1
}

func quadraticPoint(, ,  Point,  float64) Point {
	 := 1 - 
	return Point{
		X: **.X + 2***.X + **.X,
		Y: **.Y + 2***.Y + **.Y,
	}
}

func cubicBounds(, , ,  Point) Bounds {
	 := BoundsFromPoints(, )
	for ,  := range cubicExtrema(.X, .X, .X, .X) {
		 = .Include(cubicPoint(, , , , ))
	}
	for ,  := range cubicExtrema(.Y, .Y, .Y, .Y) {
		 = .Include(cubicPoint(, , , , ))
	}
	return 
}

func cubicExtrema(, , ,  float64) []float64 {
	// Derivative divided by three: a*t^2 + b*t + c.
	 := - + 3* - 3* + 
	 := 2 * ( - 2* + )
	 :=  - 
	return rootsInUnitInterval(, , )
}

func rootsInUnitInterval(, ,  float64) []float64 {
	if  == 0 {
		if  == 0 {
			return nil
		}
		 := - / 
		if  > 0 &&  < 1 {
			return []float64{}
		}
		return nil
	}
	 := * - 4**
	if  < 0 {
		return nil
	}
	 := math.Sqrt()
	 := (- - ) / (2 * )
	 := (- + ) / (2 * )
	 := make([]float64, 0, 2)
	if  > 0 &&  < 1 {
		 = append(, )
	}
	if  > 0 &&  < 1 &&  !=  {
		 = append(, )
	}
	return 
}

func cubicPoint(, , ,  Point,  float64) Point {
	 := 1 - 
	,  := *, *
	return Point{
		X: **.X + 3***.X + 3***.X + **.X,
		Y: **.Y + 3***.Y + 3***.Y + **.Y,
	}
}

// CenterArc is SVG endpoint arc geometry converted to a center
// parameterization. Its fields remain private so callers cannot construct an
// inconsistent arc; renderers use the accessors below.
type CenterArc struct {
	center Point
	u      Point
	v      Point
	start  float64
	delta  float64
}

func ( CenterArc) () float64 { return .start }
func ( CenterArc) () float64 { return .delta }

func ( CenterArc) ( float64) Point {
	,  := math.Sincos()
	return Point{X: .center.X + .u.X* + .v.X*, Y: .center.Y + .u.Y* + .v.Y*}
}

// TransformedRadiusBound returns a conservative second-derivative bound after
// applying the linear part of m. It is used to prove a device-space chord
// error during adaptive flattening.
func ( CenterArc) ( Matrix) float64 {
	 := .Vector(.u)
	 := .Vector(.v)
	return math.Hypot(math.Hypot(.X, .Y), math.Hypot(.X, .Y))
}

func arcBounds( Point,  PathCommand,  Matrix) (Bounds, error) {
	 := .P1
	 := .Point()
	 := .Point()
	if  ==  {
		return BoundsFromPoints(), nil
	}
	, ,  := EndpointArcToCenter(, )
	if  != nil {
		return Bounds{}, 
	}
	if ! {
		return BoundsFromPoints(, ), nil
	}

	 := .Point(.center)
	 := .Vector(.u)
	 := .Vector(.v)
	 := func( float64) Point {
		,  := math.Sincos()
		return Point{X: .X + .X* + .X*, Y: .Y + .Y* + .Y*}
	}
	 := BoundsFromPoints(, , (.start), (.start+.delta))
	 := [4]float64{
		math.Atan2(.X, .X),
		math.Atan2(.X, .X) + math.Pi,
		math.Atan2(.Y, .Y),
		math.Atan2(.Y, .Y) + math.Pi,
	}
	for ,  := range  {
		if angleOnArc(, .start, .delta) {
			 = .Include(())
		}
	}
	if !.IsFinite() {
		return Bounds{}, fmt.Errorf("arc bounds are outside the numeric domain")
	}
	return , nil
}

// EndpointArcToCenter implements SVG 2's endpoint-to-center conversion. The
// boolean is false for the two specified degeneracies: identical endpoints
// (omit the arc) and a zero radius (draw a line). Ratio exponents are
// normalized before division, so finite tiny radii can be corrected without
// overflowing to infinity first.
func ( Point,  PathCommand) (CenterArc, bool, error) {
	if .Kind != ArcCommand {
		return CenterArc{}, false, fmt.Errorf("endpoint arc conversion requires an arc command")
	}
	if !finitePoint() {
		return CenterArc{}, false, fmt.Errorf("non-finite arc start")
	}
	if  := .validate();  != nil {
		return CenterArc{}, false, 
	}
	 := .P1
	if  ==  {
		return CenterArc{}, false, nil
	}
	 := math.Abs(.RadiusX)
	 := math.Abs(.RadiusY)
	if  == 0 ||  == 0 {
		return CenterArc{}, false, nil
	}
	,  := math.Sincos(.Rotation)
	// Halve before subtracting so opposite large finite endpoints do not
	// overflow merely while forming their midpoint-relative vector.
	 := .X/2 - .X/2
	 := .Y/2 - .Y/2
	 := * + *
	 := -* + *
	if !finite() || !finite() {
		return CenterArc{}, false, fmt.Errorf("arc is outside the numeric domain")
	}

	, , , ,  := normalizedRadiusRatios(, , , )
	if  != nil {
		return CenterArc{}, false, 
	}
	 :=  > 0 ||  == 0 &&  >= 1
	 := 1.0
	if  {
		 = multiplyBinaryScale(, , )
		 = multiplyBinaryScale(, , )
		if !finite() || !finite() {
			return CenterArc{}, false, fmt.Errorf("arc corrected radii are outside the numeric domain")
		}
	} else {
		 = math.Ldexp(, )
	}
	 := math.Sqrt(math.Max(0, 1-*))
	if .LargeArc == .Sweep {
		 = -
	}
	 := ( * ) * 
	 := (- * ) * 

	 := Point{
		X: * - * + .X/2 + .X/2,
		Y: * + * + .Y/2 + .Y/2,
	}
	 := Point{X:  * , Y:  * }
	 := Point{X: - * , Y:  * }
	if !finitePoint() || !finitePoint() || !finitePoint() {
		return CenterArc{}, false, fmt.Errorf("arc is outside the numeric domain")
	}

	 := 
	 := 
	if ! {
		 *= 
		 *= 
	}
	 :=  / 
	 :=  / 
	 :=  - 
	 :=  - 
	 := - - 
	 := - - 
	 := math.Atan2(, )
	 := math.Atan2(*-*, *+*)
	if .Sweep &&  < 0 {
		 += 2 * math.Pi
	} else if !.Sweep &&  > 0 {
		 -= 2 * math.Pi
	}
	if !finite() || !finite() {
		return CenterArc{}, false, fmt.Errorf("arc angles are outside the numeric domain")
	}
	return CenterArc{
		center: ,
		u:      Point{X:  * , Y:  * },
		v:      Point{X: - * , Y:  * },
		start:  ,
		delta:  ,
	}, true, nil
}

// normalizedRadiusRatios returns the unit vector of
// (xPrime/rx,yPrime/ry), plus a binary representation of its length as
// mantissa*2^exponent. No component ratio is formed directly.
func normalizedRadiusRatios(, , ,  float64) (, ,  float64,  int,  error) {
	, ,  := ratioMantissaExponent(, )
	, ,  := ratioMantissaExponent(, )
	if ! && ! {
		return 0, 0, 0, 0, fmt.Errorf("arc endpoint ratio is zero")
	}
	 := 
	if ! ||  &&  >  {
		 = 
	}
	,  := 0.0, 0.0
	if  {
		 = math.Ldexp(, -)
	}
	if  {
		 = math.Ldexp(, -)
	}
	 := math.Hypot(, )
	if  == 0 || !finite() {
		return 0, 0, 0, 0, fmt.Errorf("arc endpoint ratio is outside the numeric domain")
	}
	,  := math.Frexp()
	return  / ,  / , ,  + , nil
}

func ratioMantissaExponent(,  float64) (float64, int, bool) {
	if  == 0 {
		return 0, 0, false
	}
	,  := math.Frexp()
	,  := math.Frexp()
	,  := math.Frexp( / )
	return ,  -  + , true
}

func multiplyBinaryScale(,  float64,  int) float64 {
	,  := math.Frexp()
	return math.Ldexp(*, +)
}

func angleOnArc(, ,  float64) bool {
	const  = 1e-12
	if  >= 0 {
		return positiveAngle(-) <= +
	}
	return positiveAngle(-) <= -+
}

func positiveAngle( float64) float64 {
	 = math.Mod(, 2*math.Pi)
	if  < 0 {
		 += 2 * math.Pi
	}
	return 
}