import './monotonic-interpolate' as smooth
import './transform' as : Transform && [object [transformPoint tp] [untransform utp] inverse]

define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
define [linreg x0 y0 x1 y1 x] : y0 + (x - x0) * (y1 - y0) / (x1 - x0)

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 [normalY angle] : Math.sin angle
define [normalX angle vex] : [Math.cos angle] * vex

export all : class SpiroExpansionContext
	public [new] : begin
		set this.gizmo [Transform.Id]
		set this.controlKnots {}
		set this.defaultd1 0
		set this.defaultd2 0

	public [moveTo x y unimportant] : begin
		if unimportant : return nothing
		# Transform incoming knots using gizmo
		xytransform this.gizmo x y
		this.controlKnots.push {.x x .y y .type 'g4' .d1 this.defaultd1 .d2 this.defaultd2}

	public [lineTo x y unimportant] : begin
		local lastKnot this.controlKnots.(this.controlKnots.length - 1)
		xytransform this.gizmo x y
		local thisKnot {.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2}
		if lastKnot : begin
			local normalAngle : Math.PI / 2 + [Math.atan2 (y - lastKnot.y) (x - lastKnot.x)]
			set thisKnot.normalAngle normalAngle
			if (lastKnot.normalAngle === nothing) : set lastKnot.normalAngle normalAngle
		if [not unimportant] : this.controlKnots.push thisKnot

	public [cubicTo x1 y1 x2 y2 x y unimportant] : begin
		local lastKnot this.controlKnots.(this.controlKnots.length - 1)
		xytransform this.gizmo x1 y1
		xytransform this.gizmo x2 y2
		xytransform this.gizmo x y
		local thisKnot {.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2}
		if (lastKnot && lastKnot.normalAngle === nothing) : begin
			local normalAngle : Math.PI / 2 + [Math.atan2 (y1 - lastKnot.y) (x1 - lastKnot.x)]
			if (lastKnot.normalAngle === nothing) : set lastKnot.normalAngle normalAngle
		if [not unimportant] : begin
			local normalAngle : Math.PI / 2 + [Math.atan2 (y - y2) (x - x2)]
			set thisKnot.normalAngle normalAngle
			this.controlKnots.push thisKnot

	public [set-width l r] : begin
		local lastKnot this.controlKnots.(this.controlKnots.length - 1)
		if lastKnot : then
			lastKnot.d1 = l; lastKnot.d2 = r
		: else
			this.defaultd1 = l; this.defaultd2 = r

	public [heads-to direction] : begin
		local lastKnot this.controlKnots.(this.controlKnots.length - 1)
		if lastKnot : begin
			lastKnot.proposedNormal = direction

	public [set-type type] : begin
		local lastKnot this.controlKnots.(this.controlKnots.length - 1)
		if lastKnot : begin
			lastKnot.type = type

	public [expand contrast] : begin
		local lhs {}
		local rhs {}
		
		local contrast : fallback contrast (1 / 0.9)
		
		local d1s {}
		local d2s {}
		local dxs {}
		local dys {}
		local js {}
		foreach j [range 0 this.controlKnots.length] : if [not this.controlKnots.(j).unimportant] : begin
			local knot this.controlKnots.(j)
			js.push j
			d1s.push knot.d1
			d2s.push knot.d2
			if knot.proposedNormal : then
				dxs.push : knot.proposedNormal.x - [normalX knot.normalAngle contrast]
				dys.push : knot.proposedNormal.y - [normalY knot.normalAngle contrast]
			: else
				dxs.push 0; dys.push 0
		local fd1 : smooth js d1s
		local fd2 : smooth js d2s
		local fdx : smooth js dxs
		local fdy : smooth js dys
	#	console.log deltaAngles

		# interpolate important knots
		foreach j [range 0 this.controlKnots.length] : begin
			local knot this.controlKnots.(j)
			if [not knot.unimportant] : begin
				set lhs.(j) : object
					x : knot.x + ([fdx j] + [normalX knot.normalAngle contrast]) * [fd1 j]
					y : knot.y + ([fdy j] + [normalY knot.normalAngle contrast]) * [fd1 j]
					type knot.type
				set rhs.(j) : object
					x : knot.x - ([fdx j] + [normalX knot.normalAngle contrast]) * [fd2 j]
					y : knot.y - ([fdy j] + [normalY knot.normalAngle contrast]) * [fd2 j]
					type : match knot.type
						"left" "right"
						"right" "left"
						type type
		# interpolate unimportant knots referencing their original position relationship
		foreach j [range 0 this.controlKnots.length] : begin
			local knot this.controlKnots.(j)
			if knot.unimportant : begin
				local jBefore (j - 1)
				while this.controlKnots.(jBefore).unimportant : dec jBefore
				local jAfter (j + 1)
				while this.controlKnots.(jAfter).unimportant : inc jAfter
				
				local knotBefore : utp this.gizmo this.controlKnots.(jBefore)
				local knotAfter : utp this.gizmo this.controlKnots.(jAfter)
				local ref : utp this.gizmo knot
				local lhsBefore : utp this.gizmo lhs.(jBefore)
				local lhsAfter  : utp this.gizmo lhs.(jAfter)
				local rhsBefore : utp this.gizmo rhs.(jBefore)
				local rhsAfter  : utp this.gizmo rhs.(jAfter)
				
				local kLHS : tp this.gizmo : object
					x : linreg knotBefore.x lhsBefore.x knotAfter.x lhsAfter.x ref.x
					y : linreg knotBefore.y lhsBefore.y knotAfter.y lhsAfter.y ref.y
				local kRHS : tp this.gizmo : object
					x : linreg knotBefore.x rhsBefore.x knotAfter.x rhsAfter.x ref.x
					y : linreg knotBefore.y rhsBefore.y knotAfter.y rhsAfter.y ref.y
				
				set lhs.(j) : object
					x kLHS.x
					y kLHS.y
					type knot.type
				set rhs.(j) : object
					x kRHS.x
					y kRHS.y
					type : match knot.type
						"left" "right"
						"right" "left"
						type type
		return {.lhs lhs .rhs rhs}