// SPDX-License-Identifier: Unlicense OR BSD-3-Clause

package psinterpreter

import (
	
	
	

	ot 
)

// PathBounds represents a control bounds for
// a glyph outline (in font units).
type PathBounds struct {
	Min, Max Point
}

// Enlarge enlarges the bounds to include pt
func ( *PathBounds) ( Point) {
	if .X < .Min.X {
		.Min.X = .X
	}
	if .X > .Max.X {
		.Max.X = .X
	}
	if .Y < .Min.Y {
		.Min.Y = .Y
	}
	if .Y > .Max.Y {
		.Max.Y = .Y
	}
}

// ToExtents converts a path bounds to the corresponding glyph extents.
func ( *PathBounds) () ot.GlyphExtents {
	,  := math.Round(.Min.X), math.Round(.Max.Y)
	return ot.GlyphExtents{
		XBearing: float32(),
		YBearing: float32(),
		Width:    float32(math.Round(.Max.X - )),
		Height:   float32(math.Round(.Min.Y - )),
	}
}

// Point is a 2D Point in font units.
type Point struct{ X, Y float64 }

// Move translates the Point.
func ( *Point) (,  float64) {
	.X += 
	.Y += 
}

func ( Point) () ot.SegmentPoint {
	return ot.SegmentPoint{X: float32(.X), Y: float32(.Y)}
}

// CharstringReader provides implementation
// of the operators found in a font charstring.
type CharstringReader struct {
	// Acumulated segments for the glyph outlines
	Segments []ot.Segment
	// Acumulated bounds for the glyph outlines
	Bounds PathBounds

	vstemCount   int32
	hstemCount   int32
	hintmaskSize int32

	CurrentPoint Point
	firstPoint   Point // first point in path, required to check if a path is closed
	isPathOpen   bool

	seenHintmask bool

	// bounds for an empty path is {0,0,0,0}
	// however, for the first point in the path,
	// we must not compare the coordinates with {0,0,0,0}
	seenPoint bool
}

// enlarges the current bounds to include the Point (x,y).
func ( *CharstringReader) ( Point) {
	if !.seenPoint {
		.Bounds.Min, .Bounds.Max = , 
		.seenPoint = true
		return
	}
	.Bounds.Enlarge()
}

func ( *CharstringReader) ( *Machine) {
	.hstemCount += .ArgStack.Top / 2
}

func ( *CharstringReader) ( *Machine) {
	.vstemCount += .ArgStack.Top / 2
}

func ( *CharstringReader) ( *Machine) {
	if !.seenHintmask {
		.vstemCount += .ArgStack.Top / 2
		.hintmaskSize = (.hstemCount + .vstemCount + 7) >> 3
		.seenHintmask = true
	}
}

func ( *CharstringReader) ( *Machine) {
	.determineHintmaskSize()
	.SkipBytes(.hintmaskSize)
}

func ( *CharstringReader) ( Point) {
	.ensureClosePath()

	.CurrentPoint.Move(.X, .Y)
	.isPathOpen = false
	.firstPoint = .CurrentPoint
	.Segments = append(.Segments, ot.Segment{
		Op:   ot.SegmentOpMoveTo,
		Args: [3]ot.SegmentPoint{.CurrentPoint.toSP()},
	})
}

// pt is in absolute coordinates
func ( *CharstringReader) ( Point) {
	if !.isPathOpen {
		.isPathOpen = true
		.updateBounds(.CurrentPoint)
	}
	.CurrentPoint = 
	.updateBounds()
	.Segments = append(.Segments, ot.Segment{
		Op:   ot.SegmentOpLineTo,
		Args: [3]ot.SegmentPoint{.toSP()},
	})
}

func ( *CharstringReader) (, ,  Point) {
	if !.isPathOpen {
		.isPathOpen = true
		.updateBounds(.CurrentPoint)
	}
	/* include control Points */
	.updateBounds()
	.updateBounds()
	.CurrentPoint = 
	.updateBounds()
	.Segments = append(.Segments, ot.Segment{
		Op:   ot.SegmentOpCubeTo,
		Args: [3]ot.SegmentPoint{.toSP(), .toSP(), .toSP()},
	})
}

func ( *CharstringReader) (, , , , ,  Point) {
	.curve(, , )
	.curve(, , )
}

func ( *CharstringReader) () {
	if .firstPoint != .CurrentPoint {
		.Segments = append(.Segments, ot.Segment{
			Op:   ot.SegmentOpLineTo,
			Args: [3]ot.SegmentPoint{.firstPoint.toSP()},
		})
	}
}

// ------------------------------------------------------------

// LocalSubr pops the subroutine index and call it
func ( *Machine) error {
	if .ArgStack.Top < 1 {
		return errors.New("invalid callsubr operator (empty stack)")
	}
	 := int32(.ArgStack.Pop())
	return .CallSubroutine(, true)
}

// GlobalSubr pops the subroutine index and call it
func ( *Machine) error {
	if .ArgStack.Top < 1 {
		return errors.New("invalid callgsubr operator (empty stack)")
	}
	 := int32(.ArgStack.Pop())
	return .CallSubroutine(, false)
}

// ClosePath closes the current contour, adding
// a segment to the first point if needed.
func ( *CharstringReader) () {
	.ensureClosePath()
	.isPathOpen = false
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top < 2 {
		return errors.New("invalid rmoveto operator")
	}
	 := .ArgStack.Pop()
	 := .ArgStack.Pop()
	.move(Point{, })
	return nil
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top < 1 {
		return errors.New("invalid vmoveto operator")
	}
	 := .ArgStack.Pop()
	.move(Point{0, })
	return nil
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top < 1 {
		return errors.New("invalid hmoveto operator")
	}
	 := .ArgStack.Pop()
	.move(Point{, 0})
	return nil
}

func ( *CharstringReader) ( *Machine) {
	for  := int32(0); +2 <= .ArgStack.Top;  += 2 {
		 := .CurrentPoint
		.Move(.ArgStack.Vals[], .ArgStack.Vals[+1])
		.line()
	}
	.ArgStack.Clear()
}

func ( *CharstringReader) ( *Machine) {
	var  int32
	for ; +2 <= .ArgStack.Top;  += 2 {
		 := .CurrentPoint
		.X += .ArgStack.Vals[]
		.line()
		.Y += .ArgStack.Vals[+1]
		.line()
	}
	if  < .ArgStack.Top {
		 := .CurrentPoint
		.X += .ArgStack.Vals[]
		.line()
	}
}

func ( *CharstringReader) ( *Machine) {
	var  int32
	for ; +2 <= .ArgStack.Top;  += 2 {
		 := .CurrentPoint
		.Y += .ArgStack.Vals[]
		.line()
		.X += .ArgStack.Vals[+1]
		.line()
	}
	if  < .ArgStack.Top {
		 := .CurrentPoint
		.Y += .ArgStack.Vals[]
		.line()
	}
}

// RelativeCurveTo draws a curve with controls points computed from
// the current point and `arg1`, `arg2`, `arg3`
func ( *CharstringReader) (, ,  Point) {
	 := .CurrentPoint
	.Move(.X, .Y)
	 := 
	.Move(.X, .Y)
	 := 
	.Move(.X, .Y)
	.curve(, , )
}

func ( *CharstringReader) ( *Machine) {
	for  := int32(0); +6 <= .ArgStack.Top;  += 6 {
		.RelativeCurveTo(
			Point{.ArgStack.Vals[], .ArgStack.Vals[+1]},
			Point{.ArgStack.Vals[+2], .ArgStack.Vals[+3]},
			Point{.ArgStack.Vals[+4], .ArgStack.Vals[+5]},
		)
	}
}

func ( *CharstringReader) ( *Machine) {
	var (
		   int32
		 = .CurrentPoint
	)
	if (.ArgStack.Top & 1) != 0 {
		.Y += (.ArgStack.Vals[])
		++
	}
	for ; +4 <= .ArgStack.Top;  += 4 {
		.X += .ArgStack.Vals[]
		 := 
		.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
		 := 
		.X += .ArgStack.Vals[+3]
		.curve(, , )
		 = .CurrentPoint
	}
}

func ( *CharstringReader) ( *Machine) {
	var  int32
	if (.ArgStack.Top % 8) >= 4 {
		 := .CurrentPoint
		.Y += .ArgStack.Vals[]
		 := 
		.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
		 := 
		.X += .ArgStack.Vals[+3]
		 += 4

		for ; +8 <= .ArgStack.Top;  += 8 {
			.curve(, , )
			 = .CurrentPoint
			.X += (.ArgStack.Vals[])
			 = 
			.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
			 = 
			.Y += (.ArgStack.Vals[+3])
			.curve(, , )

			 = 
			.Y += (.ArgStack.Vals[+4])
			 = 
			.Move(.ArgStack.Vals[+5], .ArgStack.Vals[+6])
			 = 
			.X += (.ArgStack.Vals[+7])
		}
		if  < .ArgStack.Top {
			.Y += (.ArgStack.Vals[])
		}
		.curve(, , )
	} else {
		for ; +8 <= .ArgStack.Top;  += 8 {
			 := .CurrentPoint
			.Y += (.ArgStack.Vals[])
			 := 
			.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
			 := 
			.X += (.ArgStack.Vals[+3])
			.curve(, , )

			 = 
			.X += (.ArgStack.Vals[+4])
			 = 
			.Move(.ArgStack.Vals[+5], .ArgStack.Vals[+6])
			 = 
			.Y += (.ArgStack.Vals[+7])
			if (.ArgStack.Top- < 16) && ((.ArgStack.Top & 1) != 0) {
				.X += (.ArgStack.Vals[+8])
			}
			.curve(, , )
		}
	}
}

func ( *CharstringReader) ( *Machine) {
	var  int32
	if (.ArgStack.Top % 8) >= 4 {
		 := .CurrentPoint
		.X += (.ArgStack.Vals[])
		 := 
		.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
		 := 
		.Y += (.ArgStack.Vals[+3])
		 += 4

		for ; +8 <= .ArgStack.Top;  += 8 {
			.curve(, , )
			 = .CurrentPoint
			.Y += (.ArgStack.Vals[])
			 = 
			.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
			 = 
			.X += (.ArgStack.Vals[+3])
			.curve(, , )

			 = 
			.X += .ArgStack.Vals[+4]
			 = 
			.Move(.ArgStack.Vals[+5], .ArgStack.Vals[+6])
			 = 
			.Y += .ArgStack.Vals[+7]
		}
		if  < .ArgStack.Top {
			.X += (.ArgStack.Vals[])
		}
		.curve(, , )
	} else {
		for ; +8 <= .ArgStack.Top;  += 8 {
			 := .CurrentPoint
			.X += (.ArgStack.Vals[])
			 := 
			.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
			 := 
			.Y += (.ArgStack.Vals[+3])
			.curve(, , )

			 = 
			.Y += (.ArgStack.Vals[+4])
			 = 
			.Move(.ArgStack.Vals[+5], .ArgStack.Vals[+6])
			 = 
			.X += (.ArgStack.Vals[+7])
			if (.ArgStack.Top- < 16) && ((.ArgStack.Top & 1) != 0) {
				.Y += .ArgStack.Vals[+8]
			}
			.curve(, , )
		}
	}
}

func ( *CharstringReader) ( *Machine) error {
	 := .ArgStack.Top
	if  < 8 {
		return fmt.Errorf("expected at least 8 operands for <rcurveline>, got %d", )
	}

	var  int32
	 :=  - 2
	for ; +6 <= ;  += 6 {
		 := .CurrentPoint
		.Move(.ArgStack.Vals[], .ArgStack.Vals[+1])
		 := 
		.Move(.ArgStack.Vals[+2], .ArgStack.Vals[+3])
		 := 
		.Move(.ArgStack.Vals[+4], .ArgStack.Vals[+5])
		.curve(, , )
	}

	 := .CurrentPoint
	.Move(.ArgStack.Vals[], .ArgStack.Vals[+1])
	.line()

	return nil
}

func ( *CharstringReader) ( *Machine) error {
	 := .ArgStack.Top
	if  < 8 {
		return fmt.Errorf("expected at least 8 operands for <rlinecurve>, got %d", )
	}
	var  int32
	 :=  - 6
	for ; +2 <= ;  += 2 {
		 := .CurrentPoint
		.Move(.ArgStack.Vals[], .ArgStack.Vals[+1])
		.line()
	}

	 := .CurrentPoint
	.Move(.ArgStack.Vals[], .ArgStack.Vals[+1])
	 := 
	.Move(.ArgStack.Vals[+2], .ArgStack.Vals[+3])
	 := 
	.Move(.ArgStack.Vals[+4], .ArgStack.Vals[+5])
	.curve(, , )

	return nil
}

func ( *CharstringReader) ( *Machine) {
	var  int32
	 := .CurrentPoint
	if (.ArgStack.Top & 1) != 0 {
		.X += .ArgStack.Vals[]
		++
	}
	for ; +4 <= .ArgStack.Top;  += 4 {
		.Y += .ArgStack.Vals[]
		 := 
		.Move(.ArgStack.Vals[+1], .ArgStack.Vals[+2])
		 := 
		.Y += .ArgStack.Vals[+3]
		.curve(, , )
		 = .CurrentPoint
	}
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top != 7 {
		return fmt.Errorf("expected 7 operands for <hflex>, got %d", .ArgStack.Top)
	}

	 := .CurrentPoint
	.X += .ArgStack.Vals[0]
	 := 
	.Move(.ArgStack.Vals[1], .ArgStack.Vals[2])
	 := 
	.X += .ArgStack.Vals[3]
	 := 
	.X += .ArgStack.Vals[4]
	 := 
	.X += .ArgStack.Vals[5]
	.Y = .Y
	 := 
	.X += .ArgStack.Vals[6]

	.doubleCurve(, , , , , )
	return nil
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top != 13 {
		return fmt.Errorf("expected 13 operands for <flex>, got %d", .ArgStack.Top)
	}

	 := .CurrentPoint
	.Move(.ArgStack.Vals[0], .ArgStack.Vals[1])
	 := 
	.Move(.ArgStack.Vals[2], .ArgStack.Vals[3])
	 := 
	.Move(.ArgStack.Vals[4], .ArgStack.Vals[5])
	 := 
	.Move(.ArgStack.Vals[6], .ArgStack.Vals[7])
	 := 
	.Move(.ArgStack.Vals[8], .ArgStack.Vals[9])
	 := 
	.Move(.ArgStack.Vals[10], .ArgStack.Vals[11])

	.doubleCurve(, , , , , )
	return nil
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top != 9 {
		return fmt.Errorf("expected 9 operands for <hflex1>, got %d", .ArgStack.Top)
	}
	 := .CurrentPoint
	.Move(.ArgStack.Vals[0], .ArgStack.Vals[1])
	 := 
	.Move(.ArgStack.Vals[2], .ArgStack.Vals[3])
	 := 
	.X += .ArgStack.Vals[4]
	 := 
	.X += .ArgStack.Vals[5]
	 := 
	.Move(.ArgStack.Vals[6], .ArgStack.Vals[7])
	 := 
	.X += .ArgStack.Vals[8]
	.Y = .CurrentPoint.Y

	.doubleCurve(, , , , , )
	return nil
}

func ( *CharstringReader) ( *Machine) error {
	if .ArgStack.Top != 11 {
		return fmt.Errorf("expected 11 operands for <flex1>, got %d", .ArgStack.Top)
	}

	var  Point
	for  := 0;  < 10;  += 2 {
		.Move(.ArgStack.Vals[], .ArgStack.Vals[+1])
	}

	 := .CurrentPoint
	.Move(.ArgStack.Vals[0], .ArgStack.Vals[1])
	 := 
	.Move(.ArgStack.Vals[2], .ArgStack.Vals[3])
	 := 
	.Move(.ArgStack.Vals[4], .ArgStack.Vals[5])
	 := 
	.Move(.ArgStack.Vals[6], .ArgStack.Vals[7])
	 := 
	.Move(.ArgStack.Vals[8], .ArgStack.Vals[9])
	 := 

	if math.Abs(.X) > math.Abs(.Y) {
		.X += .ArgStack.Vals[10]
		.Y = .CurrentPoint.Y
	} else {
		.X = .CurrentPoint.X
		.Y += .ArgStack.Vals[10]
	}

	.doubleCurve(, , , , , )
	return nil
}