import 'libspiro-js' as libspiro
import './spiroexpand' as SpiroExpansionContext
import './fairify' as fairify

define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
define [mix a b p] : a + (b - a) * p
define [bez2 a b c t] : (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c
define [bez3 a b c d t] : (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * t * t * (1 - t) * c + t * t * t * d


export : define [SetupBuilders args] : begin
	define [object para Glyph CONTRAST globalTransform STROKE SUPERNESS] args

	define [g4 x y f] {.x x .y y .type 'g4' .af f}
	define [g2 x y f] {.x x .y y .type 'g2' .af f}
	define [corner x y f] {.x x .y y .type 'corner' .af f}
	define [flat x y f] {.x x .y y .type 'left' .af f}
	define [curl x y f] {.x x .y y .type 'right' .af f}
	define [close f] {.type 'close' .af f}
	define [end f] {.type 'end' .af f}
	
	define straight {.l flat .r curl}
	
	#derived knots
	#"ai" knots, used for left and right edges of letters `o`, and similar letters
	define flat.ai : if para.isItalic g4 flat
	define curl.ai : if para.isItalic g4 curl
	#directional bi-knots
	let
		directions {{.name 'up' .x 0 .y 1}, {.name 'down' .x 0 .y (-1)}, {.name 'left' .x (-1) .y 0}, {.name 'right' .x 1 .y 0}}
		adhensions {{.name 'start' .l 0 .r 0.01}, {.name 'mid', .l (-0.005) .r 0.005}, {.name 'end', .l (-0.01) .r 0}}
		knottypes  {g4, g2, corner, straight}
		foreach [direction : items-of directions] : let [d direction] : begin
			foreach [knottype : items-of knottypes] : let [kt knottype] : begin
				set kt.(d.name) {.}
				foreach [adh : items-of adhensions] : let [a adh] : begin
					set kt.(d.name).(a.name) : lambda [x y f] : list
						[fallback kt.l kt] (x + d.x * a.l) (y + d.y * a.l) f
						[fallback kt.r kt] (x + d.x * a.r) (y + d.y * a.r) f
	
	# Aux functions
	define [widths l r] : lambda [] : this.set-width l r
	define [widths.lhs w] : widths [fallback w STROKE] 0
	define [widths.rhs w] : widths 0 [fallback w STROKE]
	define [widths.center w] : widths ([fallback w STROKE] / 2) ([fallback w STROKE] / 2)
	
	define [heading d] : lambda [] : this.heads-to d
	define [widths.heading l r d] : lambda [] : begin [this.set-width l r] [this.heads-to d]
	define [widths.lhs.heading w d] : lambda [] : begin [this.set-width [fallback w STROKE] 0] [this.heads-to d]
	define [widths.rhs.heading w d] : lambda [] : begin [this.set-width 0 [fallback w STROKE]] [this.heads-to d]
	define [widths.center.heading w d] : lambda [] : begin [this.set-width ([fallback w STROKE] / 2) ([fallback w STROKE] / 2)] [this.heads-to d]
	define [unimportant] : begin
		if (this.points && this.points.length && this.points.(this.points.length - 1)) : this.points.(this.points.length - 1).subdivided = true
		if (this.controlKnots && this.controlKnots.length && this.controlKnots.(this.controlKnots.length - 1)) : this.controlKnots.(this.controlKnots.length - 1).unimportant = true
	define [important] nothing
	
	# Interpolation pesudoknots
	define [afInterpolate before after args] : g4
		mix before.x after.x args.rx
		mix before.y after.y args.ry
		fallback args.raf unimportant
	define [afInterpolateThem before after args] : begin
		local knots {}
		foreach {rx ry preserve} [items-of args.rs] : knots.push : [fallback args.ty g2] [mix before.x after.x rx] [mix before.y after.y ry] : fallback args.raf : match preserve
			1 before.af
			2 after.af
			otherwise unimportant
		return knots
	define [alsothru rx ry raf] {.type 'interpolate' .rx rx .ry ry .raf raf .af afInterpolate}
	define [alsothruthem rs raf ty] {.type 'interpolate' .rs rs .raf raf .ty ty .af afInterpolateThem}
	define [bezcontrols x1 y1 x2 y2 _samples raf ty] : begin
		local samples : fallback _samples 3
		local rs {}
		foreach j [range 1 samples] : rs.push : list
			bez3 0 x1 x2 1 (j / samples)
			bez3 0 y1 y2 1 (j / samples)
		alsothruthem rs raf ty
	define [quadcontrols x1 y1 samples raf ty] : bezcontrols (x1 * 2 / 3) (y1 * 2 / 3) [mix 1 x1 (2 / 3)] [mix 1 y1 (2 / 3)] samples raf ty
	
	define [bezcontrols.absolute x1 y1 x2 y2 _samples raf ty] : object [type 'interpolate'] : af : lambda [before after] : begin
		local samples : fallback _samples 3
		local rs {}
		foreach j [range 1 samples] : rs.push : [fallback ty g4]
			bez3 before.x x1 x2 after.x (j / samples)
			bez3 before.y y1 y2 after.y (j / samples)
			fallback raf unimportant
		return rs

	define DEFAULT_STEPS 6
	define {jhv, jvh} : let [cache {}] : begin
		local [build samples] : begin
			local hv {}
			local vh {}
			foreach [j : range 1 samples] : begin
				local theta : j / samples * Math.PI / 2
				local c : Math.pow [Math.cos theta] (2 / SUPERNESS)
				local s : Math.pow [Math.sin theta] (2 / SUPERNESS)
				hv.push {s (1 - c)}
				vh.push {(1 - c) s}
			cache.(samples) = {.hv hv .vh vh}
		local [hv samples] : begin
			if [not cache.(samples)] : build samples
			return cache.(samples).hv
		local [vh samples] : begin
			if [not cache.(samples)] : build samples
			return cache.(samples).vh
		list hv vh
	define [archv samples notiny k raf] : alsothruthem [jhv [fallback samples DEFAULT_STEPS]] raf
	define [arcvh samples notiny k raf] : alsothruthem [jvh [fallback samples DEFAULT_STEPS]] raf
	
	define [complexThru] : begin
		local a : {}.slice.call arguments
		return {.type 'interpolate' .af [lambda [before after args] : begin \\
			local ks {}
			foreach knot [items-of a] : ks.push [knot.af.call this before after knot]
			return ks
		]}
	define [flatten knots] : begin
		local a {}
		foreach p [items-of knots] : piecewise
			(p <@ Array) : set a : a.concat [flatten p]
			true         : a.push p
		return a
	define [prepareSpiroKnots _knots s] : begin
		local closed false
		local lastafs {}
		local knots _knots
		while (knots.0 && knots.0 <@ Function) : begin
			knots.0.call s
			set knots : knots.slice 1
		while (knots.(knots.length - 1) && (knots.(knots.length - 1).type === 'close' || knots.(knots.length - 1).type === 'end')) : begin
			set closed : knots.(knots.length - 1).type === 'close'
			lastafs.push knots.(knots.length - 1).af
			set knots : knots.slice 0 (-1)
		set knots : flatten knots
		if closed : knots.push knots.0
		foreach j [range 0 knots.length] : if (knots.(j) && knots.(j).type === 'interpolate') : begin
			set knots.(j) : knots.(j).af.call s knots.(j - 1) knots.(j + 1) knots.(j)
		if closed : knots.pop
		return {.knots [flatten knots] .closed closed .lastafs lastafs}
	
	define QUAD false
	define PRECISION 0.5
	define [dispiro] : begin
		local s : new SpiroExpansionContext
		set s.gizmo globalTransform
		local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots [{}.slice.call arguments 0] s
		foreach knot [items-of knots] : let [ty knot.type] [af knot.af] : begin
			set knot.af : lambda [] : begin
				this.set-type ty
				if af : af.apply this arguments
		libspiro.spiroToBezierOnContext knots closed s
		foreach af [items-of lastafs] : if af : af.call s
		
		local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast CONTRAST]
		if closed : then
			local g : new Glyph
			libspiro.spiroToBezierOnContext [lhs.slice 0 (-1)] true g QUAD PRECISION
			local lhsContour g.contours.0
			set g.contours {}
			libspiro.spiroToBezierOnContext [rhs.reverse :.slice 0 (-1)] true g QUAD PRECISION
			local rhsContour g.contours.0
			set g.contours {[lhsContour.concat rhsContour]}
		: else
			local g : new Glyph
			lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
			libspiro.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
		if ([not s.unfair] && [not para.unfair]) : foreach [j : range 0 g.contours.length] : begin
			set g.contours.(j) : fairify g.contours.(j) globalTransform s.dense s.cleanmore
		set g.knots knots
		set g.lhsknots lhs
		set g.rhsknots rhs
		return g
	define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [] : begin
		local g : new Glyph
		set g.gizmo globalTransform 
		local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots k g
		libspiro.spiroToBezierOnContext knots closed g QUAD PRECISION
		foreach af [items-of lastafs] : if af : af.call g
		if [not para.unfair] : foreach [j : range 0 g.contours.length] : begin
			set g.contours.(j) : fairify g.contours.(j) (g.fairGizmo || g.gizmo) g.dense g.cleanmore
		this.include g
	
	return [object
		g4 g2 corner flat curl close end straight
		widths heading unimportant important
		alsothru alsothruthem bezcontrols quadcontrols archv arcvh complexThru
		dispiro spiro-outline]