import {
	SegmentType,
} from "qrc:/js/lib/generated/enum";

import {
	assert,
	isBoolean,
	isNumber,
} from "./utils";

/**
 * Convenience function to create a [[Segment]] of [[SegmentType]] `line`
 */
export function createLineSegment(fromX: number, fromY: number, toX: number, toY: number): Segment {
	return {
		type: SegmentType.line,
		content: {
			from: {
				entries: [
					fromX,
					fromY,
				],
			},
			to: {
				entries: [
					toX,
					toY,
				],
			},
		},
	};
}

/**
 * Convenience function to create a [[Segment]] of [[SegmentType]] `arc`
 */
export function createArcSegment(fromX: number, fromY: number, toX: number, toY: number, centerX: number, centerY: number, isCcw: boolean): Segment {
	return {
		type: SegmentType.arc,
		content: {
			from: {
				entries: [
					fromX,
					fromY,
				],
			},
			to: {
				entries: [
					toX,
					toY,
				],
			},
			center: {
				entries: [
					centerX,
					centerY,
				],
			},
			ccw: isCcw,
		},
	};
}

/**
 * Compute polygon path from a list of points
 *
 * The points are combined pair-wise.
 * The first and last point are connected to close the polygon path.
 *
 * A segment's from point defines the segment type.
 * If arc-specific values are set then an arc segment is created.
 */
export function createPolygonPath(points: readonly Readonly<[ number, number, number?, number?, boolean?]>[]): Segment[] {
	assert(points.length > 1, "Expecting at least two points.  Actual number of points:  " + points.length.toString());
	const froms = points;
	const tos = [
		...points.slice(1),
		points[0],
	];
	return froms.map(([
		fromX,
		fromY,
		fromCenterX,
		fromCenterY,
		fromIsCcw,
	], index) => {
		const [
			toX,
			toY,
		] = tos[index]!;

		if (isNumber(fromCenterX) && isNumber(fromCenterY) && isBoolean(fromIsCcw)) {
			return createArcSegment(fromX, fromY, toX, toY, fromCenterX, fromCenterY, fromIsCcw);
		} else {
			return createLineSegment(fromX, fromY, toX, toY);
		}
	});
}

export function createQuadPath(dimX: number, dimY: number): Segment[] {
	return createPolygonPath([
		[
			-0.5 * dimX,
			-0.5 * dimY,
		],
		[
			0.5 * dimX,
			-0.5 * dimY,
		],
		[
			0.5 * dimX,
			0.5 * dimY,
		],
		[
			-0.5 * dimX,
			0.5 * dimY,
		],
	]);
}

export function createCuboidAssembly(dimX: number, dimY: number, dimZ: number, name = ""): Assembly|undefined {
	const path = createQuadPath(dimX, dimY);
	const iop = wsi4.geo.util.createIop(path);
	if (iop === undefined) {
		return undefined;
	} else {
		return wsi4.geo.assembly.fromIop(iop, dimZ, name);
	}
}

export interface LProfile {
	thickness: number;
	innerRadius: number;
	outerRadius: number;
	xFlangeLength: number;
	yFlangeLength: number;
}

/**
 * Create L-shaped profile path
 *
 * t = 5   (sheet thickness)
 * r = 5   (inner radius)
 * R = 10  (outer radius)
 *
 *    t
 *   ___
 *  |   |
 *  |   |
 *  |   |
 *  |   |
 *  |   | y-flange
 *  |   |
 *  |   |
 *  |   |
 *  |   |
 *  |   | r
 *  |   `-----------------------,
 *  \                           | t
 * R ` -------------------------'
 *              x-flange
 */
export function createLProfilePath(profile: Readonly<LProfile>): Segment[] {
	const innerArcCenterX = profile.thickness + profile.innerRadius;
	const innerArcCenterY = innerArcCenterX;

	return createPolygonPath([
		[
			profile.outerRadius,
			0,
		],
		[
			profile.xFlangeLength,
			0,
		],
		[
			profile.xFlangeLength,
			profile.thickness,
		],
		[
			innerArcCenterX,
			profile.thickness,
			innerArcCenterX,
			innerArcCenterY,
			false,
		],
		[
			profile.thickness,
			innerArcCenterY,
		],
		[
			profile.thickness,
			profile.yFlangeLength,
		],
		[
			0,
			profile.yFlangeLength,
		],
		[
			0,
			profile.outerRadius,
			profile.outerRadius,
			profile.outerRadius,
			true,
		],
	]);
}

export function createLProfileIop(profile: Readonly<LProfile>): InnerOuterPolygon {
	const path = createLProfilePath(profile);
	const iop = wsi4.geo.util.createIop(path);
	assert(iop !== undefined, "Could not create InnerOuterPolygon for the submitted L-profile");
	return iop;
}

/**
 * Create L-shaped profile
 *
 * t : sheet thickness
 * r : inner radius
 * R : outer radius
 * d : extrution depth
 *
 * (see [[createLProfilePath]])
 *
 *       ___
 *      /  '
 *   d /   |
 *    /   /|
 *   /   / |
 *  '___/  |
 *  |   |  |
 *  |   |  |
 *  |   |  |
 *  |   |  |
 *  |   |  |
 *  |   |  |
 *  |   |  .------------------------.
 *  |   |  /                       /|
 *  |   | / r                     / /
 *  |   |/                       / /
 *  |   `-----------------------' /
 *  \                         t |/
 * R ` -------------------------'
 */
export function createLProfileAssembly(profile: Readonly<LProfile>, extrudeDepth: number, name = ""): Assembly|undefined {
	const path = createLProfilePath(profile);
	const iop = wsi4.geo.util.createIop(path);
	if (iop === undefined) {
		return undefined;
	} else {
		return wsi4.geo.assembly.fromIop(iop, extrudeDepth, name);
	}
}

/**
 * Create circle at coordinate system origin
 */
export function createCircleProfilePath(radius: number): Segment[] {
	return createPolygonPath([
		[
			-radius,
			0.,
			0,
			0,
			true,
		],
		[
			radius,
			0.,
			0,
			0,
			true,
		],
	]);
}

/**
 * Create rectangle that is centered at the coordinate system origin
 */
export function createRectangleProfilePath(dimX: number, dimY: number): Segment[] {
	const xval = 0.5 * dimX;
	const yval = 0.5 * dimY;
	return createPolygonPath([
		[
			-xval,
			-yval,
		],
		[
			xval,
			-yval,
		],
		[
			xval,
			yval,
		],
		[
			-xval,
			yval,
		],
	]);
}

/**
 * Create rectangle with rounded corners that is centered at the coordinate system origin
 */
export function createRoundedRectangleProfilePath(dimX: number, dimY: number, radiusInput: number): Segment[] {
	const radius = Math.min(radiusInput, (0.5 * Math.min(dimX, dimY)));
	const eps = 0.1;
	if (radius < eps) {
		return createRectangleProfilePath(dimX, dimY);
	} else {
		const xval = 0.5 * dimX;
		const yval = 0.5 * dimY;
		return createPolygonPath([
			[
				-xval,
				radius - yval,
				radius - xval,
				radius - yval,
				true,
			],
			[
				radius - xval,
				-yval,
			],
			[
				xval - radius,
				-yval,
				xval - radius,
				radius - yval,
				true,
			],
			[
				xval,
				radius - yval,
			],
			[
				xval,
				yval - radius,
				xval - radius,
				yval - radius,
				true,
			],
			[
				xval - radius,
				yval,
			],
			[
				radius - xval,
				yval,
				radius - xval,
				yval - radius,
				true,
			],
			[
				-xval,
				yval - radius,
			],
		]);
	}
}

/**
 * Create circular profile path
 */
export function createCircTubeProfilePath(profile: Readonly<TubeProfileGeometryCircular>): Segment[] {
	return [
		// Outer polygon
		...createCircleProfilePath(profile.outerRadius),
		// Inner polygon
		...createCircleProfilePath(profile.outerRadius - profile.thickness),
	];
}

/**
 * Create rectangular profile path
 */
export function createRectTubeProfilePath(profile: Readonly<TubeProfileGeometryRectangular>): Segment[] {
	return [
		// Outer polygon
		...createRoundedRectangleProfilePath(profile.dimY, profile.dimZ, 2 * profile.thickness),
		// Inner polygon
		...createRoundedRectangleProfilePath(profile.dimY - 2 * profile.thickness, profile.dimZ - 2 * profile.thickness, profile.thickness),
	];
}

/**
 * Set coordinate system in a way so the extrusion is in x-direction.
 * This ensures consistency with the tube table axis conventions which
 * in turn are consistent with the machine coordinate system of e.g.
 * a tube laser cutter.
 */
function setTubeCosys(assembly: Assembly): Assembly {
	const cosys = wsi4.geo.util.createCoordinateSystem(
		{
			entries: [
				0,
				0,
				0,
			],
		},
		{
			entries: [
				0,
				0,
				1,
			],
		},
		{
			entries: [
				0,
				1,
				0,
			],
		},
	);
	return wsi4.geo.assembly.setCoordinateSystem(assembly, cosys);
}

/**
 * Pre-condition:  Profile is meaningful
 */
export function createCircTube(profile: Readonly<TubeProfileGeometryCircular>, dimX: number, name = ""): Assembly {
	const segments = createCircTubeProfilePath(profile);

	const iop = wsi4.geo.util.createIop(segments);
	assert(iop !== undefined, "Expecting valid IOP for circular tube profile");

	const assembly = wsi4.geo.assembly.fromIop(iop, dimX, name);
	return setTubeCosys(assembly);
}

/**
 * Pre-condition:  Profile is meaningful
 */
export function createRectTube(profile: Readonly<TubeProfileGeometryRectangular>, dimX: number, name = ""): Assembly {
	const segments = createRectTubeProfilePath(profile);

	const iop = wsi4.geo.util.createIop(segments);
	assert(iop !== undefined, "Expecting valid IOP for circular tube profile");

	const assembly = wsi4.geo.assembly.fromIop(iop, dimX, name);
	return setTubeCosys(assembly);
}
