import { getLabel, getNodeIdentifiers, isSwitch } from './nodes.js'; import { incrementAnonCounter } from './state.js'; export function buildSwitchUplinks(allSwitches, switchLinks) { const uplinks = new Map(); if (allSwitches.length === 0 || switchLinks.length === 0) return uplinks; const adjacency = new Map(); allSwitches.forEach(sw => adjacency.set(sw.id, [])); switchLinks.forEach(link => { adjacency.get(link.switchA.id).push({ neighbor: link.switchB, localPort: link.portA, remotePort: link.portB, localSpeed: link.speedA, localErrors: link.errorsA, localRates: link.ratesA }); adjacency.get(link.switchB.id).push({ neighbor: link.switchA, localPort: link.portB, remotePort: link.portA, localSpeed: link.speedB, localErrors: link.errorsB, localRates: link.ratesB }); }); for (const edges of adjacency.values()) { edges.sort((a, b) => getLabel(a.neighbor).localeCompare(getLabel(b.neighbor))); } const sortedSwitches = [...allSwitches].sort((a, b) => getLabel(a).localeCompare(getLabel(b))); let bestRoot = sortedSwitches[0]; let bestReachable = 0; let bestMaxDepth = Infinity; for (const candidate of sortedSwitches) { const visited = new Set([candidate.id]); const queue = [{ sw: candidate, depth: 0 }]; let maxDepth = 0; while (queue.length > 0) { const { sw, depth } = queue.shift(); maxDepth = Math.max(maxDepth, depth); for (const edge of adjacency.get(sw.id) || []) { if (!visited.has(edge.neighbor.id)) { visited.add(edge.neighbor.id); queue.push({ sw: edge.neighbor, depth: depth + 1 }); } } } const reachable = visited.size; if (reachable > bestReachable || (reachable === bestReachable && maxDepth < bestMaxDepth)) { bestReachable = reachable; bestMaxDepth = maxDepth; bestRoot = candidate; } } uplinks.set(bestRoot.id, 'ROOT'); const visited = new Set([bestRoot.id]); const queue = [bestRoot]; while (queue.length > 0) { const current = queue.shift(); for (const edge of adjacency.get(current.id) || []) { if (!visited.has(edge.neighbor.id)) { visited.add(edge.neighbor.id); const reverseEdge = adjacency.get(edge.neighbor.id).find(e => e.neighbor.id === current.id); uplinks.set(edge.neighbor.id, { localPort: reverseEdge?.localPort || '?', remotePort: reverseEdge?.remotePort || '?', parentNode: current, parentName: getLabel(current), speed: reverseEdge?.localSpeed || 0, errors: reverseEdge?.localErrors || null, rates: reverseEdge?.localRates || null }); queue.push(edge.neighbor); } } } return uplinks; } export function buildLocationTree(locations, parent) { if (!locations) return []; return locations.map((loc, idx) => { let locId; let anonymous = false; if (loc.name) { locId = 'loc_' + loc.name.replace(/[^a-zA-Z0-9]/g, '_'); } else { locId = 'loc_anon_' + incrementAnonCounter(); anonymous = true; } const locObj = { id: locId, name: loc.name || '', anonymous: anonymous, direction: loc.direction || 'horizontal', nodeRefs: (loc.nodes || []).map(n => n.toLowerCase()), parent: parent, children: [] }; locObj.children = buildLocationTree(loc.children, locObj); return locObj; }); } export function getSwitchesInLocation(loc, assignedNodes) { const switches = []; const nodes = assignedNodes.get(loc) || []; nodes.forEach(n => { if (isSwitch(n)) switches.push(n); }); loc.children.forEach(child => { if (child.anonymous) { switches.push(...getSwitchesInLocation(child, assignedNodes)); } }); return switches; } export function findEffectiveSwitch(loc, assignedNodes) { if (!loc) return null; const switches = getSwitchesInLocation(loc, assignedNodes); if (switches.length === 1) { return switches[0]; } if (loc.parent) { return findEffectiveSwitch(loc.parent, assignedNodes); } return null; } export function buildNodeIndex(locations, index) { locations.forEach(loc => { loc.nodeRefs.forEach(ref => { index.set(ref, loc); }); buildNodeIndex(loc.children, index); }); } export function findLocationForNode(node, nodeIndex) { const identifiers = getNodeIdentifiers(node); for (const ident of identifiers) { if (nodeIndex.has(ident)) { return nodeIndex.get(ident); } } return null; }