export function XProto(url: string, options: XOptions): XRequest {
    let request: XRequest = new XRequest();
    let opts: XOptions = Object.create(options);
    opts.url = url;
    opts.responseType = "arraybuffer";

    let data = opts.data ? opts.data : undefined;

    if (opts.data !== null && opts.data !== undefined) {
        opts.method = "POST";
    }
    request.open(opts);

    setProtoHeaders(request, opts);
    setHeaders(request, opts);
    setProtoConvert(request, opts);
    setFunctionOverrides(request, opts);

    if (isBinarySerializer(data)) {
        request.send(data.serializeBinary())
    } else {
        request.send(data);
    }

    return request;
}


function setHeaders(request: XRequest, options: XOptions) {
    if (options.hasOwnProperty('headers')) return;

    for (let key in options.headers) {
        request.setHeader(key, options.headers[key]);
    }
}


function setXRequestedWithHeader(request: XRequest, options: XOptions) {
    if (!options.hasOwnProperty('requestedWith') || options.requestedWith) {
        request.setHeader("X-Requested-With", "XMLHttpRequest");
    }
}


function setTextHeaders(request: XRequest , options: XOptions) {
    setXRequestedWithHeader(request, options);
    request.setHeader("Content-Type", "text/plain");
}


function setJSONHeaders(request: XRequest, options: XOptions) {
    setXRequestedWithHeader(request, options);
    request.setHeader("Content-Type", "application/json");
}


function setProtoHeaders(request: XRequest, options: XOptions) {
    setXRequestedWithHeader(request, options);
    request.setHeader("Content-Types", "application/octet-stream");
}


function  setFunctionOverrides(request: XRequest, options: XOptions) {
    if (options.hasOwnProperty('convert')) request.convert = options.convert;
}


function setProtoConvert(request: XRequest, options: XOptions) {
    if (!options.hasOwnProperty('proto')) request.convert = buildProtoConvert(options.proto);
}


function buildProtoConvert(proto: BinaryDeserializer) {
    return function (data: Uint8Array): any {
        return proto.deserializeBinary(data);
    }
}


export interface BinarySerializer {
    serializeBinary: () => Uint8Array;
}


export interface BinaryDeserializer {
    deserializeBinary: (d: Uint8Array | string) => any;
}


// The AppComponent is now attached to a router and displaying routed views. For this reason and to distinguish it from other kinds of components, we call this type of component a Router Component.
@Component({
  selector: 'my-app',
  template: `

{{title}}

  Dashboard
  Heroes

  <router-outlet></router-outlet>
`,
  directives: [ROUTER_DIRECTIVES],
  providers: [
    ROUTER_PROVIDERS,
    HeroService
  ]
})
@RouteConfig([
  {
    path: '/detail/:id',
    name: 'HeroDetail',
    component: HeroDetailComponent
  },
  {
    path: '/heroes',
    name: 'Heroes',
    component: HeroesComponent
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: DashboardComponent,
    useAsDefault: true
  }
])
export class AppComponent {
  title = 'Tour of Heroes';
}


/**
 * 删除时保留 EMPTY_P
 * @param editor 编辑器实例
 * @param deleteUpEvents delete 键 up 时的 hooks
 * @param deleteDownEvents delete 建 down 时的 hooks
 */
function deleteToKeepP(editor: Editor, deleteUpEvents: Function[], deleteDownEvents: Function[]) {
    function upFn() {
        const $textElem = editor.$textElem
        const html = editor.$textElem.html()
        const text = editor.$textElem.text()
        const txtHtml = html.trim()
        /**
          @description
            如果编辑区清空的状态下，单单插入一张图片，删除图片后，会存在空的情况：'

'
            需要包含这种边界情况
        **/

        const emptyTags: string[] = ['

', '
', '

', EMPTY_P]

        // 编辑器中的字符是""或空白，说明内容为空
        if (/^\s*$/.test(text) && (!txtHtml || emptyTags.includes(txtHtml))) {
            // 内容空了
            $textElem.html(EMPTY_P)

            /**
             * 当编辑器 - 文本区内容为空的情况下，会插入一个空的P，此时应该将选区移动到这个空标签上，重置选区
             * bug: 如果选区没有从$textElem上调整到p上，就会有问题，在清空内容，设置标题时，会报错。
             */
            const containerElem = $textElem.getNode()

            // 设置新的选区
            editor.selection.createRangeByElems(
                containerElem.childNodes[0],
                containerElem.childNodes[0]
            )

            const $selectionElem = editor.selection.getSelectionContainerElem()!

            editor.selection.restoreSelection()

            // 设置折叠后的光标位置，在firebox等浏览器下
            // 光标设置在end位置会自动换行
            editor.selection.moveCursor($selectionElem.getNode(), 0)
        }
    }
    // deleteUpEvents.push(upFn)

    function downFn(e: Event) {
        const $textElem = editor.$textElem
        const txtHtml = $textElem.html().toLowerCase().trim()
        if (txtHtml === EMPTY_P) {
            // 最后剩下一个空行，就不再删除了
            e.preventDefault()
            return
        }
    }
    deleteDownEvents.push(downFn)
}


function upFn() {
        const $textElem = editor.$textElem
        const html = editor.$textElem.html()
        const text = editor.$textElem.text()
        const txtHtml = html.trim()
        /**
          @description
            如果编辑区清空的状态下，单单插入一张图片，删除图片后，会存在空的情况：'

'
            需要包含这种边界情况
        **/

        const emptyTags: string[] = ['

', '
', '

', EMPTY_P]

        // 编辑器中的字符是""或空白，说明内容为空
        if (/^\s*$/.test(text) && (!txtHtml || emptyTags.includes(txtHtml))) {
            // 内容空了
            $textElem.html(EMPTY_P)

            /**
             * 当编辑器 - 文本区内容为空的情况下，会插入一个空的P，此时应该将选区移动到这个空标签上，重置选区
             * bug: 如果选区没有从$textElem上调整到p上，就会有问题，在清空内容，设置标题时，会报错。
             */
            const containerElem = $textElem.getNode()

            // 设置新的选区
            editor.selection.createRangeByElems(
                containerElem.childNodes[0],
                containerElem.childNodes[0]
            )

            const $selectionElem = editor.selection.getSelectionContainerElem()!

            editor.selection.restoreSelection()

            // 设置折叠后的光标位置，在firebox等浏览器下
            // 光标设置在end位置会自动换行
            editor.selection.moveCursor($selectionElem.getNode(), 0)
        }
    }


// deleteUpEvents.push(upFn)
function downFn(e: Event) {
        const $textElem = editor.$textElem
        const txtHtml = $textElem.html().toLowerCase().trim()
        if (txtHtml === EMPTY_P) {
            // 最后剩下一个空行，就不再删除了
            e.preventDefault()
            return
        }
    }


/**
 * 剪切时保留 EMPTY_P
 * @param editor 编辑器实例
 * @param cutEvents keydown hooks
 */
export function cutToKeepP(editor: Editor, cutEvents: Function[]) {
    function upFn(e: KeyboardEvent) {
        if (e.keyCode !== 88) {
            return
        }

        const $textElem = editor.$textElem
        const txtHtml = $textElem.html().toLowerCase().trim()

        // firefox 时用 txtHtml === '
' 判断，其他用 !txtHtml 判断
        if (!txtHtml || txtHtml === '
') {
            // 内容空了
            const $p = $(EMPTY_P)
            $textElem.html(' ') // 一定要先清空，否则在 firefox 下有问题
            $textElem.append($p)
            editor.selection.createRangeByElem($p, false, true)
            editor.selection.restoreSelection()
            // 设置折叠后的光标位置，在firebox等浏览器下
            // 光标设置在end位置会自动换行
            editor.selection.moveCursor($p.getNode(), 0)
        }
    }

    cutEvents.push(upFn)
}


function upFn(e: KeyboardEvent) {
        if (e.keyCode !== 88) {
            return
        }

        const $textElem = editor.$textElem
        const txtHtml = $textElem.html().toLowerCase().trim()

        // firefox 时用 txtHtml === '
' 判断，其他用 !txtHtml 判断
        if (!txtHtml || txtHtml === '
') {
            // 内容空了
            const $p = $(EMPTY_P)
            $textElem.html(' ') // 一定要先清空，否则在 firefox 下有问题
            $textElem.append($p)
            editor.selection.createRangeByElem($p, false, true)
            editor.selection.restoreSelection()
            // 设置折叠后的光标位置，在firebox等浏览器下
            // 光标设置在end位置会自动换行
            editor.selection.moveCursor($p.getNode(), 0)
        }
    }


declare function INIT_HTML(): void


function objectAssignConcatArray<T, U>(a: T, b: U): T & U {
	for (const key of Object.keys(b)) {
		if (Array.isArray(g[key]) && Array.isArray(b[key])) {
			a[key].push(...b[key])
		} else if (undefined !== b[key]) {
			a[key] = b[key]
		}
	}
	return a as any
}


function initBRep() {
	eye.pos = V(1, 2, 101)
	eye.focus = V(0, 1, 0)
	eye.up = V(0, 1, 0)
	eye.zoomFactor = 8

	const htmlContext = INIT_HTML()

	const hash = window.location.search.substr(1) || window.location.hash.substr(1) || ''
	const command = decodeURIComponent(hash)
	console.log(command)
	const hashContext = new Function(
		`let ${renderObjectKeys.join(',')};${command};return{${renderObjectKeys.join(',')}}`,
	)() as RenderObjects

	// hashContext last, so i value in hash wins
	objectAssignConcatArray(g, htmlContext)
	objectAssignConcatArray(g, hashContext)
	console.log(htmlContext)

	Object.assign(eye, g.i)
	// let gets: any = {a, b, c, d, mesh, edges, points, vectors}
	// g.hjk && Object.assign(g, HJK())
	arrayLiteralType(['a', 'b', 'c', 'd']).forEach(k => {
		if (g[k]) {
			bReps.push(g[k])
		}
	})

	bRepMeshes = bReps.map(bRep => bRep.toMesh())
	bRepMeshes.forEach(mesh => {
		mesh.computeWireframeFromFlatTriangles('wireframe')
		mesh.computeNormalLines(0.1, 'normallines')
		mesh.compile()
	})

	if (g.mesh) {
		console.log('mesh/es from GET', bRepMeshes)
		meshes = g.mesh instanceof Array ? g.mesh : [g.mesh]
		meshes.forEach(mesh => {
			mesh.computeWireframeFromFlatTriangles('wireframe')
			mesh.computeNormalLines(0.1, 'normallines')
			mesh.compile()
		})
	}

	if (g.edges) {
		console.log('edges from GET')
		edgesMesh = new Mesh()
			.addIndexBuffer('TRIANGLES')
			.addVertexBuffer('normals', 'ts_Normal')
			.addVertexBuffer('curve1', 'curve1')
			.addVertexBuffer('curve1colors', 'curve1colors')
		g.edges.forEach((edge, edgeIndex) => {
			const points = edge.points()
			for (let i = 0; i < points.length - 1; i++) {
				const color = edgeViewerColors[(edgeIndex + i % 2) % edgeViewerColors.length]
				// const tangent = edge.tangentAt(i)
				// dMesh.curve1.push(points[i], points[i].plus(tangent.toLength(1)))
				edgesMesh.curve1.push(points[i], points[i + 1])
				edgesMesh.curve1colors.push(color, color)
			}
			edge.curve instanceof PICurve && (edge.curve as PICurve).addToMesh(edgesMesh, 8, 0.02, 2)

			if (edge.curve.debugInfo) {
				const { points, lines } = edge.curve.debugInfo()
				points && edgeDebugPoints.push(...points)
				lines && edgeDebugLines.push(...lines)
			}
		})
		//dMesh.computeWireframeFromFlatTriangles()
		edgesMesh.compile()
	}
	if (g.face) {
		if (!g.face.length) {
			g.face = [g.face] as any
		}
		faceMesh = new Mesh()
			.addIndexBuffer('TRIANGLES')
			.addIndexBuffer('LINES')
			.addVertexBuffer('tangents', 'tangents')
			.addVertexBuffer('normals', 'ts_Normal')
		for (const face of g.face) {
			face.addToMesh(faceMesh)
			for (const edge of face.allEdges) {
				const ts = edge.curve.calcSegmentTs(edge.aT, edge.bT, edge.reversed, true)
				for (const t of ts) {
					const p = edge.curve.at(t)
					faceMesh.tangents.push(p, p.plus(edge.tangentAt(t)))
				}
			}
		}
		faceMesh.compile()
	}

	g.drPs.push()
}


function viewerPaint(time: int, gl: BREPGLContext) {
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
	gl.loadIdentity()

	//setupCamera(eye, gl)

	gl.drawVectors(g.drVs, 4 / eye.zoomFactor)

	g.drPs.forEach(info =>
		gl.drawPoint(
			info instanceof V3 ? info : info.p,
			info instanceof V3 || !info.color ? chroma('#cc0000').gl() : chroma(info.color).gl(),
			6 / eye.zoomFactor,
		),
	)
	drawPlanes.forEach(plane => gl.drawPlane(plane, plane.color, hovering == plane))
	g.planes.forEach(plane => gl.drawPlane(plane, plane.color, hovering == plane))

	g.boxes.forEach(m4 => gl.drawBox(m4))

	gl.shaders.lighting.uniforms({ camPos: eye.pos })
	for (let i = 0; i < bRepMeshes.length; i++) {
		const mesh = bRepMeshes[i]
		gl.pushMatrix()
		//gl.translate(30, 0, 0)
		gl.projectionMatrix.m[11] -= 1 / (1 << 20) // prevent Z-fighting
		g.paintWireframe &&
			mesh.indexBuffers.wireframe &&
			gl.shaders.singleColor
				.uniforms({ color: COLORS.TS_STROKE.gl() })
				.drawBuffers(mesh.vertexBuffers, mesh.indexBuffers.wireframe, gl.LINES)
		g.paintMeshNormals &&
			mesh.indexBuffers.normallines &&
			gl.shaders.singleColor
				.uniforms({ color: COLORS.TS_STROKE.gl() })
				.drawBuffers(mesh.vertexBuffers, mesh.indexBuffers.normallines, gl.LINES)
		gl.shaders.singleColor
			.uniforms({ color: COLORS.TS_STROKE.gl() })
			.drawBuffers(mesh.vertexBuffers, mesh.indexBuffers.LINES, gl.LINES)
		gl.projectionMatrix.m[11] += 1 / (1 << 20)

		let faceIndex = bReps[i].faces.length
		while (faceIndex--) {
			const face = bReps[i].faces[faceIndex]
			const faceTriangleIndexes = mesh.faceIndexes.get(face)
			gl.shaders.lighting
				.uniforms({
					color:
						hovering == face
							? brepMeshColors
									.emod(i)
									.emod(faceIndex)
									.darken(2)
									.gl()
							: brepMeshColorssGL.emod(i).emod(faceIndex),
				})
				.draw(mesh, gl.TRIANGLES, faceTriangleIndexes.start, faceTriangleIndexes.count)
		}

		gl.popMatrix()
	}

	if (faceMesh) {
		gl.shaders.singleColor
			.uniforms({ color: chroma('red').gl() })
			.drawBuffers({ ts_Vertex: faceMesh.vertexBuffers.tangents }, undefined, gl.LINES)
	}

	for (let i = 0; i < meshes.length; i++) {
		const mesh = meshes[i]
		gl.pushMatrix()
		gl.projectionMatrix.m[11] -= 1 / (1 << 20) // prevent Z-fighting
		g.paintWireframe &&
			mesh.indexBuffers.wireframe &&
			gl.shaders.singleColor
				.uniforms({ color: COLORS.TS_STROKE.gl() })
				.drawBuffers(mesh.vertexBuffers, mesh.indexBuffers.wireframe, gl.LINES)
		g.paintMeshNormals &&
			mesh.indexBuffers.normallines &&
			gl.shaders.singleColor
				.uniforms({ color: COLORS.TS_STROKE.gl() })
				.drawBuffers(mesh.vertexBuffers, mesh.indexBuffers.normallines, gl.LINES)
		gl.projectionMatrix.m[11] += 1 / (1 << 20)
		mesh.TRIANGLES &&
			gl.shaders.lighting
				.uniforms({
					color: meshColorsGL.emod(i),
					camPos: eye.pos,
				})
				.draw(mesh)
		gl.popMatrix()
	}

	if (hovering instanceof Edge) {
		gl.projectionMatrix.m[11] -= 1 / (1 << 20) // prevent Z-fighting
		gl.drawEdge(hovering, GL_COLOR_BLACK, 2 / eye.zoomFactor)
		gl.projectionMatrix.m[11] += 1 / (1 << 20)
	}
	g.edges.forEach((e, i) => gl.drawEdge(e, edgeViewerColors.emod(i), 3 / eye.zoomFactor))

	if (g.paintCurveDebug) {
		gl.begin(gl.LINES)
		gl.color('red')
		edgeDebugLines.forEach(x => gl.vertex(x))
		gl.end()
		edgeDebugPoints.forEach(p => gl.drawPoint(p, chroma('red').gl(), 6 / eye.zoomFactor))
	}
	if (0 !== g.drLines.length) {
		gl.begin(gl.LINES)
		g.drLines.forEach(x => {
			gl.color((x as any).color || 'red')
			gl.vertex(x)
		})
		gl.end()
	}
}


function getHovering(
	mouseLine: L3,
	faces: Face[],
	planes: CustomPlane[],
	points: V3[],
	edges: Edge[],
	mindist: number,
	...consider: ('faces' | 'planes' | 'sketchElements' | 'points' | 'edges' | 'features')[]
): any {
	let hoverHighlight = null,
		nearest = Infinity

	const checkFeatures = consider.includes('features')
	assert(!checkFeatures || !consider.includes('faces'))

	function checkEl(el: any, distance: number) {
		if (distance < nearest) {
			nearest = distance
			hoverHighlight = el
		}
	}

	if (faces && (consider.includes('faces') || consider.includes('features'))) {
		for (const face of faces) {
			checkEl(checkFeatures ? face.info.feature : face, face.intersectsLine(mouseLine))
		}
	}
	if (planes && consider.includes('planes')) {
		for (const plane of planes) {
			checkEl(plane, plane.distanceTo(mouseLine, mindist))
		}
	}
	if (consider.includes('points')) {
		for (const p of points) {
			const t = mouseLine.pointT(p)
			if (mouseLine.at(t).distanceTo(p) < mindist * 1.2) {
				checkEl(p, t - 0.1)
			}
		}
	}
	if (consider.includes('edges')) {
		const projPlane = new P3(mouseLine.dir1, 0)
		const projPoint = projPlane.projectedPoint(mouseLine.anchor)
		for (const edge of edges) {
			const curve = edge.curve
			const prio = 0.05
			if (curve instanceof L3 && curve.dir1.isParallelTo(mouseLine.dir1)) {
				const d = mouseLine.distanceToPoint(edge.a)
				const t = mouseLine.pointT(edge.a)

				if (d < mindist) {
					checkEl(edge, t - prio)
				}
			} else {
				if (!(curve instanceof ImplicitCurve)) {
					const projCurve = curve.project(projPlane)
					const curveT = edge.clampedT(projCurve.closestTToPoint(projPoint))
					const p = curve.at(curveT)
					const t = mouseLine.pointT(p)
					if (projCurve.at(curveT).distanceTo(projPoint) < mindist) {
						checkEl(edge, t - prio)
					}
				}
			}
		}
	}

	return hoverHighlight
}


function checkEl(el: any, distance: number) {
		if (distance < nearest) {
			nearest = distance
			hoverHighlight = el
		}
	}


function initInfoEvents(paintScreen: () => {}, gl: BREPGLContext) {
	gl.canvas.addEventListener('mousemove', function(e) {
		const mouseLine = getMouseLine({ x: e.clientX, y: e.clientY }, gl)
		const faces = bReps.flatMap(b2 => b2 && b2.faces)
		const testEdges: Edge[] = [
			...bReps.flatMap(b2 => Array.from<Edge>(b2.buildAdjacencies().edgeFaces.keys())),
			...g.edges,
		]
		hovering = getHovering(mouseLine, faces, undefined, [], testEdges, 0.1, 'faces', 'edges')
		//let html = '', pp
		//if (hovering instanceof Edge) {
		//	pp = V(e.clientX, e.clientY)
		//	defaultRoundFunction = x => round10(x, -3)
		//	html = hovering.toString(x => round10(x, -3)) + ' length=' + hovering.length().toFixed(3)
		//} else if (hovering instanceof Face) {
		//	pp = V(e.clientX, e.clientY)
		//	defaultRoundFunction = x => round10(x, -3)
		//   let area
		//   try { area = hovering.calcArea() } catch (e) {}
		//	html = `face surface=${hovering.surface.constructor.name} edges=${hovering.contour.length} area=${area}`
		//}
		//if (pp) {
		//	//const pSC = gl.projectionMatrix.times(gl.modelViewMatrix).transformPoint(pp)
		//	//const x = (pSC.x * 0.5 + 0.5) * window.innerWidth, y = (-pSC.y * 0.5 + 0.5) * window.innerHeight
		//	tooltipShow(html, pp.x, pp.y)
		//} else {
		//	tooltipHide()
		//}
		paintScreen()
	})
}


export async function viewerMain() {
	const meshNormalsCheckbox = document.getElementById('paint-mesh-normals')
	meshNormalsCheckbox.onclick = e => {
		g.paintMeshNormals = !g.paintMeshNormals
		paintScreen()
	}

	const wireframeCheckbox = document.getElementById('paint-wireframe')
	wireframeCheckbox.onclick = e => {
		g.paintWireframe = !g.paintWireframe
		paintScreen()
	}

	const paintDebugCheckbox = document.getElementById('paint-curvedebug')
	paintDebugCheckbox.onclick = e => {
		g.paintCurveDebug = !g.paintCurveDebug
		paintScreen()
	}

	paintScreen = () => requestAnimationFrame(t => viewerPaint(t, gl))
	// B2T.defaultFont = await B2T.loadFont(BREPTS_ROOT + '/fonts/FiraSansMedium.woff')
	window.onerror = function(errorMsg, url, lineNumber, column, errorObj) {
		console.log(errorMsg, url, lineNumber, column, errorObj)
	}
	window.onpopstate = function(e) {
		const hash = window.location.search.substr(1) || window.location.hash.substr(1) || ''
		const command = decodeURIComponent(hash)
		const hashContext = new Function(
			`let ${renderObjectKeys.join(',')};${command};return{${renderObjectKeys.join(',')}}`,
		)() as RenderObjects

		Object.assign(eye, hashContext.i)
		setupCamera(eye, gl, true)
		paintScreen()
	}
	const gl = BREPGLContext.create(
		TSGLContext.create({ canvas: document.getElementById('testcanvas') as HTMLCanvasElement }),
	)
	gl.fullscreen()
	gl.canvas.oncontextmenu = () => false

	setupCamera(eye, gl)
	//gl.cullFace(gl.FRONT_AND_BACK);
	gl.clearColor(1.0, 1.0, 1.0, 0.0)
	gl.enable(gl.BLEND)
	gl.enable(gl.DEPTH_TEST)
	gl.enable(gl.CULL_FACE)
	gl.depthFunc(gl.LEQUAL)
	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) // TODO ?!

	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
	gl.loadIdentity()
	gl.scale(10, 10, 10)

	gl.loadIdentity()
	;(window as any).gl = gl

	initNavigationEvents(gl, eye, paintScreen)
	cameraChangeListeners.push(
		debounce(function(eye) {
			const round = (x: number) => round10(x, -3)
			const roundedEye = {
				pos: eye.pos.map(round),
				focus: eye.focus.map(round),
				up: eye.up.map(round),
				zoomFactor: round(eye.zoomFactor),
			}
			const iSource = 'i=' + roundedEye.toSource().replace(/[\n\r\s]+|^\(|\)$/g, '')
			const hash = window.location.hash.substr(1) || iSource
			const result = hash.match(/i=\{[^}]*\}/) ? hash.replace(/i=\{[^}]*\}/, iSource) : hash + ';' + iSource
			window.history.pushState(undefined, undefined, '#' + result)
		}, 500),
	)
	// initInfoEvents(paintScreen, g l)
	//initToolTips() // hide tooltip on mouseover
	//initPointInfoEvents()
	initBRep()
	setupCamera(eye, gl)
	paintScreen()
}


export function alignX(dir: number) {
	eye.pos = eye.focus.plus(V(100 * dir, 0, 0))
	eye.up = V3.Z
	setupCamera(eye, (window as any).gl)
	paintScreen()
}


export function alignY(dir: number) {
	eye.pos = eye.focus.plus(V(0, 100 * dir, 0))
	eye.up = V3.Z
	setupCamera(eye, (window as any).gl)
	paintScreen()
}


export function alignZ(dir: number) {
	eye.pos = eye.focus.plus(V(0, 0, 100 * dir))
	eye.up = eye.pos.cross(V3.X).unit()
	setupCamera(eye, (window as any).gl)
	paintScreen()
}


export function rot(angleInDeg: number) {
	eye.up = M4.rotateLine(eye.pos, eye.pos.to(eye.focus), angleInDeg * DEG).transformVector(eye.up)
	setupCamera(eye, (window as any).gl)
	paintScreen()
}


c => chroma(c).gl()


(to: any, from: any) =>
	Object.keys(from).forEach(key => 'Buffer' != key && !to[key] && (to[key] = from[key]))


key => 'Buffer' != key && !to[key] && (to[key] = from[key])


<T extends string>(x: T[]): T[] => x


k => {
		if (g[k]) {
			bReps.push(g[k])
		}
	}


bRep => bRep.toMesh()


mesh => {
		mesh.computeWireframeFromFlatTriangles('wireframe')
		mesh.computeNormalLines(0.1, 'normallines')
		mesh.compile()
	}


mesh => {
			mesh.computeWireframeFromFlatTriangles('wireframe')
			mesh.computeNormalLines(0.1, 'normallines')
			mesh.compile()
		}


(edge, edgeIndex) => {
			const points = edge.points()
			for (let i = 0; i < points.length - 1; i++) {
				const color = edgeViewerColors[(edgeIndex + i % 2) % edgeViewerColors.length]
				// const tangent = edge.tangentAt(i)
				// dMesh.curve1.push(points[i], points[i].plus(tangent.toLength(1)))
				edgesMesh.curve1.push(points[i], points[i + 1])
				edgesMesh.curve1colors.push(color, color)
			}
			edge.curve instanceof PICurve && (edge.curve as PICurve).addToMesh(edgesMesh, 8, 0.02, 2)

			if (edge.curve.debugInfo) {
				const { points, lines } = edge.curve.debugInfo()
				points && edgeDebugPoints.push(...points)
				lines && edgeDebugLines.push(...lines)
			}
		}


scale => scale.mode('lab').colors(20, null as 'alpha')


cs => cs.map(c => c.gl())


c => c.gl()


info =>
		gl.drawPoint(
			info instanceof V3 ? info : info.p,
			info instanceof V3 || !info.color ? chroma('#cc0000').gl() : chroma(info.color).gl(),
			6 / eye.zoomFactor,
		)


plane => gl.drawPlane(plane, plane.color, hovering == plane)


m4 => gl.drawBox(m4)


(e, i) => gl.drawEdge(e, edgeViewerColors.emod(i), 3 / eye.zoomFactor)


x => gl.vertex(x)


p => gl.drawPoint(p, chroma('red').gl(), 6 / eye.zoomFactor)


x => {
			gl.color((x as any).color || 'red')
			gl.vertex(x)
		}


b2 => b2 && b2.faces


b2 => Array.from<Edge>(b2.buildAdjacencies().edgeFaces.keys())


e => {
		g.paintMeshNormals = !g.paintMeshNormals
		paintScreen()
	}


e => {
		g.paintWireframe = !g.paintWireframe
		paintScreen()
	}


e => {
		g.paintCurveDebug = !g.paintCurveDebug
		paintScreen()
	}


() => requestAnimationFrame(t => viewerPaint(t, gl))


t => viewerPaint(t, gl)


(x: number) => round10(x, -3)


// tslint:disable-next-line:class-name
export class RenderObjects {
	a: BRep = undefined
	b: BRep = undefined
	c: BRep = undefined
	d: BRep = undefined
	face: Face[]
	edges: Edge[] = []
	wireframe: boolean = false
	normallines: boolean = false
	i: any = undefined
	hjk: any = undefined
	drPs: (V3 | { p: V3; info?: string; color?: string })[] = []
	drVs: { v: V3; anchor: V3; color?: GL_COLOR }[] = []
	drLines: V3[] = []
	mesh: (Mesh & { TRIANGLES: int[]; normals: V3[] })[] = []
	boxes: M4[] = []
	paintMeshNormals = false
	paintWireframe = false
	paintCurveDebug = false
	planes: CustomPlane[] = []
}


interface Window extends RenderObjects {}


event => this.model = event


error =>  this.errorMessage = <any>error


@Component({
  selector: 'event-form',
  templateUrl: 'src/pages/event-form.component.html',
  providers: [EventFormServiceComponent]
})

export class EventFormComponent {
  constructor(
    private _eventService: EventFormServiceComponent
  ){}

  public model: Event = {name: '', id: 1 };
  public errorMessage: string;

  newEvent() {
    this._eventService.addEvent(this.model.name)
      .subscribe(
        event => this.model = event,
        error =>  this.errorMessage = <any>error
      );
    window.history.back();
  }

  onKey(event: any) {
    this.model.name = event.target.value;
  }
}


newEvent() {
    this._eventService.addEvent(this.model.name)
      .subscribe(
        event => this.model = event,
        error =>  this.errorMessage = <any>error
      );
    window.history.back();
  }


onKey(event: any) {
    this.model.name = event.target.value;
  }


(fun) => (...args) =>
  mayAsync(
    () => fun(...args),
    (err) => {
      throw Error(err.message, { code: err.code, cause: err })
    }
  )


(...args) =>
  mayAsync(
    () => fun(...args),
    (err) => {
      throw Error(err.message, { code: err.code, cause: err })
    }
  )


() => fun(...args)


(err) => {
      throw Error(err.message, { code: err.code, cause: err })
    }


type Errorify = <A extends UnknownArgs, R extends UnknownReturns>(fun: AsyncFun<A, R>) => (...args: A) => Promise<R>


() => {
  return
LOADER

}


export interface LoaderProps {}


n => { this.comment = n.getStringValue(); }


n => { this.message = n.getObjectValue<Message>(createMessageFromDiscriminatorValue); }


n => { this.toRecipients = n.getCollectionOfObjectValues<Recipient>(createRecipientFromDiscriminatorValue); }


/** Provides operations to call the createForward method. */
export class CreateForwardPostRequestBody implements AdditionalDataHolder, Parsable {
    /** Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. */
    private _additionalData: Record<string, unknown>;
    /** The Comment property */
    private _comment?: string | undefined;
    /** The Message property */
    private _message?: Message | undefined;
    /** The ToRecipients property */
    private _toRecipients?: Recipient[] | undefined;
    /**
     * Gets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
     * @returns a Record<string, unknown>
     */
    public get additionalData() {
        return this._additionalData;
    };
    /**
     * Sets the additionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
     * @param value Value to set for the AdditionalData property.
     */
    public set additionalData(value: Record<string, unknown>) {
        this._additionalData = value;
    };
    /**
     * Gets the comment property value. The Comment property
     * @returns a string
     */
    public get comment() {
        return this._comment;
    };
    /**
     * Sets the comment property value. The Comment property
     * @param value Value to set for the Comment property.
     */
    public set comment(value: string | undefined) {
        this._comment = value;
    };
    /**
     * Instantiates a new createForwardPostRequestBody and sets the default values.
     */
    public constructor() {
        this._additionalData = {};
    };
    /**
     * The deserialization information for the current model
     * @returns a Record<string, (node: ParseNode) => void>
     */
    public getFieldDeserializers() : Record<string, (node: ParseNode) => void> {
        return {
            "comment": n => { this.comment = n.getStringValue(); },
            "message": n => { this.message = n.getObjectValue<Message>(createMessageFromDiscriminatorValue); },
            "toRecipients": n => { this.toRecipients = n.getCollectionOfObjectValues<Recipient>(createRecipientFromDiscriminatorValue); },
        };
    };
    /**
     * Gets the message property value. The Message property
     * @returns a message
     */
    public get message() {
        return this._message;
    };
    /**
     * Sets the message property value. The Message property
     * @param value Value to set for the Message property.
     */
    public set message(value: Message | undefined) {
        this._message = value;
    };
    /**
     * Serializes information the current object
     * @param writer Serialization writer to use to serialize this model
     */
    public serialize(writer: SerializationWriter) : void {
        if(!writer) throw new Error("writer cannot be undefined");
        writer.writeStringValue("comment", this.comment);
        writer.writeObjectValue<Message>("message", this.message);
        writer.writeCollectionOfObjectValues<Recipient>("toRecipients", this.toRecipients);
        writer.writeAdditionalData(this.additionalData);
    };
    /**
     * Gets the toRecipients property value. The ToRecipients property
     * @returns a recipient
     */
    public get toRecipients() {
        return this._toRecipients;
    };
    /**
     * Sets the toRecipients property value. The ToRecipients property
     * @param value Value to set for the ToRecipients property.
     */
    public set toRecipients(value: Recipient[] | undefined) {
        this._toRecipients = value;
    };
}


/**
     * The deserialization information for the current model
     * @returns a Record<string, (node: ParseNode) => void>
     */
public getFieldDeserializers() : Record<string, (node: ParseNode) => void> {
        return {
            "comment": n => { this.comment = n.getStringValue(); },
            "message": n => { this.message = n.getObjectValue<Message>(createMessageFromDiscriminatorValue); },
            "toRecipients": n => { this.toRecipients = n.getCollectionOfObjectValues<Recipient>(createRecipientFromDiscriminatorValue); },
        };
    }


/**
     * Serializes information the current object
     * @param writer Serialization writer to use to serialize this model
     */
public serialize(writer: SerializationWriter) : void {
        if(!writer) throw new Error("writer cannot be undefined");
        writer.writeStringValue("comment", this.comment);
        writer.writeObjectValue<Message>("message", this.message);
        writer.writeCollectionOfObjectValues<Recipient>("toRecipients", this.toRecipients);
        writer.writeAdditionalData(this.additionalData);
    }


export class AutoFollowedCluster {
  cluster_name: Name
  last_seen_metadata_version: VersionNumber
  time_since_last_check_millis: DateString
}


(args: ProductCardProps) => {
  return (
    <MemoryRouter>
      <Wrapper>
        <ProductCard
          {...args}
          onAddToCartClick={action('add-to-cart-button-clicked')}
          onWishlistClick={action('wishlist-button-clicked')}
        />
      </Wrapper>
    </MemoryRouter>


(args: ProductSliderProps) => {
  return (
    <MemoryRouter>
      <Wrapper style={{ maxWidth: '58rem' }}


action('add-to-cart-button-clicked')


action('wishlist-button-clicked')


(success, failure) => {
  Theme();
  Plugin();

  const cAssertHasDraft = (expected: boolean) => Chain.op((editor: Editor) => {
    RawAssertions.assertEq(`should${!expected ? 'n\'t' : ''} have draft`, expected, editor.plugins.autosave.hasDraft());
  });

  const cStoreDraft = Chain.op((editor: Editor) => {
    editor.plugins.autosave.storeDraft();
  });

  const cRemoveDraft = Chain.op((editor: Editor) => {
    editor.plugins.autosave.removeDraft();
  });

  const cAddUndoLevel = Chain.op((editor: Editor) => {
    editor.undoManager.add();
  });

  const testingPrefix = Math.random().toString(36).substring(7);
  Pipeline.async({}, [
    Logger.t('should restore draft when empty with setting', Chain.asStep({}, [
      McEditor.cFromSettings({ skin_url: '/project/js/tinymce/skins/lightgray', plugins: 'autosave', autosave_prefix: testingPrefix }),
      cAssertHasDraft(false),
      ApiChains.cSetContent('
X
'),
      cAddUndoLevel,
      cStoreDraft,
      cAssertHasDraft(true),
      McEditor.cRemove,
      McEditor.cFromSettings({ autosave_restore_when_empty: true, skin_url: '/project/js/tinymce/skins/lightgray', plugins: 'autosave', autosave_prefix: testingPrefix }),
      cAssertHasDraft(true),
      ApiChains.cAssertContent('
X
'),
      cRemoveDraft,
      McEditor.cRemove
    ])),
    Logger.t('shouldn\'t restore draft when empty without setting', Chain.asStep({}, [
      McEditor.cFromSettings({ skin_url: '/project/js/tinymce/skins/lightgray', plugins: 'autosave', autosave_prefix: testingPrefix }),
      cAssertHasDraft(false),
      ApiChains.cSetContent('
X
'),
      cAddUndoLevel,
      cStoreDraft,
      cAssertHasDraft(true),
      McEditor.cRemove,
      McEditor.cFromSettings({ skin_url: '/project/js/tinymce/skins/lightgray', plugins: 'autosave', autosave_prefix: testingPrefix }),
      cAssertHasDraft(true),
      ApiChains.cAssertContent(''),
      cRemoveDraft,
      McEditor.cRemove
    ]))
  ], () => success(), failure);
}


(expected: boolean) => Chain.op((editor: Editor) => {
    RawAssertions.assertEq(`should${!expected ? 'n\'t' : ''} have draft`, expected, editor.plugins.autosave.hasDraft());
  })


(editor: Editor) => {
    RawAssertions.assertEq(`should${!expected ? 'n\'t' : ''} have draft`, expected, editor.plugins.autosave.hasDraft());
  }


(editor: Editor) => {
    editor.plugins.autosave.storeDraft();
  }


(editor: Editor) => {
    editor.plugins.autosave.removeDraft();
  }


(editor: Editor) => {
    editor.undoManager.add();
  }


() => success()


(
  state: ISourcecastWorkspace,
  action: ActionType<typeof actions>
) => {
  switch (action.type) {
    case SAVE_SOURCECAST_DATA:
      return {
        ...state,
        title: action.payload.title,
        description: action.payload.description,
        audioUrl: action.payload.audioUrl,
        playbackData: action.payload.playbackData
      };
    case SET_CODE_DELTAS_TO_APPLY:
      return {
        ...state,
        codeDeltasToApply: action.payload.deltas
      };
    case SET_INPUT_TO_APPLY:
      return {
        ...state,
        inputToApply: action.payload.inputToApply
      };
    case SET_SOURCECAST_DATA:
      return {
        ...state,
        title: action.payload.title,
        description: action.payload.description,
        audioUrl: action.payload.audioUrl,
        playbackData: action.payload.playbackData
      };
    case SET_SOURCECAST_PLAYBACK_DURATION:
      return {
        ...state,
        playbackDuration: action.payload.duration
      };
    case SET_SOURCECAST_PLAYBACK_STATUS:
      return {
        ...state,
        playbackStatus: action.payload.playbackStatus
      };
    case UPDATE_SOURCECAST_INDEX:
      return {
        ...state,
        sourcecastIndex: action.payload.index
      };
    default:
      return state;
  }
}


target => {
      const io = new IntersectionObserver((entries , observer) => {

        entries.forEach( entry => {
          console.log('chika');

          if(entry.isIntersecting) {
            const img = entry.target;
            const src = img.getAttribute('data-lazy');

            img.setAttribute('src',src);

            observer.disconnect();
          }

        })
      });

      io.observe(target);
    }


(entries , observer) => {

        entries.forEach( entry => {
          console.log('chika');

          if(entry.isIntersecting) {
            const img = entry.target;
            const src = img.getAttribute('data-lazy');

            img.setAttribute('src',src);

            observer.disconnect();
          }

        })
      }


entry => {
          console.log('chika');

          if(entry.isIntersecting) {
            const img = entry.target;
            const src = img.getAttribute('data-lazy');

            img.setAttribute('src',src);

            observer.disconnect();
          }

        }


@Component({
  selector: 'app-post8-fit-college-student',
  templateUrl: './post8-fit-college-student.component.html',
  styleUrls: ['./post8-fit-college-student.component.css']
})
export class Post8FitCollegeStudentComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    const targets = document.querySelectorAll('img');

    const lazyload = target => {
      const io = new IntersectionObserver((entries , observer) => {

        entries.forEach( entry => {
          console.log('chika');

          if(entry.isIntersecting) {
            const img = entry.target;
            const src = img.getAttribute('data-lazy');

            img.setAttribute('src',src);

            observer.disconnect();
          }

        })
      });

      io.observe(target);
    };

    targets.forEach(lazyload);

  }

}


ngOnInit() {
    const targets = document.querySelectorAll('img');

    const lazyload = target => {
      const io = new IntersectionObserver((entries , observer) => {

        entries.forEach( entry => {
          console.log('chika');

          if(entry.isIntersecting) {
            const img = entry.target;
            const src = img.getAttribute('data-lazy');

            img.setAttribute('src',src);

            observer.disconnect();
          }

        })
      });

      io.observe(target);
    };

    targets.forEach(lazyload);

  }


() => {
  it('skal vise beregningsgrunnlaget når en ikke har overstyrt', () => {
    const beregningResultat = {
      beregnetTilkjentYtelse: 120000,
      satsVerdi: 60000,
      antallBarn: 2,
    };

    const wrapper = shallowWithIntl(<BeregningsresultatEngangsstonadFormImpl
      {...reduxFormPropsMock}
      intl={intlMock}
      behandlingResultatstruktur={beregningResultat}
      aksjonspunktCodes={[]}
      isOverstyrt={false}
      kanOverstyre
      toggleOverstyring={sinon.spy()}
    />);

    const texts = wrapper.find(Element);

    expect(texts).to.have.length(3);
    expect(texts.first().childAt(0).text()).to.eql('60 000 kr');
    expect(texts.at(1).childAt(0).text()).to.eql('2');
    expect(texts.last().childAt(0).text()).to.eql('120 000 kr');
  });

  it('skal ved valg av overstyr vise tekstfelter', () => {
    const beregningResultat = {
      beregnetTilkjentYtelse: 120000,
      satsVerdi: 60000,
      antallBarn: 2,
    };

    const wrapper = shallowWithIntl(<BeregningsresultatEngangsstonadFormImpl
      {...reduxFormPropsMock}
      intl={intlMock}
      behandlingResultatstruktur={beregningResultat}
      aksjonspunktCodes={[]}
      isOverstyrt={false}
      kanOverstyre
      toggleOverstyring={sinon.spy()}
    />);

    expect(wrapper.find(OverstyringPanel)).to.have.length(0);

    wrapper.find(OverstyringKnapp).prop('onClick')();

    expect(wrapper.find(OverstyringPanel)).to.have.length(1);
  });
}


() => {
    const beregningResultat = {
      beregnetTilkjentYtelse: 120000,
      satsVerdi: 60000,
      antallBarn: 2,
    };

    const wrapper = shallowWithIntl(<BeregningsresultatEngangsstonadFormImpl
      {...reduxFormPropsMock}
      intl={intlMock}
      behandlingResultatstruktur={beregningResultat}
      aksjonspunktCodes={[]}
      isOverstyrt={false}
      kanOverstyre
      toggleOverstyring={sinon.spy()}
    />);

    const texts = wrapper.find(Element);

    expect(texts).to.have.length(3);
    expect(texts.first().childAt(0).text()).to.eql('60 000 kr');
    expect(texts.at(1).childAt(0).text()).to.eql('2');
    expect(texts.last().childAt(0).text()).to.eql('120 000 kr');
  }


() => {
    const beregningResultat = {
      beregnetTilkjentYtelse: 120000,
      satsVerdi: 60000,
      antallBarn: 2,
    };

    const wrapper = shallowWithIntl(<BeregningsresultatEngangsstonadFormImpl
      {...reduxFormPropsMock}
      intl={intlMock}
      behandlingResultatstruktur={beregningResultat}
      aksjonspunktCodes={[]}
      isOverstyrt={false}
      kanOverstyre
      toggleOverstyring={sinon.spy()}
    />);

    expect(wrapper.find(OverstyringPanel)).to.have.length(0);

    wrapper.find(OverstyringKnapp).prop('onClick')();

    expect(wrapper.find(OverstyringPanel)).to.have.length(1);
  }


(x: number, y: number, z: number) => {
  return Math.sqrt(x * x + y * y + z * z);
}


export default function useOnDragStart(): () => void {
  return useDispatchRndDragStart();
}


(data) => {
      const msg = data.toString().trim();

      if (!this.clientAuthorized && msg && /^auth:(.*):(.*)$/.test(msg)) {
        const [auth, key, cert] = msg.split(':');

        this.log.debug(`Attempt client auth: ${auth} key=${key} cert=${cert}`);

        this.client
          .authorize(this, Buffer.from(key, 'base64'), Buffer.from(cert, 'base64'))
          .then((sig) => {
            if (sig) {
              // this.reg(sig);
              this.clientAuthorized = !!sig;
              // this.init();

              this.client.socket.on('data', (data) => {
                this.log.debug(`Nested client data: ${data}`);
                this.write(data);
              });

              this.client.socket.on('close', (hasError: boolean) => {
                this.log.debug(`Linked client exited.`, { hasError });
                this.exit('Connection closed.');
              });
            } else {
              this.write(`Invalid credentials\n`.red);
            }
          })
          .catch(this.log.error.bind(this));
      } else if (this.clientAuthorized) {
        this.log.debug(`Forward with authd client: ${data}`);
        this.client.write(data.toString());
      } else {
        this.log.debug(`Ignoring msg (not auth): ${msg}`);
      }
    }


(sig) => {
            if (sig) {
              // this.reg(sig);
              this.clientAuthorized = !!sig;
              // this.init();

              this.client.socket.on('data', (data) => {
                this.log.debug(`Nested client data: ${data}`);
                this.write(data);
              });

              this.client.socket.on('close', (hasError: boolean) => {
                this.log.debug(`Linked client exited.`, { hasError });
                this.exit('Connection closed.');
              });
            } else {
              this.write(`Invalid credentials\n`.red);
            }
          }


(data) => {
                this.log.debug(`Nested client data: ${data}`);
                this.write(data);
              }


(hasError: boolean) => {
                this.log.debug(`Linked client exited.`, { hasError });
                this.exit('Connection closed.');
              }


export class SockzWebClient extends SockzRelay implements ISockzClient {
  public client: SockzClient;

  constructor(public ctl: SockzController, public socket: WebSocket) {
    super(ctl, socket, ctl.prompt);

    this.client = this.ctl.startClient(true);
    this.init();

    this.socket.on('message', (data) => {
      const msg = data.toString().trim();

      if (!this.clientAuthorized && msg && /^auth:(.*):(.*)$/.test(msg)) {
        const [auth, key, cert] = msg.split(':');

        this.log.debug(`Attempt client auth: ${auth} key=${key} cert=${cert}`);

        this.client
          .authorize(this, Buffer.from(key, 'base64'), Buffer.from(cert, 'base64'))
          .then((sig) => {
            if (sig) {
              // this.reg(sig);
              this.clientAuthorized = !!sig;
              // this.init();

              this.client.socket.on('data', (data) => {
                this.log.debug(`Nested client data: ${data}`);
                this.write(data);
              });

              this.client.socket.on('close', (hasError: boolean) => {
                this.log.debug(`Linked client exited.`, { hasError });
                this.exit('Connection closed.');
              });
            } else {
              this.write(`Invalid credentials\n`.red);
            }
          })
          .catch(this.log.error.bind(this));
      } else if (this.clientAuthorized) {
        this.log.debug(`Forward with authd client: ${data}`);
        this.client.write(data.toString());
      } else {
        this.log.debug(`Ignoring msg (not auth): ${msg}`);
      }
    });
  }

  get docPath(): string {
    return `webClients/${this.id}`;
  }

  public init(): void {
    super.init();

    // this.on('message', this.data.bind(this));
    this.on('reset', this.showPrompt.bind(this));
    // this.on('message', (data) => {
    //   this.log.warn(`Received message: ${data}`);

    //   if (this.client) {
    //     this.client.data(data);
    //   }
    // });
  }

  public start(): void {
    // start web client?
  }
}


public init(): void {
    super.init();

    // this.on('message', this.data.bind(this));
    this.on('reset', this.showPrompt.bind(this));
    // this.on('message', (data) => {
    //   this.log.warn(`Received message: ${data}`);

    //   if (this.client) {
    //     this.client.data(data);
    //   }
    // });
  }


public start(): void {
    // start web client?
  }


(message: string, level: ColorName = 'success', params: IShowNotificationParameters = {}) => dispatch => {
    const {position, timeOut} = {...showNotificationDefaults, ...params} as IShowNotificationParameters;
    const id = ++ID_COUNTER;

    dispatch({ type: NOTIFICATIONS_SHOW, id, message, level, position});

    if(timeOut > 0){
        setTimeout(() => dispatch(closeNotification(id)), timeOut);
    }
}


dispatch => {
    const {position, timeOut} = {...showNotificationDefaults, ...params} as IShowNotificationParameters;
    const id = ++ID_COUNTER;

    dispatch({ type: NOTIFICATIONS_SHOW, id, message, level, position});

    if(timeOut > 0){
        setTimeout(() => dispatch(closeNotification(id)), timeOut);
    }
}


() => dispatch(closeNotification(id))


(id = null) => ({
    type: NOTIFICATIONS_CLOSE,
    id
})


flashes =>
    Object.keys(flashes).map((level: string) => {
        return []
            .concat(flashes[level] || [])
            .map(message => showNotification(message, level));
    })


(level: string) => {
        return []
            .concat(flashes[level] || [])
            .map(message => showNotification(message, level));
    }


message => showNotification(message, level)


export interface IShowNotificationParameters {
    position?: 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left' | string;
    timeOut?: number;
}


export default class ScriptFactory {}


@ccclass('AppMain')
export class AppMain extends Component {
    static CDNPath : string = `https://anseyuyin.github.io/wfc2D/`;

    async start() {
        //开启 动态合图
        macro.CLEANUP_IMAGE_CACHE = false;
        dynamicAtlasManager.enabled = true;
        let dirPath = `${AppMain.CDNPath}res/configs/`;
        let _dataUrl = `${dirPath}endlessMap.cfg.json`;
        let req = await WfcLoader.xhrLoad(_dataUrl, "json");
        let datas: { resName: string }[] = req.response;
        if (!datas || datas.length < 1) return;
        DownList.dataArr = datas as any;
        //开始 展示第一个
        var tile = this.node.scene.getComponentInChildren<TileMap>(TileMap);
        tile?.setRes(datas[0]);
    };
}


async start() {
        //开启 动态合图
        macro.CLEANUP_IMAGE_CACHE = false;
        dynamicAtlasManager.enabled = true;
        let dirPath = `${AppMain.CDNPath}res/configs/`;
        let _dataUrl = `${dirPath}endlessMap.cfg.json`;
        let req = await WfcLoader.xhrLoad(_dataUrl, "json");
        let datas: { resName: string }[] = req.response;
        if (!datas || datas.length < 1) return;
        DownList.dataArr = datas as any;
        //开始 展示第一个
        var tile = this.node.scene.getComponentInChildren<TileMap>(TileMap);
        tile?.setRes(datas[0]);
    }


export type ModuleProps = Readonly<ViewProps & {
}>;


export function createTextRangeWithKind<T extends SyntaxKind>(pos: number, end: number, kind: T): TextRangeWithKind<T> {
        const textRangeWithKind: TextRangeWithKind<T> = { pos, end, kind };
        if (Debug.isDebugging) {
            Object.defineProperty(textRangeWithKind, "__debugKind", {
                get: () => Debug.formatSyntaxKind(kind),
            });
        }
        return textRangeWithKind;
    }


export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
        const line = sourceFile.getLineAndCharacterOfPosition(position).line;
        if (line === 0) {
            return [];
        }
        // After the enter key, the cursor is now at a new line. The new line may or may not contain non-whitespace characters.
        // If the new line has only whitespaces, we won't want to format this line, because that would remove the indentation as
        // trailing whitespaces. So the end of the formatting span should be the later one between:
        //  1. the end of the previous line
        //  2. the last non-whitespace character in the current line
        let endOfFormatSpan = getEndLinePosition(line, sourceFile);
        while (isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(endOfFormatSpan))) {
            endOfFormatSpan--;
        }
        // if the character at the end of the span is a line break, we shouldn't include it, because it indicates we don't want to
        // touch the current line at all. Also, on some OSes the line break consists of two characters (\r\n), we should test if the
        // previous character before the end of format span is line break character as well.
        if (isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) {
            endOfFormatSpan--;
        }
        const span = {
            // get start position for the previous line
            pos: getStartPositionOfLine(line - 1, sourceFile),
            // end value is exclusive so add 1 to the result
            end: endOfFormatSpan + 1
        };
        return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatOnEnter);
    }


export function formatOnSemicolon(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
        const semicolon = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.SemicolonToken, sourceFile);
        return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, formatContext, FormattingRequestKind.FormatOnSemicolon);
    }


export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
        const openingCurly = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.OpenBraceToken, sourceFile);
        if (!openingCurly) {
            return [];
        }
        const curlyBraceRange = openingCurly.parent;
        const outermostNode = findOutermostNodeWithinListLevel(curlyBraceRange);

        /**
         * We limit the span to end at the opening curly to handle the case where
         * the brace matched to that just typed will be incorrect after further edits.
         * For example, we could type the opening curly for the following method
         * body without brace-matching activated:
         * ```
         * class C {
         *     foo()
         * }
         * ```
         * and we wouldn't want to move the closing brace.
         */
        const textRange: TextRange = {
            pos: getLineStartPositionForPosition(outermostNode!.getStart(sourceFile), sourceFile), // TODO: GH#18217
            end: position
        };

        return formatSpan(textRange, sourceFile, formatContext, FormattingRequestKind.FormatOnOpeningCurlyBrace);
    }


export function formatOnClosingCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
        const precedingToken = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.CloseBraceToken, sourceFile);
        return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, formatContext, FormattingRequestKind.FormatOnClosingCurlyBrace);
    }


export function formatDocument(sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
        const span = {
            pos: 0,
            end: sourceFile.text.length
        };
        return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatDocument);
    }


export function formatSelection(start: number, end: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
        // format from the beginning of the line
        const span = {
            pos: getLineStartPositionForPosition(start, sourceFile),
            end,
        };
        return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatSelection);
    }


/**
     * Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment).
     * @param expectedTokenKind The kind of the last token constituting the desired parent node.
     */
function findImmediatelyPrecedingTokenOfKind(end: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node | undefined {
        const precedingToken = findPrecedingToken(end, sourceFile);

        return precedingToken && precedingToken.kind === expectedTokenKind && end === precedingToken.getEnd() ?
            precedingToken :
            undefined;
    }


/**
     * Finds the highest node enclosing `node` at the same list level as `node`
     * and whose end does not exceed `node.end`.
     *
     * Consider typing the following
     * ```
     * let x = 1;
     * while (true) {
     * }
     * ```
     * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding
     * variable declaration.
     */
function findOutermostNodeWithinListLevel(node: Node | undefined) {
        let current = node;
        while (current &&
            current.parent &&
            current.parent.end === node!.end &&
            !isListElement(current.parent, current)) {
            current = current.parent;
        }

        return current;
    }


// Returns true if node is a element in some list in parent
// i.e. parent is class declaration with the list of members and node is one of members.
function isListElement(parent: Node, node: Node): boolean {
        switch (parent.kind) {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.InterfaceDeclaration:
                return rangeContainsRange((<InterfaceDeclaration>parent).members, node);
            case SyntaxKind.ModuleDeclaration:
                const body = (<ModuleDeclaration>parent).body;
                return !!body && body.kind === SyntaxKind.ModuleBlock && rangeContainsRange(body.statements, node);
            case SyntaxKind.SourceFile:
            case SyntaxKind.Block:
            case SyntaxKind.ModuleBlock:
                return rangeContainsRange((<Block>parent).statements, node);
            case SyntaxKind.CatchClause:
                return rangeContainsRange((<CatchClause>parent).block.statements, node);
        }

        return false;
    }


/** find node that fully contains given text range */
function findEnclosingNode(range: TextRange, sourceFile: SourceFile): Node {
        return find(sourceFile);

        function find(n: Node): Node {
            const candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c);
            if (candidate) {
                const result = find(candidate);
                if (result) {
                    return result;
                }
            }

            return n;
        }
    }


function find(n: Node): Node {
            const candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c);
            if (candidate) {
                const result = find(candidate);
                if (result) {
                    return result;
                }
            }

            return n;
        }


/** formatting is not applied to ranges that contain parse errors.
     * This function will return a predicate that for a given text range will tell
     * if there are any parse errors that overlap with the range.
     */
function prepareRangeContainsErrorFunction(errors: readonly Diagnostic[], originalRange: TextRange): (r: TextRange) => boolean {
        if (!errors.length) {
            return rangeHasNoErrors;
        }

        // pick only errors that fall in range
        const sorted = errors
            .filter(d => rangeOverlapsWithStartEnd(originalRange, d.start!, d.start! + d.length!)) // TODO: GH#18217
            .sort((e1, e2) => e1.start! - e2.start!);

        if (!sorted.length) {
            return rangeHasNoErrors;
        }

        let index = 0;

        return r => {
            // in current implementation sequence of arguments [r1, r2...] is monotonically increasing.
            // 'index' tracks the index of the most recent error that was checked.
            while (true) {
                if (index >= sorted.length) {
                    // all errors in the range were already checked -> no error in specified range
                    return false;
                }

                const error = sorted[index];
                if (r.end <= error.start!) {
                    // specified range ends before the error referred by 'index' - no error in range
                    return false;
                }

                if (startEndOverlapsWithStartEnd(r.pos, r.end, error.start!, error.start! + error.length!)) {
                    // specified range overlaps with error range
                    return true;
                }

                index++;
            }
        };

        function rangeHasNoErrors(): boolean {
            return false;
        }
    }


function rangeHasNoErrors(): boolean {
            return false;
        }


/**
     * Start of the original range might fall inside the comment - scanner will not yield appropriate results
     * This function will look for token that is located before the start of target range
     * and return its end as start position for the scanner.
     */
function getScanStartPosition(enclosingNode: Node, originalRange: TextRange, sourceFile: SourceFile): number {
        const start = enclosingNode.getStart(sourceFile);
        if (start === originalRange.pos && enclosingNode.end === originalRange.end) {
            return start;
        }

        const precedingToken = findPrecedingToken(originalRange.pos, sourceFile);
        if (!precedingToken) {
            // no preceding token found - start from the beginning of enclosing node
            return enclosingNode.pos;
        }

        // preceding token ends after the start of original range (i.e when originalRange.pos falls in the middle of literal)
        // start from the beginning of enclosingNode to handle the entire 'originalRange'
        if (precedingToken.end >= originalRange.pos) {
            return enclosingNode.pos;
        }

        return precedingToken.end;
    }


/*
     * For cases like
     * if (a ||
     *     b ||$
     *     c) {...}
     * If we hit Enter at $ we want line '    b ||' to be indented.
     * Formatting will be applied to the last two lines.
     * Node that fully encloses these lines is binary expression 'a ||...'.
     * Initial indentation for this node will be 0.
     * Binary expressions don't introduce new indentation scopes, however it is possible
     * that some parent node on the same line does - like if statement in this case.
     * Note that we are considering parents only from the same line with initial node -
     * if parent is on the different line - its delta was already contributed
     * to the initial indentation.
     */
function getOwnOrInheritedDelta(n: Node, options: FormatCodeSettings, sourceFile: SourceFile): number {
        let previousLine = Constants.Unknown;
        let child: Node | undefined;
        while (n) {
            const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line;
            if (previousLine !== Constants.Unknown && line !== previousLine) {
                break;
            }

            if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) {
                return options.indentSize!;
            }

            previousLine = line;
            child = n;
            n = n.parent;
        }
        return 0;
    }


export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] {
        const range = { pos: 0, end: sourceFileLike.text.length };
        return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker(
            range,
            node,
            initialIndentation,
            delta,
            scanner,
            formatContext,
            FormattingRequestKind.FormatSelection,
            _ => false, // assume that node does not have any errors
            sourceFileLike));
    }


function formatNodeLines(node: Node | undefined, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] {
        if (!node) {
            return [];
        }

        const span = {
            pos: getLineStartPositionForPosition(node.getStart(sourceFile), sourceFile),
            end: node.end
        };

        return formatSpan(span, sourceFile, formatContext, requestKind);
    }


function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] {
        // find the smallest node that fully wraps the range and compute the initial indentation for the node
        const enclosingNode = findEnclosingNode(originalRange, sourceFile);
        return getFormattingScanner(
            sourceFile.text,
            sourceFile.languageVariant,
            getScanStartPosition(enclosingNode, originalRange, sourceFile),
            originalRange.end,
            scanner => formatSpanWorker(
                originalRange,
                enclosingNode,
                SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options),
                getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile),
                scanner,
                formatContext,
                requestKind,
                prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange),
                sourceFile));
    }


// local functions
/** Tries to compute the indentation for a list element.
         * If list element is not in range then
         * function will pick its actual indentation
         * so it can be pushed downstream as inherited indentation.
         * If list element is in the range - its indentation will be equal
         * to inherited indentation from its predecessors.
         */
function tryComputeIndentationForListItem(startPos: number,
            endPos: number,
            parentStartLine: number,
            range: TextRange,
            inheritedIndentation: number): number {

            if (rangeOverlapsWithStartEnd(range, startPos, endPos) ||
                rangeContainsStartEnd(range, startPos, endPos) /* Not to miss zero-range nodes e.g. JsxText */) {

                if (inheritedIndentation !== Constants.Unknown) {
                    return inheritedIndentation;
                }
            }
            else {
                const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line;
                const startLinePosition = getLineStartPositionForPosition(startPos, sourceFile);
                const column = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, startPos, sourceFile, options);
                if (startLine !== parentStartLine || startPos === column) {
                    // Use the base indent size if it is greater than
                    // the indentation of the inherited predecessor.
                    const baseIndentSize = SmartIndenter.getBaseIndentation(options);
                    return baseIndentSize > column ? baseIndentSize : column;
                }
            }

            return Constants.Unknown;
        }


function computeIndentation(
            node: TextRangeWithKind,
            startLine: number,
            inheritedIndentation: number,
            parent: Node,
            parentDynamicIndentation: DynamicIndentation,
            effectiveParentStartLine: number
        ): { indentation: number, delta: number; } {
            const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0;

            if (effectiveParentStartLine === startLine) {
                // if node is located on the same line with the parent
                // - inherit indentation from the parent
                // - push children if either parent of node itself has non-zero delta
                return {
                    indentation: startLine === lastIndentedLine ? indentationOnLastIndentedLine : parentDynamicIndentation.getIndentation(),
                    delta: Math.min(options.indentSize!, parentDynamicIndentation.getDelta(node) + delta)
                };
            }
            else if (inheritedIndentation === Constants.Unknown) {
                if (node.kind === SyntaxKind.OpenParenToken && startLine === lastIndentedLine) {
                    // the is used for chaining methods formatting
                    // - we need to get the indentation on last line and the delta of parent
                    return { indentation: indentationOnLastIndentedLine, delta: parentDynamicIndentation.getDelta(node) };
                }
                else if (SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile)) {
                    return { indentation: parentDynamicIndentation.getIndentation(), delta };
                }
                else if (SmartIndenter.argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, sourceFile)) {
                    return { indentation: parentDynamicIndentation.getIndentation(), delta };
                }
                else {
                    return { indentation: parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta };
                }
            }
            else {
                return { indentation: inheritedIndentation, delta };
            }
        }


function getFirstNonDecoratorTokenOfNode(node: Node) {
            if (node.modifiers && node.modifiers.length) {
                return node.modifiers[0].kind;
            }
            switch (node.kind) {
                case SyntaxKind.ClassDeclaration: return SyntaxKind.ClassKeyword;
                case SyntaxKind.InterfaceDeclaration: return SyntaxKind.InterfaceKeyword;
                case SyntaxKind.FunctionDeclaration: return SyntaxKind.FunctionKeyword;
                case SyntaxKind.EnumDeclaration: return SyntaxKind.EnumDeclaration;
                case SyntaxKind.GetAccessor: return SyntaxKind.GetKeyword;
                case SyntaxKind.SetAccessor: return SyntaxKind.SetKeyword;
                case SyntaxKind.MethodDeclaration:
                    if ((<MethodDeclaration>node).asteriskToken) {
                        return SyntaxKind.AsteriskToken;
                    }
                    // falls through

                case SyntaxKind.PropertyDeclaration:
                case SyntaxKind.Parameter:
                    const name = getNameOfDeclaration(<Declaration>node);
                    if (name) {
                        return name.kind;
                    }
            }
        }


function getDynamicIndentation(node: Node, nodeStartLine: number, indentation: number, delta: number): DynamicIndentation {
            return {
                getIndentationForComment: (kind, tokenIndentation, container) => {
                    switch (kind) {
                        // preceding comment to the token that closes the indentation scope inherits the indentation from the scope
                        // ..  {
                        //     // comment
                        // }
                        case SyntaxKind.CloseBraceToken:
                        case SyntaxKind.CloseBracketToken:
                        case SyntaxKind.CloseParenToken:
                            return indentation + getDelta(container);
                    }
                    return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation;
                },
                // if list end token is LessThanToken '>' then its delta should be explicitly suppressed
                // so that LessThanToken as a binary operator can still be indented.
                // foo.then
                //     <
                //         number,
                //         string,
                //     >();
                // vs
                // var a = xValue
                //     > yValue;
                getIndentationForToken: (line, kind, container, suppressDelta) =>
                    !suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation,
                getIndentation: () => indentation,
                getDelta,
                recomputeIndentation: (lineAdded, parent) => {
                    if (SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) {
                        indentation += lineAdded ? options.indentSize! : -options.indentSize!;
                        delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0;
                    }
                }
            };

            function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean {
                switch (kind) {
                    // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent
                    case SyntaxKind.OpenBraceToken:
                    case SyntaxKind.CloseBraceToken:
                    case SyntaxKind.CloseParenToken:
                    case SyntaxKind.ElseKeyword:
                    case SyntaxKind.WhileKeyword:
                    case SyntaxKind.AtToken:
                        return false;
                    case SyntaxKind.SlashToken:
                    case SyntaxKind.GreaterThanToken:
                        switch (container.kind) {
                            case SyntaxKind.JsxOpeningElement:
                            case SyntaxKind.JsxClosingElement:
                            case SyntaxKind.JsxSelfClosingElement:
                                return false;
                        }
                        break;
                    case SyntaxKind.OpenBracketToken:
                    case SyntaxKind.CloseBracketToken:
                        if (container.kind !== SyntaxKind.MappedType) {
                            return false;
                        }
                        break;
                }
                // if token line equals to the line of containing node (this is a first token in the node) - use node indentation
                return nodeStartLine !== line
                    // if this token is the first token following the list of decorators, we do not need to indent
                    && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node));
            }

            function getDelta(child: TextRangeWithKind) {
                // Delta value should be zero when the node explicitly prevents indentation of the child node
                return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0;
            }
        }


function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean {
                switch (kind) {
                    // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent
                    case SyntaxKind.OpenBraceToken:
                    case SyntaxKind.CloseBraceToken:
                    case SyntaxKind.CloseParenToken:
                    case SyntaxKind.ElseKeyword:
                    case SyntaxKind.WhileKeyword:
                    case SyntaxKind.AtToken:
                        return false;
                    case SyntaxKind.SlashToken:
                    case SyntaxKind.GreaterThanToken:
                        switch (container.kind) {
                            case SyntaxKind.JsxOpeningElement:
                            case SyntaxKind.JsxClosingElement:
                            case SyntaxKind.JsxSelfClosingElement:
                                return false;
                        }
                        break;
                    case SyntaxKind.OpenBracketToken:
                    case SyntaxKind.CloseBracketToken:
                        if (container.kind !== SyntaxKind.MappedType) {
                            return false;
                        }
                        break;
                }
                // if token line equals to the line of containing node (this is a first token in the node) - use node indentation
                return nodeStartLine !== line
                    // if this token is the first token following the list of decorators, we do not need to indent
                    && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node));
            }


function getDelta(child: TextRangeWithKind) {
                // Delta value should be zero when the node explicitly prevents indentation of the child node
                return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0;
            }


function processChildNode(
                child: Node,
                inheritedIndentation: number,
                parent: Node,
                parentDynamicIndentation: DynamicIndentation,
                parentStartLine: number,
                undecoratedParentStartLine: number,
                isListItem: boolean,
                isFirstListItem?: boolean): number {

                const childStartPos = child.getStart(sourceFile);

                const childStartLine = sourceFile.getLineAndCharacterOfPosition(childStartPos).line;

                let undecoratedChildStartLine = childStartLine;
                if (child.decorators) {
                    undecoratedChildStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(child, sourceFile)).line;
                }

                // if child is a list item - try to get its indentation, only if parent is within the original range.
                let childIndentationAmount = Constants.Unknown;

                if (isListItem && rangeContainsRange(originalRange, parent)) {
                    childIndentationAmount = tryComputeIndentationForListItem(childStartPos, child.end, parentStartLine, originalRange, inheritedIndentation);
                    if (childIndentationAmount !== Constants.Unknown) {
                        inheritedIndentation = childIndentationAmount;
                    }
                }

                // child node is outside the target range - do not dive inside
                if (!rangeOverlapsWithStartEnd(originalRange, child.pos, child.end)) {
                    if (child.end < originalRange.pos) {
                        formattingScanner.skipToEndOf(child);
                    }
                    return inheritedIndentation;
                }

                if (child.getFullWidth() === 0) {
                    return inheritedIndentation;
                }

                while (formattingScanner.isOnToken()) {
                    // proceed any parent tokens that are located prior to child.getStart()
                    const tokenInfo = formattingScanner.readTokenInfo(node);
                    if (tokenInfo.token.end > childStartPos) {
                        // stop when formatting scanner advances past the beginning of the child
                        break;
                    }

                    consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, node);
                }

                if (!formattingScanner.isOnToken()) {
                    return inheritedIndentation;
                }

                // JSX text shouldn't affect indenting
                if (isToken(child) && child.kind !== SyntaxKind.JsxText) {
                    // if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules
                    const tokenInfo = formattingScanner.readTokenInfo(child);
                    Debug.assert(tokenInfo.token.end === child.end, "Token end is child end");
                    consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child);
                    return inheritedIndentation;
                }

                const effectiveParentStartLine = child.kind === SyntaxKind.Decorator ? childStartLine : undecoratedParentStartLine;
                const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, node, parentDynamicIndentation, effectiveParentStartLine);

                processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta);
                if (child.kind === SyntaxKind.JsxText) {
                    const range: TextRange = { pos: child.getStart(), end: child.getEnd() };
                    if (range.pos !== range.end) { // don't indent zero-width jsx text
                        const siblings = parent.getChildren(sourceFile);
                        const currentIndex = findIndex(siblings, arg => arg.pos === child.pos);
                        const previousNode = siblings[currentIndex - 1];
                        if (previousNode) {
                            // The jsx text needs no indentation whatsoever if it ends on the same line the previous sibling ends on
                            if (sourceFile.getLineAndCharacterOfPosition(range.end).line !== sourceFile.getLineAndCharacterOfPosition(previousNode.end).line) {
                                // The first line is (already) "indented" if the text starts on the same line as the previous sibling element ends on
                                const firstLineIsIndented = sourceFile.getLineAndCharacterOfPosition(range.pos).line === sourceFile.getLineAndCharacterOfPosition(previousNode.end).line;
                                indentMultilineCommentOrJsxText(range, childIndentation.indentation, firstLineIsIndented, /*indentFinalLine*/ false, /*jsxStyle*/ true);
                            }
                        }
                    }
                }

                childContextNode = node;

                if (isFirstListItem && parent.kind === SyntaxKind.ArrayLiteralExpression && inheritedIndentation === Constants.Unknown) {
                    inheritedIndentation = childIndentation.indentation;
                }

                return inheritedIndentation;
            }


function processChildNodes(nodes: NodeArray<Node>,
                parent: Node,
                parentStartLine: number,
                parentDynamicIndentation: DynamicIndentation): void {
                Debug.assert(isNodeArray(nodes));

                const listStartToken = getOpenTokenForList(parent, nodes);

                let listDynamicIndentation = parentDynamicIndentation;
                let startLine = parentStartLine;

                if (listStartToken !== SyntaxKind.Unknown) {
                    // introduce a new indentation scope for lists (including list start and end tokens)
                    while (formattingScanner.isOnToken()) {
                        const tokenInfo = formattingScanner.readTokenInfo(parent);
                        if (tokenInfo.token.end > nodes.pos) {
                            // stop when formatting scanner moves past the beginning of node list
                            break;
                        }
                        else if (tokenInfo.token.kind === listStartToken) {
                            // consume list start token
                            startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line;

                            consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent);

                            let indentationOnListStartToken: number;
                            if (indentationOnLastIndentedLine !== Constants.Unknown) {
                                // scanner just processed list start token so consider last indentation as list indentation
                                // function foo(): { // last indentation was 0, list item will be indented based on this value
                                //   foo: number;
                                // }: {};
                                indentationOnListStartToken = indentationOnLastIndentedLine;
                            }
                            else {
                                const startLinePosition = getLineStartPositionForPosition(tokenInfo.token.pos, sourceFile);
                                indentationOnListStartToken = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.pos, sourceFile, options);
                            }

                            listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, options.indentSize!); // TODO: GH#18217
                        }
                        else {
                            // consume any tokens that precede the list as child elements of 'node' using its indentation scope
                            consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent);
                        }
                    }
                }

                let inheritedIndentation = Constants.Unknown;
                for (let i = 0; i < nodes.length; i++) {
                    const child = nodes[i];
                    inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0);
                }

                const listEndToken = getCloseTokenForOpenToken(listStartToken);
                if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken()) {
                    let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent);
                    if (tokenInfo.token.kind === SyntaxKind.CommaToken && isCallLikeExpression(parent)) {
                        const commaTokenLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line;
                        if (startLine !== commaTokenLine) {
                            formattingScanner.advance();
                            tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined;
                        }
                    }

                    // consume the list end token only if it is still belong to the parent
                    // there might be the case when current token matches end token but does not considered as one
                    // function (x: function) <--
                    // without this check close paren will be interpreted as list end token for function expression which is wrong
                    if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
                        // consume list end token
                        consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent, /*isListEndToken*/ true);
                    }
                }
            }


function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node, isListEndToken?: boolean): void {
                Debug.assert(rangeContainsRange(parent, currentTokenInfo.token));

                const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine();
                let indentToken = false;

                if (currentTokenInfo.leadingTrivia) {
                    processTrivia(currentTokenInfo.leadingTrivia, parent, childContextNode, dynamicIndentation);
                }

                let lineAction = LineAction.None;
                const isTokenInRange = rangeContainsRange(originalRange, currentTokenInfo.token);

                const tokenStart = sourceFile.getLineAndCharacterOfPosition(currentTokenInfo.token.pos);
                if (isTokenInRange) {
                    const rangeHasError = rangeContainsError(currentTokenInfo.token);
                    // save previousRange since processRange will overwrite this value with current one
                    const savePreviousRange = previousRange;
                    lineAction = processRange(currentTokenInfo.token, tokenStart, parent, childContextNode, dynamicIndentation);
                    // do not indent comments\token if token range overlaps with some error
                    if (!rangeHasError) {
                        if (lineAction === LineAction.None) {
                            // indent token only if end line of previous range does not match start line of the token
                            const prevEndLine = savePreviousRange && sourceFile.getLineAndCharacterOfPosition(savePreviousRange.end).line;
                            indentToken = lastTriviaWasNewLine && tokenStart.line !== prevEndLine;
                        }
                        else {
                            indentToken = lineAction === LineAction.LineAdded;
                        }
                    }
                }

                if (currentTokenInfo.trailingTrivia) {
                    processTrivia(currentTokenInfo.trailingTrivia, parent, childContextNode, dynamicIndentation);
                }

                if (indentToken) {
                    const tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ?
                        dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container, !!isListEndToken) :
                        Constants.Unknown;

                    let indentNextTokenOrTrivia = true;
                    if (currentTokenInfo.leadingTrivia) {
                        const commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container);
                        indentNextTokenOrTrivia = indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia,
                            item => insertIndentation(item.pos, commentIndentation, /*lineAdded*/ false));
                    }

                    // indent token only if is it is in target range and does not overlap with any error ranges
                    if (tokenIndentation !== Constants.Unknown && indentNextTokenOrTrivia) {
                        insertIndentation(currentTokenInfo.token.pos, tokenIndentation, lineAction === LineAction.LineAdded);

                        lastIndentedLine = tokenStart.line;
                        indentationOnLastIndentedLine = tokenIndentation;
                    }
                }

                formattingScanner.advance();

                childContextNode = parent;
            }


function indentTriviaItems(
            trivia: TextRangeWithKind[],
            commentIndentation: number,
            indentNextTokenOrTrivia: boolean,
            indentSingleLine: (item: TextRangeWithKind) => void) {
            for (const triviaItem of trivia) {
                const triviaInRange = rangeContainsRange(originalRange, triviaItem);
                switch (triviaItem.kind) {
                    case SyntaxKind.MultiLineCommentTrivia:
                        if (triviaInRange) {
                            indentMultilineCommentOrJsxText(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia);
                        }
                        indentNextTokenOrTrivia = false;
                        break;
                    case SyntaxKind.SingleLineCommentTrivia:
                        if (indentNextTokenOrTrivia && triviaInRange) {
                            indentSingleLine(triviaItem);
                        }
                        indentNextTokenOrTrivia = false;
                        break;
                    case SyntaxKind.NewLineTrivia:
                        indentNextTokenOrTrivia = true;
                        break;
                }
            }
            return indentNextTokenOrTrivia;
        }


function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, dynamicIndentation: DynamicIndentation): void {
            for (const triviaItem of trivia) {
                if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) {
                    const triviaItemStart = sourceFile.getLineAndCharacterOfPosition(triviaItem.pos);
                    processRange(triviaItem, triviaItemStart, parent, contextNode, dynamicIndentation);
                }
            }
        }


function processRange(range: TextRangeWithKind,
            rangeStart: LineAndCharacter,
            parent: Node,
            contextNode: Node,
            dynamicIndentation: DynamicIndentation): LineAction {

            const rangeHasError = rangeContainsError(range);
            let lineAction = LineAction.None;
            if (!rangeHasError) {
                if (!previousRange) {
                    // trim whitespaces starting from the beginning of the span up to the current line
                    const originalStart = sourceFile.getLineAndCharacterOfPosition(originalRange.pos);
                    trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line);
                }
                else {
                    lineAction =
                        processPair(range, rangeStart.line, parent, previousRange, previousRangeStartLine, previousParent, contextNode, dynamicIndentation);
                }
            }

            previousRange = range;
            previousParent = parent;
            previousRangeStartLine = rangeStart.line;

            return lineAction;
        }


function processPair(currentItem: TextRangeWithKind,
            currentStartLine: number,
            currentParent: Node,
            previousItem: TextRangeWithKind,
            previousStartLine: number,
            previousParent: Node,
            contextNode: Node,
            dynamicIndentation: DynamicIndentation): LineAction {

            formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode);

            const rules = getRules(formattingContext);

            let trimTrailingWhitespaces = formattingContext.options.trimTrailingWhitespace !== false;
            let lineAction = LineAction.None;
            if (rules) {
                // Apply rules in reverse order so that higher priority rules (which are first in the array)
                // win in a conflict with lower priority rules.
                forEachRight(rules, rule => {
                    lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine);
                    switch (lineAction) {
                        case LineAction.LineRemoved:
                            // Handle the case where the next line is moved to be the end of this line.
                            // In this case we don't indent the next line in the next pass.
                            if (currentParent.getStart(sourceFile) === currentItem.pos) {
                                dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false, contextNode);
                            }
                            break;
                        case LineAction.LineAdded:
                            // Handle the case where token2 is moved to the new line.
                            // In this case we indent token2 in the next pass but we set
                            // sameLineIndent flag to notify the indenter that the indentation is within the line.
                            if (currentParent.getStart(sourceFile) === currentItem.pos) {
                                dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true, contextNode);
                            }
                            break;
                        default:
                            Debug.assert(lineAction === LineAction.None);
                    }

                    // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line
                    trimTrailingWhitespaces = trimTrailingWhitespaces && !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines;
                });
            }
            else {
                trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem.kind !== SyntaxKind.EndOfFileToken;
            }

            if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) {
                // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line
                trimTrailingWhitespacesForLines(previousStartLine, currentStartLine, previousItem);
            }

            return lineAction;
        }


function insertIndentation(pos: number, indentation: number, lineAdded: boolean | undefined): void {
            const indentationString = getIndentationString(indentation, options);
            if (lineAdded) {
                // new line is added before the token by the formatting rules
                // insert indentation string at the very beginning of the token
                recordReplace(pos, 0, indentationString);
            }
            else {
                const tokenStart = sourceFile.getLineAndCharacterOfPosition(pos);
                const startLinePosition = getStartPositionOfLine(tokenStart.line, sourceFile);
                if (indentation !== characterToColumn(startLinePosition, tokenStart.character) || indentationIsDifferent(indentationString, startLinePosition)) {
                    recordReplace(startLinePosition, tokenStart.character, indentationString);
                }
            }
        }


function characterToColumn(startLinePosition: number, characterInLine: number): number {
            let column = 0;
            for (let i = 0; i < characterInLine; i++) {
                if (sourceFile.text.charCodeAt(startLinePosition + i) === CharacterCodes.tab) {
                    column += options.tabSize! - column % options.tabSize!;
                }
                else {
                    column++;
                }
            }
            return column;
        }


function indentationIsDifferent(indentationString: string, startLinePosition: number): boolean {
            return indentationString !== sourceFile.text.substr(startLinePosition, indentationString.length);
        }


function indentMultilineCommentOrJsxText(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true, jsxTextStyleIndent?: boolean) {
            // split comment in lines
            let startLine = sourceFile.getLineAndCharacterOfPosition(commentRange.pos).line;
            const endLine = sourceFile.getLineAndCharacterOfPosition(commentRange.end).line;
            if (startLine === endLine) {
                if (!firstLineIsIndented) {
                    // treat as single line comment
                    insertIndentation(commentRange.pos, indentation, /*lineAdded*/ false);
                }
                return;
            }

            const parts: TextRange[] = [];
            let startPos = commentRange.pos;
            for (let line = startLine; line < endLine; line++) {
                const endOfLine = getEndLinePosition(line, sourceFile);
                parts.push({ pos: startPos, end: endOfLine });
                startPos = getStartPositionOfLine(line + 1, sourceFile);
            }

            if (indentFinalLine) {
                parts.push({ pos: startPos, end: commentRange.end });
            }

            if (parts.length === 0) return;

            const startLinePos = getStartPositionOfLine(startLine, sourceFile);

            const nonWhitespaceColumnInFirstPart =
                SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(startLinePos, parts[0].pos, sourceFile, options);

            let startIndex = 0;
            if (firstLineIsIndented) {
                startIndex = 1;
                startLine++;
            }

            // shift all parts on the delta size
            let delta = indentation - nonWhitespaceColumnInFirstPart.column;
            for (let i = startIndex; i < parts.length; i++ , startLine++) {
                const startLinePos = getStartPositionOfLine(startLine, sourceFile);
                const nonWhitespaceCharacterAndColumn =
                    i === 0
                        ? nonWhitespaceColumnInFirstPart
                        : SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(parts[i].pos, parts[i].end, sourceFile, options);
                if (jsxTextStyleIndent) {
                    // skip adding indentation to blank lines
                    if (isLineBreak(sourceFile.text.charCodeAt(getStartPositionOfLine(startLine, sourceFile)))) continue;
                    // reset delta on every line
                    delta = indentation - nonWhitespaceCharacterAndColumn.column;
                }
                const newIndentation = nonWhitespaceCharacterAndColumn.column + delta;
                if (newIndentation > 0) {
                    const indentationString = getIndentationString(newIndentation, options);
                    recordReplace(startLinePos, nonWhitespaceCharacterAndColumn.character, indentationString);
                }
                else {
                    recordDelete(startLinePos, nonWhitespaceCharacterAndColumn.character);
                }
            }
        }