// Copyright (C) 2024 Rubén Beltrán del Río // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see https://map.tranquil.systems. import Foundation import WmapParser struct RenderableMap { let vertices: [Vertex] let edges: [MapEdge] let inertias: [Inertia] let opportunities: [Opportunity] let notes: [Note] let stages: [Double] let groups: [[Vertex]] static let empty: RenderableMap = RenderableMap( vertices: [], edges: [], inertias: [], opportunities: [], notes: [], stages: defaultDimensions, groups: []) static func from(_ map: WmapParser.Map) -> RenderableMap { var vertices: [Vertex] = [] var edges: [MapEdge] = [] var inertias: [Inertia] = [] var opportunities: [Opportunity] = [] var notes: [Note] = [] var stages: [Double] = [25.0, 50.0, 75.0] // default dimensions var groups: [[Vertex]] = [] // Build a vertex lookup for edges, groups, etc. var vertexMap: [String: Vertex] = [:] for (index, component) in map.components.enumerated() { let newVertex = Vertex( id: index, label: component.label, position: CGPoint( x: component.coordinates.xCoordinate, y: component.coordinates.yCoordinate), shape: component.shape ) vertices.append(newVertex) vertexMap[component.label.lowercased()] = newVertex } for (index, note) in map.notes.enumerated() { notes.append( Note( id: index, position: CGPoint(x: note.coordinates.xCoordinate, y: note.coordinates.yCoordinate), text: note.text )) } for stage in map.stages { let stageIndex = stageToIndex(stage.stage) if stageIndex >= 0 && stageIndex < 3 { stages[stageIndex] = stage.value } } // Second pass for entities that depend on vertices for (index, dependency) in map.dependencies.enumerated() { if let fromVertex = vertexMap[dependency.fromComponent.lowercased()], let toVertex = vertexMap[dependency.toComponent.lowercased()] { edges.append( MapEdge( id: index, origin: fromVertex.position, destination: toVertex.position, arrowhead: dependency.isDirected )) } } for (index, inertia) in map.inertias.enumerated() { if let vertex = vertexMap[inertia.component.lowercased()] { inertias.append( Inertia( id: index, position: vertex.position )) } } for (index, evolution) in map.evolutions.enumerated() { if let vertex = vertexMap[evolution.component.lowercased()] { let destination = CGPoint( x: vertex.position.x + evolution.value, y: vertex.position.y ) opportunities.append( Opportunity( id: index, origin: vertex.position, destination: destination )) } } for group in map.groups { var groupVertices: [Vertex] = [] for componentName in group.components { if let vertex = vertexMap[componentName.lowercased()] { groupVertices.append(vertex) } } if !groupVertices.isEmpty { groups.append(groupVertices) } } return RenderableMap( vertices: vertices, edges: edges, inertias: inertias, opportunities: opportunities, notes: notes, stages: stages, groups: groups ) } private static func stageToIndex(_ stageNumber: WmapParser.StageNumber) -> Int { switch stageNumber { case .stageI: return 0 case .stageII: return 1 case .stageIII: return 2 case .stageIV: return 3 } } } struct Vertex: Identifiable, Hashable { let id: Int let label: String let position: CGPoint var shape: WmapParser.Shape = .circle } struct Note { let id: Int let position: CGPoint let text: String } struct MapEdge { let id: Int let origin: CGPoint let destination: CGPoint let arrowhead: Bool } struct Inertia { let id: Int let position: CGPoint } struct Opportunity { let id: Int let origin: CGPoint let destination: CGPoint } private let defaultDimensions: [Double] = [ 25.0, 50.0, 75.0 ]