/**
 * Creates a CAD segment
 *
 *
 */
import {CADSopNode} from './_BaseCAD';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import type {TopoDS_Vertex, OpenCascadeInstance, gp_Pnt2d} from '../../../core/geometry/modules/cad/CadCommon';
import {CadLoader} from '../../../core/geometry/modules/cad/CadLoader';
import {cadEdgeCreate} from '../../../core/geometry/modules/cad/toObject3D/CadEdge';
import {CadGeometryType} from '../../../core/geometry/modules/cad/CadCommon';
import {CadObject} from '../../../core/geometry/modules/cad/CadObject';
import {TypeAssert} from '../../poly/Assert';
import {CoreGroup} from '../../../core/geometry/Group';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {cadFilterObjects} from '../../../core/geometry/modules/cad/utils/CadFilter';

enum SegmentMode {
	FROM_PAIRS = 'from pairs',
	LINK_ALL = 'link all',
}
const SEGMENT_MODES: SegmentMode[] = [SegmentMode.FROM_PAIRS, SegmentMode.LINK_ALL];

class CADSegmentSopParamsConfig extends NodeParamsConfig {
	/** @param mode */

	mode = ParamConfig.INTEGER(SEGMENT_MODES.indexOf(SegmentMode.LINK_ALL), {
		menu: {
			entries: SEGMENT_MODES.map((name, value) => ({name, value})),
		},
	});
}
const ParamsConfig = new CADSegmentSopParamsConfig();

export class CADSegmentSopNode extends CADSopNode<CADSegmentSopParamsConfig> {
	override paramsConfig = ParamsConfig;
	static override type() {
		return SopType.CAD_SEGMENT;
	}

	protected override initializeNode() {
		this.io.inputs.setCount(1, 2);
	}

	override async cook(inputCoreGroups: CoreGroup[]) {
		const oc = await CadLoader.core(this);

		const mode = SEGMENT_MODES[this.pv.mode];
		switch (mode) {
			case SegmentMode.FROM_PAIRS: {
				return this._createSegmentsFromPairs(oc, inputCoreGroups);
			}
			case SegmentMode.LINK_ALL: {
				return this._createSegmentsFromAll(oc, inputCoreGroups);
			}
		}
		TypeAssert.unreachable(mode);
	}

	private _createSegmentsFromPairs(oc: OpenCascadeInstance, inputCoreGroups: CoreGroup[]) {
		const newObjects: CadObject<CadGeometryType>[] = [];
		const inputCoreGroup0 = inputCoreGroups[0];
		const inputCoreGroup1 = inputCoreGroups[1];
		if (!inputCoreGroup1) {
			this.states.error.set('input 1 required for this mode');
			return;
		}
		_createSegmentsFromVertexPairs(oc, inputCoreGroup0, inputCoreGroup1, newObjects);
		_createSegmentsFromPoint2DPairs(oc, inputCoreGroup0, inputCoreGroup1, newObjects);
		this.setCADObjects(newObjects);
	}
	private _createSegmentsFromAll(oc: OpenCascadeInstance, inputCoreGroups: CoreGroup[]) {
		const newObjects: CadObject<CadGeometryType>[] = [];
		const inputCoreGroup0 = inputCoreGroups[0];
		const inputCoreGroup1 = inputCoreGroups[1];
		let vertexObjects = cadFilterObjects(inputCoreGroup0.cadObjects(), CadGeometryType.VERTEX);
		let point2DObjects = cadFilterObjects(inputCoreGroup0.cadObjects(), CadGeometryType.POINT_2D);
		if (vertexObjects) {
			if (inputCoreGroup1) {
				const vertexObjects1 = cadFilterObjects(inputCoreGroup1.cadObjects(), CadGeometryType.VERTEX);
				if (vertexObjects1) {
					vertexObjects = vertexObjects.concat(vertexObjects1);
				}
			}
			_createSegmentsFromAllVertices(oc, vertexObjects, newObjects);
		}
		if (point2DObjects) {
			if (inputCoreGroup1) {
				const pointObjects1 = cadFilterObjects(inputCoreGroup1.cadObjects(), CadGeometryType.POINT_2D);
				if (pointObjects1) {
					point2DObjects = point2DObjects.concat(pointObjects1);
				}
			}
			_createSegmentsFromAllPoint2D(oc, point2DObjects, newObjects);
		}
		this.setCADObjects(newObjects);
	}
}

function _createSegmentsFromAllVertices(
	oc: OpenCascadeInstance,
	coreObjects: CadObject<CadGeometryType.VERTEX>[],
	newObjects: CadObject<CadGeometryType>[]
) {
	let previousVertexObject: CadObject<CadGeometryType.VERTEX> | undefined;
	for (const inputObject of coreObjects) {
		if (previousVertexObject) {
			newObjects.push(_createSegment(oc, previousVertexObject.cadGeometry(), inputObject.cadGeometry()));
		}
		previousVertexObject = inputObject;
	}
}
function _createSegmentsFromAllPoint2D(
	oc: OpenCascadeInstance,
	coreObjects: CadObject<CadGeometryType.POINT_2D>[],
	newObjects: CadObject<CadGeometryType>[]
) {
	let previousPoint2DObject: CadObject<CadGeometryType.POINT_2D> | undefined;
	for (const inputObject of coreObjects) {
		if (previousPoint2DObject) {
			newObjects.push(_createSegment2d(oc, previousPoint2DObject.cadGeometry(), inputObject.cadGeometry()));
		}
		previousPoint2DObject = inputObject;
	}
}

function _createSegmentsFromVertexPairs(
	oc: OpenCascadeInstance,
	inputCoreGroup0: CoreGroup,
	inputCoreGroup1: CoreGroup,
	newObjects: CadObject<CadGeometryType>[]
) {
	const inputVertexObjects0 = cadFilterObjects(inputCoreGroup0.cadObjects(), CadGeometryType.VERTEX);
	const inputVertexObjects1 = cadFilterObjects(inputCoreGroup1.cadObjects(), CadGeometryType.VERTEX);
	if (inputVertexObjects0 && inputVertexObjects1) {
		const minVerticesCount = Math.min(inputVertexObjects0.length, inputVertexObjects1.length);
		for (let i = 0; i < minVerticesCount; i++) {
			const vertex0 = inputVertexObjects0[i].cadGeometry();
			const vertex1 = inputVertexObjects1[i].cadGeometry();
			newObjects.push(_createSegment(oc, vertex0, vertex1));
		}
	}
}
function _createSegmentsFromPoint2DPairs(
	oc: OpenCascadeInstance,
	inputCoreGroup0: CoreGroup,
	inputCoreGroup1: CoreGroup,
	newObjects: CadObject<CadGeometryType>[]
) {
	const inputPoint2DObjects0 = cadFilterObjects(inputCoreGroup0.cadObjects(), CadGeometryType.POINT_2D);
	const inputPoint2DObjects1 = cadFilterObjects(inputCoreGroup1.cadObjects(), CadGeometryType.POINT_2D);
	if (inputPoint2DObjects0 && inputPoint2DObjects1) {
		const minPoint2DCount = Math.min(inputPoint2DObjects0.length, inputPoint2DObjects1.length);
		for (let i = 0; i < minPoint2DCount; i++) {
			const point2D0 = inputPoint2DObjects0[i].cadGeometry();
			const point2D1 = inputPoint2DObjects1[i].cadGeometry();
			newObjects.push(_createSegment2d(oc, point2D0, point2D1));
		}
	}
}

function _createSegment(oc: OpenCascadeInstance, vertex0: TopoDS_Vertex, vertex1: TopoDS_Vertex) {
	const point0 = oc.BRep_Tool.Pnt(vertex0);
	const point1 = oc.BRep_Tool.Pnt(vertex1);
	const api = new oc.GC_MakeSegment_1(point0, point1);
	const curve = api.Value().get();
	const edge = cadEdgeCreate(oc, curve);
	api.delete();
	return new CadObject(edge, CadGeometryType.EDGE);
}
function _createSegment2d(oc: OpenCascadeInstance, point0: gp_Pnt2d, point1: gp_Pnt2d) {
	const api = new oc.GCE2d_MakeSegment_1(point0, point1);
	const curve = api.Value().get();
	api.delete();
	return new CadObject(curve, CadGeometryType.CURVE_2D);
}
