// SPDX-License-Identifier: MIT
// Derived from Rough.js 4.6.6 and hachure-fill 0.5.2.
// Copyright (c) 2019, 2023 Preet Shihn.

package rough

import (
	
	
)

type hachureEdgeEntry struct {
	ymin   float64
	ymax   float64
	x      float64
	islope float64
}

type activeHachureEdgeEntry struct {
	s    float64
	edge *hachureEdgeEntry
}

const collapsedEndpointMaxULPs = 8

func polygonHachureLines( []Point,  *ResolvedOptions) []Line {
	 := Point{0, 0}
	 := .HachureAngle + 90
	 := .HachureGap
	if  < 0 {
		 = .StrokeWidth * 4
	}
	 = jsRound(math.Max(, 0.1))
	 := 1.0
	if .Roughness >= 1 && random() > 0.7 {
		 = 
	}
	 := 
	var  []Point
	if  != 0 {
		 = append([]Point(nil), ...)
		rotatePoints(, , )
		// Keep the source-observable point rotation/round trip on the caller's
		// slice, but stabilize scan-line event ordering on a private copy. V8
		// and Go trig can collapse two near-coincident rotated ordinates onto
		// the same float on only one runtime, which otherwise changes edge
		// activation and the entire filler topology.
		 = append([]Point(nil), ...)
		stabilizeCollapsedRotatedOrdinates(, , )
	}
	 := straightHachureLinesModern(, , )
	if  != 0 {
		rotatePoints(, , -)
		rotateLines(, , -)
	}
	return 
}

func straightHachureLinesModern( []Point, ,  float64) []Line {
	 := append([]Point(nil), ...)
	if len() == 0 {
		return nil
	}
	if !pointsJoinEqual([0], [len()-1]) {
		 = append(, [0])
	}
	 := make([]Line, 0)
	if len() <= 2 {
		return 
	}

	 := make([]*hachureEdgeEntry, 0, len()-1)
	for  := 0;  < len()-1; ++ {
		,  := [], [+1]
		if [1] == [1] {
			continue
		}
		 := math.Min([1], [1])
		 = append(, &hachureEdgeEntry{
			ymin: , ymax: math.Max([1], [1]),
			x:      chooseFloat( == [1], [0], [0]),
			islope: ([0] - [0]) / ([1] - [1]),
		})
	}
	sort.SliceStable(, func(,  int) bool {
		,  := [], []
		if .ymin != .ymin {
			return .ymin < .ymin
		}
		if .x != .x {
			return .x < .x
		}
		return .ymax < .ymax
	})
	if len() == 0 {
		return 
	}

	 := make([]activeHachureEdgeEntry, 0)
	 := [0].ymin
	 := 0.0
	for len() != 0 || len() != 0 {
		 := -1
		for  := range  {
			if [].ymin >  {
				break
			}
			 = 
		}
		if  >= 0 {
			for ,  := range [:+1] {
				 = append(, activeHachureEdgeEntry{s: , edge: })
			}
			 = [+1:]
		}
		 := [:0]
		for ,  := range  {
			if .edge.ymax >  {
				 = append(, )
			}
		}
		 = 
		sort.SliceStable(, func(,  int) bool { return [].edge.x < [].edge.x })
		if  != 1 || math.Mod(, ) == 0 {
			for  := 0; +1 < len();  += 2 {
				 = append(, Line{{jsRound([].edge.x), }, {jsRound([+1].edge.x), }})
			}
		}
		 += 
		for  := range  {
			[].edge.x +=  * [].edge.islope
		}
		++
	}
	return 
}

func stabilizeCollapsedRotatedOrdinates(,  []Point,  float64) {
	if len() != len() || len() < 2 {
		return
	}
	 := (math.Pi / 180) * 
	,  := math.Sin(), math.Cos()
	for  := range  {
		 :=  + 1
		if  == len() {
			 = 0
		}
		if [][1] != [][1] ||
			pointsJoinEqual([], []) ||
			!pointsWithinULPs([], [], collapsedEndpointMaxULPs) {
			continue
		}
		// Compute the transformed difference directly instead of subtracting
		// the already-rounded absolute coordinates. This retains the sign that
		// was lost to cancellation in the ordinary rotation.
		 := ([][0]-[][0])* +
			([][1]-[][1])*
		switch {
		case  > 0:
			[][1] = math.Nextafter([][1], math.Inf(1))
		case  < 0:
			[][1] = math.Nextafter([][1], math.Inf(-1))
		}
	}
}

func pointsWithinULPs(,  Point,  float64) bool {
	 := 1.0
	for  := range  {
		for ,  := range [...]float64{[], []} {
			if math.IsNaN() || math.IsInf(, 0) {
				return false
			}
			 = math.Max(, math.Abs())
		}
	}
	 :=  * (math.Nextafter(, math.Inf(1)) - )
	for  := range  {
		if math.Abs([]-[]) >  {
			return false
		}
	}
	return true
}

func straightHachureLines( []Point,  *ResolvedOptions) []Line {
	 := append([]Point(nil), ...)
	if len() == 0 {
		// Rough.js would fail while indexing an empty polygon. Returning the
		// empty source-shaped result keeps the unexported helper total without
		// changing any generator-visible nonempty behavior.
		return nil
	}
	if !pointsJoinEqual([0], [len()-1]) {
		 = append(, Point{[0][0], [0][1]})
	}
	 := make([]Line, 0)
	if len() <= 2 {
		return 
	}

	 := .HachureGap
	if  < 0 {
		 = .StrokeWidth * 4
	}
	 = math.Max(, 0.1)

	 := make([]*hachureEdgeEntry, 0, len()-1)
	for  := 0;  < len()-1; ++ {
		 := []
		 := [+1]
		if [1] != [1] {
			 := math.Min([1], [1])
			 = append(, &hachureEdgeEntry{
				ymin:   ,
				ymax:   math.Max([1], [1]),
				x:      chooseFloat( == [1], [0], [0]),
				islope: ([0] - [0]) / ([1] - [1]),
			})
		}
	}
	// ECMAScript's Array.prototype.sort is stable. Stable ties are observable
	// for coincident polygon edges, so do not use sort.Slice here.
	sort.SliceStable(, func(,  int) bool {
		,  := [], []
		if .ymin != .ymin {
			return .ymin < .ymin
		}
		if .x != .x {
			return .x < .x
		}
		return .ymax < .ymax
	})
	if len() == 0 {
		return 
	}

	 := make([]activeHachureEdgeEntry, 0)
	 := [0].ymin
	for len() > 0 || len() > 0 {
		if len() > 0 {
			 := -1
			for  := range  {
				if [].ymin >  {
					break
				}
				 = 
			}
			if  >= 0 {
				 := [:+1]
				 = [+1:]
				for ,  := range  {
					 = append(, activeHachureEdgeEntry{s: , edge: })
				}
			}
		}

		 := [:0]
		for ,  := range  {
			if .edge.ymax >  {
				 = append(, )
			}
		}
		 = 
		sort.SliceStable(, func(,  int) bool {
			return [].edge.x < [].edge.x
		})

		if len() > 1 {
			for  := 0;  < len();  += 2 {
				 :=  + 1
				if  >= len() {
					break
				}
				 := [].edge
				 := [].edge
				 = append(, Line{{jsRound(.x), }, {jsRound(.x), }})
			}
		}

		 += 
		for  := range  {
			 := &[]
			.edge.x +=  * .edge.islope
		}
	}
	return 
}

func chooseFloat( bool, ,  float64) float64 {
	if  {
		return 
	}
	return 
}

func pointsJoinEqual(,  Point) bool {
	for  := 0;  < 2; ++ {
		if [] == [] {
			continue
		}
		// JavaScript's Array#join renders every NaN as "NaN".
		if math.IsNaN([]) && math.IsNaN([]) {
			continue
		}
		return false
	}
	return true
}