extern isFinite
import 'node-sfnt/lib/math/bezierCubic2Q2' as bezierCubic2Q2
import 'bezier-js' as Bezier
import './point' as Point
import './transform' as : Transform && [object [transformPoint tp] [untransform utp] inverse]
import './anchor' as Anchor

define-macro xytransform : syntax-rules
	`[xytransform @tfm @x @y] : let [t : env.newt] `[begin \\
		set @t @x
		set @x : @x * @tfm.xx + @y * @tfm.yx + @tfm.x
		set @y : @t * @tfm.xy + @y * @tfm.yy + @tfm.y
	]

define [mix a b p] : a + (b - a) * p
define [ratio l r m] : if [l === r] 0 ((m - l) / (r - l))
define [byx a b] : a - b
define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)

define [closepoint p q t] : begin
	return : [Math.abs (p.x - q.x)] <= t && [Math.abs (p.y - q.y)] <= t
define [oncurveRemovable a b c t] : begin
	local xm : (a.x + c.x) / 2
	local ym : (a.y + c.y) / 2
	return : [not a.onCurve] && b.onCurve && [not c.onCurve] && [not a.cubic] && [not c.cubic] && (a.x <= b.x && b.x <= c.x || a.x >= b.x && b.x >= c.x) && (a.y <= b.y && b.y <= c.y || a.y >= b.y && b.y >= c.y) && [Math.abs (b.x - xm)] <= (t / 2) && [Math.abs (b.y - ym)] <= (t / 2)

export all : class Glyph
	public [new name] : begin
		set this.name name
		set this.unicode {}
		set this.contours {}
		set this.advanceWidth 500
		set this.anchors {.}
		set this.gizmo : Transform.Id
		set this.dependencies {}
		set this.defaultTag null
		return nothing
	static is {.unapply [function [obj arity] [if (obj <@ Glyph) {obj} null]]}
	
	public [set-width w] : begin
		this.advanceWidth = w
		return this

	public [assign-unicode u] : begin
		this.unicode.push : piecewise
			([typeof u] === 'string') : u.charCodeAt 0
			true u
		return this
	
	public [start-from x y] : begin
		local contour {[Point.transformed this.gizmo x y true]}
		set contour.tag this.defaultTag
		this.contours.push contour
		return this
	public moveTo : public start-from

	public [line-to x y] : begin
		this.contours.((this.contours.length - 1)).push [Point.transformed this.gizmo x y true]
		return this
	public lineTo : public line-to

	public [curve-control x y] : begin
		this.contours.((this.contours.length - 1)).push [tp this.gizmo [new Point x y false]]
		return this

	public [curve-to xc yc x y] : begin
		this.contours.((this.contours.length - 1)).push [Point.transformed this.gizmo xc yc false] [Point.transformed this.gizmo x y true]
		return this
	public curveTo : public curve-to

	public [cubic-to x1 y1 x2 y2 x y] : begin
		#local lastContour this.contours.(this.contours.length - 1)
		#local lastPoint : utp this.gizmo lastContour.(lastContour.length - 1)
		#local segments : bezierCubic2Q2 lastPoint {.x x1 .y y1} {.x x2 .y y2} {.x x .y y}
		#foreach {p0 {.x xc .y yc} {.x xf .y yf}} [items-of segments] : lastContour.push [Point.transformed this.gizmo xc yc false] [Point.transformed this.gizmo xf yf true]
		this.contours.((this.contours.length - 1)).push
			Point.transformed this.gizmo x1 y1 false true
			Point.transformed this.gizmo x2 y2 false true
			Point.transformed this.gizmo x y true
		return this
	public cubicTo : public cubic-to

	public [reverse-last] : begin
		if [this.contours && this.contours.(this.contours.length - 1)] : begin
			this.contours.(this.contours.length - 1) = [this.contours.(this.contours.length - 1).reverse]
		return this
	public reverseLast : public reverse-last

	public [tag-contour tag n] : begin
		if this.contours : begin
			local lastContour this.contours.(this.contours.length - 1)
			if lastContour : if tag : set lastContour.tag tag
		return this
	public [retag-contour oldtag newtag] : begin
		if this.contours : foreach [c : items-of this.contours] : if (c.tag === oldtag) : set c.tag newtag
		return this
	public [eject-contour tag] : begin
		set this.contours : this.contours.filter : lambda [c] (c.tag !== tag)
		return this

	public [include component copyAnchors] : begin
		piecewise
			(component <@ Function) : begin
				local t this.defaultTag
				if component.tag : set this.defaultTag component.tag
				component.call this
				set this.defaultTag t
				return this
			(component <@ Transform) : return : this.apply-transform component copyAnchors
			(component <@ Array) : begin
				local contours component
				local glyph {.contours contours}
			true : begin
				local contours component.contours
				local glyph component

		local shiftx 0
		local shifty 0
		local t this
		if (this.anchors && glyph.anchors) : foreach markid [items-of [Object.keys this.anchors]] : begin
			local anchorThis this.anchors.(markid)
			local anchorThat glyph.anchors.(markid)
			if (   anchorThis && (anchorThis.type === Anchor.BASE || anchorThis.mbx !== nothing && anchorThis.mby !== nothing)
				&& anchorThat && anchorThat.type === Anchor.MARK) : begin
				set shiftx : [fallback anchorThis.mbx anchorThis.x] - anchorThat.x
				set shifty : [fallback anchorThis.mby anchorThis.y] - anchorThat.y
				# we have a mark-to-mark position
				if (anchorThat.mbx !== nothing && anchorThat.mby !== nothing) : if (anchorThis.type === Anchor.BASE)
					then : set this.anchors.(markid) : new Anchor
						* (anchorThis.x + anchorThat.mbx - anchorThat.x)
						* (anchorThis.y + anchorThat.mby - anchorThat.y)
						* Anchor.BASE
					else : set this.anchors.(markid) : new Anchor
						* anchorThis.x
						* anchorThis.y
						* anchorThis.type
						* (anchorThis.mbx + anchorThat.mbx - anchorThat.x)
						* (anchorThis.mby + anchorThat.mby - anchorThat.y)
		if contours : begin
			local newcontours {}
			foreach [contour : items-of contours] : begin
				local c {}
				set c.tag : contour.tag || component.tag || t.defaultTag
				foreach [point : items-of contour] : begin
					c.push : new Point (point.x + shiftx) (point.y + shifty) point.onCurve point.cubic point.subdivided
				newcontours.push c
			set this.contours : this.contours.concat newcontours
		if (([not contours] || copyAnchors) && glyph.anchors) : begin
			foreach [k : items-of : Object.keys glyph.anchors] : set this.anchors.(k) glyph.anchors.(k)
		piecewise
			glyph.name : this.dependencies.push glyph.name
			glyph.dependencies : this.dependencies = [this.dependencies.concat glyph.dependencies]
		return this

	public [apply-transform transform alsoAnchors] : begin
		foreach [c : items-of this.contours] : foreach [j : range 0 c.length] : set c.(j) : tp transform c.(j)
		if alsoAnchors : foreach key [items-of [Object.keys this.anchors]] : begin
			set this.anchors.(key) : Anchor.transform transform this.anchors.(key)
		return this

	public [set-anchor id type x y mbx mby] : begin
		xytransform this.gizmo x y
		if (mbx !== nothing && mby !== nothing)
		: then : begin
			xytransform this.gizmo mbx mby
			set this.anchors.(id) : new Anchor x y type mbx mby
		: else : set this.anchors.(id) : new Anchor x y type
		return this

	public [cleanup t] : begin
		foreach c [range 0 this.contours.length] : begin
			local ocontour this.contours.(c)
			# add infections
			local contour {[new Point ocontour.0.x ocontour.0.y ocontour.0.onCurve]}
			local flag 0
			foreach [j : range 1 (ocontour.length - 1)] : piecewise
				flag : dec flag
				ocontour.(j).onCurve : contour.push ocontour.(j)
				ocontour.(j).cubic : begin
					local p0 contour.(contour.length - 1)
					local p1 ocontour.(j)
					local p2 ocontour.(j + 1)
					local p3 ocontour.(j + 2)
					local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y p3.x p3.y
					local ts [strand.inflections].y
					piecewise
						(!ts || ts.length < 1) : contour.push p1 p2 p3
						true : begin
							set ts [ts.sort byx]
							ts.unshift 0
							ts.push 1
							foreach [k : range 0 (ts.length - 1)] : begin
								local s : strand.split ts.(k) ts.(k + 1)
								if s.points : contour.push 
									new Point s.points.1.x s.points.1.y false true
									new Point s.points.2.x s.points.2.y false true
									new Point s.points.3.x s.points.3.y true
					set flag 2
				true : begin
					local p0 contour.(contour.length - 1)
					local p1 ocontour.(j)
					local p2 ocontour.(j + 1)
					if [not p2.onCurve] : set p2 : new Point [mix p1.x p2.x 0.5] [mix p1.y p2.y 0.5] true
					local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y
					local ts [strand.inflections].y
					piecewise
						(!ts || ts.length < 1) : contour.push p1 p2
						true : begin
							set ts [ts.sort byx]
							ts.unshift 0
							ts.push 1
							foreach [k : range 0 (ts.length - 1)] : begin
								local s : strand.split ts.(k) ts.(k + 1)
								if s.points : contour.push 
									new Point s.points.1.x s.points.1.y false
									new Point s.points.2.x s.points.2.y true
					set flag 1

			contour.push [new Point ocontour.(ocontour.length - 1).x ocontour.(ocontour.length - 1).y ocontour.(ocontour.length - 1).onCurve]
			
			# cleanup
			local cleanedContour {}
			foreach j [range 1 : contour.length - 1] : begin
				local p0 contour.(j - 1)
				local p1 contour.(j)
				local p2 contour.(j + 1)
				if [oncurveRemovable p0 p1 p2 t] : set p1.unimportant true
			foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point
			set contour cleanedContour
				
			set cleanedContour {}
			foreach j [range 0 contour.length] : if ([not contour.(j).cubic] && [not contour.(j).unimportant]) : begin
				local found false
				for [local k : j + 1] ((k < contour.length) && (
						[not contour.(k).cubic] && [closepoint contour.(j) contour.(k) t]
					||	contour.(k).cubic && contour.(k + 1).cubic 
							&& [closepoint contour.(j) contour.(k) t] 
							&& [closepoint contour.(j) contour.(k) t] 
							&& [closepoint contour.(j) contour.(k + 2) t])) [inc k] : begin
					set contour.(k).unimportant true
					set found true
					if (contour.(k).cubic && contour.(k + 1).cubic 
							&& [closepoint contour.(j) contour.(k) t] 
							&& [closepoint contour.(j) contour.(k) t] 
							&& [closepoint contour.(j) contour.(k + 2) t]) : begin
								set contour.(k + 1).unimportant true
								set contour.(k + 2).unimportant true
								set k : k + 2
				if found : begin
					set contour.(j).onCurve true
					set j (k - 1)
			foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point
			this.contours.(c) = cleanedContour
		return this