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

package rough

import (
	
	
)

const noStyle = "none"

// Generator creates Rough.js-compatible drawing operations.
//
// A Generator retains the same mutable default-option random state as
// Rough.js. Its methods are safe for concurrent use, though callers that need
// independent deterministic streams should use separate generators.
type Generator struct {
	mu             sync.Mutex
	defaultOptions ResolvedOptions
	surface        *Surface
}

// NewGenerator creates a generator. A nil config uses Rough.js 4.6.6's
// defaults. The optional surface supplies patterned-path fallback dimensions.
func ( *Config,  ...*Surface) *Generator {
	 := &Generator{defaultOptions: defaultResolvedOptions()}
	if  != nil && .Options != nil {
		.defaultOptions = mergeOptions(.defaultOptions, .Options)
	}
	if len() != 0 {
		.surface = [0]
	}
	return 
}

// DefaultOptions returns a value copy of the generator's current defaults.
func ( *Generator) () ResolvedOptions {
	.mu.Lock()
	defer .mu.Unlock()
	return cloneResolvedOptions(.defaultOptions)
}

func ( *Generator) ( *Options) *ResolvedOptions {
	if  == nil {
		return &.defaultOptions
	}
	 := mergeOptions(.defaultOptions, )
	return &
}

func drawable( string,  []OpSet,  *ResolvedOptions) Drawable {
	if  == nil {
		 = []OpSet{}
	}
	var  *ResolvedOptions
	if  != nil {
		 := cloneResolvedOptions(*)
		 = &
	}
	return Drawable{Shape: , Sets: , Options: }
}

func cloneResolvedOptions( ResolvedOptions) ResolvedOptions {
	 := 
	.StrokeLineDash = append([]float64(nil), .StrokeLineDash...)
	.FillLineDash = append([]float64(nil), .FillLineDash...)
	if .FixedDecimalPlaceDigits != nil {
		 := *.FixedDecimalPlaceDigits
		.FixedDecimalPlaceDigits = &
	}
	if .randomizer != nil {
		 := *.randomizer
		.randomizer = &
	}
	return 
}

// Line creates a rough line.
func ( *Generator) (, , ,  float64,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	return drawable("line", []OpSet{line(, , , , )}, )
}

// Rectangle creates a rough rectangle.
func ( *Generator) (, , ,  float64,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	 := []OpSet{}
	 := rectangle(, , , , )
	if .Fill != "" {
		 := []Point{{, }, { + , }, { + ,  + }, {,  + }}
		if .FillStyle == "solid" {
			 = append(, solidFillPolygons([][]Point{}, ))
		} else {
			 = append(, patternFillPolygon(, ))
		}
	}
	if .Stroke != noStyle {
		 = append(, )
	}
	return drawable("rectangle", , )
}

// Ellipse creates a rough ellipse centered at x,y.
func ( *Generator) (, , ,  float64,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	 := []OpSet{}
	 := generateEllipseParams(, , )
	 := ellipseWithParams(, , , )
	if .Fill != "" {
		if .FillStyle == "solid" {
			 := ellipseWithParams(, , , ).opset
			.Type = OpSetFillPath
			 = append(, )
		} else {
			 = append(, patternFillPolygon(.estimatedPoints, ))
		}
	}
	if .Stroke != noStyle {
		 = append(, .opset)
	}
	return drawable("ellipse", , )
}

// Circle creates a rough circle centered at x,y.
func ( *Generator) (, ,  float64,  *Options) Drawable {
	 := .Ellipse(, , , , )
	.Shape = "circle"
	return 
}

// LinearPath creates an open rough path through points.
func ( *Generator) ( []Point,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	return drawable("linearPath", []OpSet{linearPath(, false, )}, )
}

// Arc creates a rough elliptical arc.
func ( *Generator) (, , , , ,  float64,  bool,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	 := []OpSet{}
	 := arc(, , , , , , , true, )
	if  && .Fill != "" {
		if .FillStyle == "solid" {
			 := cloneResolvedOptions(*)
			.DisableMultiStroke = true
			 := arc(, , , , , , true, false, &)
			.Type = OpSetFillPath
			 = append(, )
		} else {
			 = append(, patternFillArc(, , , , , , ))
		}
	}
	if .Stroke != noStyle {
		 = append(, )
	}
	return drawable("arc", , )
}

// Curve creates a rough curve through points.
func ( *Generator) ( []Point,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	return drawable("curve", []OpSet{curve(, )}, )
}

// Polygon creates a closed rough polygon.
func ( *Generator) ( []Point,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	 := []OpSet{}
	 := linearPath(, true, )
	if .Fill != "" {
		if .FillStyle == "solid" {
			 = append(, solidFillPolygons([][]Point{}, ))
		} else {
			 = append(, patternFillPolygon(, ))
		}
	}
	if .Stroke != noStyle {
		 = append(, )
	}
	return drawable("polygon", , )
}

// Path creates a rough rendering of SVG path data.
func ( *Generator) ( string,  *Options) Drawable {
	.mu.Lock()
	defer .mu.Unlock()
	 := .options()
	 := []OpSet{}
	if  == "" {
		return drawable("path", , )
	}
	 = normalizePathData()
	 := svgPath(, )
	 := .Fill != "" && .Fill != "transparent" && .Fill != noStyle
	if  {
		if .FillStyle == "solid" {
			 := newRoughPath().linearPoints()
			if len() == 1 {
				 := cloneResolvedOptions(*)
				.DisableMultiStroke = true
				if .Roughness != 0 {
					.Roughness += .FillShapeRoughnessGain
				}
				 := svgPath(, &)
				.Type = OpSetFillPath
				.Ops = mergedShape(.Ops)
				 = append(, )
			} else {
				 = append(, solidFillPolygons(, ))
			}
		} else {
			 := .computePathSize()
			 := []Point{{0, 0}, {[0], 0}, {[0], [1]}, {0, [1]}}
			 := patternFillPolygon(, )
			.Type = OpSetPath2DPattern
			.Size = &
			.Path = 
			 = append(, )
		}
	}
	if .Stroke != noStyle {
		 = append(, )
	}
	return drawable("path", , )
}

func ( *Generator) ( string) Point {
	if .surface != nil {
		,  := .surface.Width, .surface.Height
		if  == 0 {
			 = 100
		}
		if  == 0 {
			 = 100
		}
		return Point{, }
	}
	return Point{100, 100}
}

// OpsToPath serializes operations using Rough.js's SVG punctuation and number
// formatting.
func ( *Generator) ( OpSet) string {
	return .opsToPath(, nil)
}

func ( *Generator) ( OpSet,  *float64) string {
	var  strings.Builder
	for ,  := range .Ops {
		 := .Data
		if  != nil && * >= 0 {
			 = append([]float64(nil), ...)
			for  := range  {
				[] = jsToFixedNumber([], int(*))
			}
		}
		switch .Op {
		case OpMove:
			.WriteString("M" + jsNumberString([0]) + " " + jsNumberString([1]) + " ")
		case OpBCurveTo:
			.WriteString("C" + jsNumberString([0]) + " " + jsNumberString([1]) + ", " + jsNumberString([2]) + " " + jsNumberString([3]) + ", " + jsNumberString([4]) + " " + jsNumberString([5]) + " ")
		case OpLineTo:
			.WriteString("L" + jsNumberString([0]) + " " + jsNumberString([1]) + " ")
		}
	}
	return strings.TrimSpace(.String())
}

// ToPaths converts a Drawable into the ordered SVG path descriptions emitted
// by Rough.js's generator API.
func ( *Generator) ( Drawable) []PathInfo {
	.mu.Lock()
	defer .mu.Unlock()
	 := .Options
	if  == nil {
		 = &.defaultOptions
	}
	 := make([]PathInfo, 0, len(.Sets))
	for ,  := range .Sets {
		var  PathInfo
		switch .Type {
		case OpSetPath:
			 = PathInfo{D: .opsToPath(, .FixedDecimalPlaceDigits), Stroke: .Stroke, StrokeWidth: .StrokeWidth, Fill: noStyle}
		case OpSetFillPath:
			 := .Fill
			if  == "" {
				 = noStyle
			}
			 = PathInfo{D: .opsToPath(, .FixedDecimalPlaceDigits), Stroke: noStyle, StrokeWidth: 0, Fill: }
		case OpSetFillSketch:
			 = .fillSketch(, )
		case OpSetPath2DFill:
			 := .Fill
			if  == "" {
				 = noStyle
			}
			 = PathInfo{D: .Path, Stroke: noStyle, StrokeWidth: 0, Fill: }
		case OpSetPath2DPattern:
			if .Size == nil {
				continue
			}
			 := *.Size
			 := PatternInfo{
				X: 0, Y: 0, Width: 1, Height: 1,
				ViewBox:      "0 0 " + jsNumberString(jsRound([0])) + " " + jsNumberString(jsRound([1])),
				PatternUnits: "objectBoundingBox",
				Path:         .fillSketch(, ),
			}
			 = PathInfo{D: .Path, Stroke: noStyle, StrokeWidth: 0, Pattern: &}
		default:
			continue
		}
		 = append(, )
	}
	return 
}

func ( *Generator) ( OpSet,  *ResolvedOptions) PathInfo {
	 := .FillWeight
	if  < 0 {
		 = .StrokeWidth / 2
	}
	 := .Fill
	if  == "" {
		 = noStyle
	}
	return PathInfo{D: .opsToPath(, .FixedDecimalPlaceDigits), Stroke: , StrokeWidth: , Fill: noStyle}
}

func normalizePathData( string) string {
	 = strings.ReplaceAll(, "\n", " ")
	 = pathMinusSpaceRE.ReplaceAllString(, "-")
	return strings.Replace(, pathLiteralSpaceBug, " ", 1)
}

func mergedShape( []Op) []Op {
	 := make([]Op, 0, len())
	for ,  := range  {
		if  != 0 && .Op == OpMove {
			continue
		}
		 = append(, )
	}
	return 
}