Projekt_SS25/Project_Periodensystem.Controller/PeriodensystemController.cs
2025-06-30 15:30:55 +02:00

539 lines
21 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Project_Periodensystem.Model;
using Project_Periodensystem.Persistence;
namespace Project_Periodensystem.Controller
{
/// <summary>
/// Haupt-Controller für das Periodensystem - implementiert MVC-Pattern.
/// Koordiniert Geschäftslogik zwischen View und Model/Persistence.
/// Delegiert komplexe Aufgaben an spezialisierte Helper-Klassen.
/// </summary>
public class PeriodensystemController
{
// ===== PRIVATE FELDER =====
/// <summary>
/// Liste aller chemischen Elemente
/// </summary>
private List<Element> _elements = new List<Element>();
/// <summary>
/// Navigation Service für Seitenwechsel (optional, da Interface-basiert)
/// </summary>
private readonly INavigationService? _navigationService;
// ===== HELPER-KLASSEN FÜR CODE-ORGANISATION =====
/// <summary>
/// Spezialisierte Klasse für Validierungslogik
/// Nullable: Wird erst nach dem Laden der Elemente initialisiert
/// Separation of Concerns: Validierung ist getrennt von anderen Aufgaben
/// </summary>
private ElementValidator? _validator;
/// <summary>
/// Spezialisierte Klasse für statistische Berechnungen
/// Nullable: Wird erst nach dem Laden der Elemente initialisiert
/// Single Responsibility: Nur für Statistiken zuständig
/// </summary>
private ElementStatistics? _statistics;
// ===== KONSTRUKTOR =====
/// <summary>
/// Konstruktor mit optionaler Navigation
/// </summary>
public PeriodensystemController(INavigationService? navigationService = null)
{
_navigationService = navigationService;
LoadElements(); // Daten sofort beim Erstellen laden
}
/// <summary>
/// Lädt alle chemischen Elemente und initialisiert Helper-Klassen
/// </summary>
private void LoadElements()
{
try
{
// Elemente aus DataManager laden
_elements = DataManager.LoadElements();
Logger.Log($"Controller: {_elements.Count} Elemente erfolgreich geladen");
// Helper-Klassen initialisieren
_validator = new ElementValidator(_elements);
_statistics = new ElementStatistics(_elements);
// Demonstrative Logging der geladenen Daten
var symbols = GetAllSymbols();
Logger.LogArray("Element-Symbole (erste 10)", symbols.Take(10).ToArray());
var (totalElements, uniqueSeries, avgWeight) = GetStatistics();
Logger.LogTuple("Statistiken", $"Elemente: {totalElements}, Serien: {uniqueSeries}, Ø-Gewicht: {avgWeight:F2}");
var (lightest, heaviest) = GetWeightExtremes();
if (lightest != null && heaviest != null)
{
Logger.LogTuple("Gewichts-Extreme", $"Leichtestes: {lightest.Symbol} ({lightest.AtomicWeight}), Schwerstes: {heaviest.Symbol} ({heaviest.AtomicWeight})");
}
}
catch (Exception ex)
{
// Graceful Degradation: Bei Fehlern eine leere Liste verwenden
Logger.Log($"EXCEPTION in LoadElements: {ex.Message}");
_elements = new List<Element>();
// Helper-Klassen können nicht initialisiert werden (bleiben null)
_validator = null;
_statistics = null;
}
}
// ===== PUBLIC API METHODEN - ELEMENTZUGRIFF =====
/// <summary>
/// Gibt alle Elemente als List&lt;Element&gt; zurück
///
/// RÜCKGABETYP: List&lt;Element&gt;
/// - Ermöglicht Add/Remove-Operationen
/// - LINQ-Operationen verfügbar
/// - Dynamische Größe
///
/// VERWENDUNG:
/// - Hauptsächlich für UI-Bindungen (WPF/Avalonia DataBinding)
/// - Für LINQ-Abfragen in der View-Schicht
/// </summary>
public List<Element> GetAllElements()
{
return _elements;
}
/// <summary>
/// Gibt alle Elemente als Array zurück
///
/// RÜCKGABETYP: Element[]
/// - Feste Größe (Read-Only nach Erstellung)
/// - Bessere Performance für Iterationen
/// - Weniger Speicher-Overhead als List
///
/// C# KONZEPT: LINQ ToArray()
/// - Konvertiert IEnumerable zu Array
/// - Erstellt eine Kopie der Daten
///
/// VERWENDUNG:
/// - Für Algorithmen, die feste Arrays benötigen
/// - Wenn Unveränderlichkeit wichtig ist
/// </summary>
public Element[] GetAllElementsAsArray()
{
return _elements.ToArray();
}
/// <summary>
/// Extrahiert alle Element-Symbole als String-Array
///
/// ZWECK:
/// - Demonstriert klassische Array-Initialisierung und -Population
/// - Zeigt manuelle Iteration vs. LINQ-Alternativen
///
/// C# KONZEPTE:
/// - Array-Initialisierung: new string[size]
/// - For-Schleifen: Klassische imperative Programmierung
/// - Array-Indexing: array[i] = value
///
/// ALTERNATIVE (LINQ):
/// return _elements.Select(e => e.Symbol).ToArray();
///
/// LERNWERT:
/// - Zeigt Unterschied zwischen imperativer und funktionaler Programmierung
/// - Arrays vs. Collections
/// </summary>
public string[] GetAllSymbols()
{
// Klassische Array-Erstellung mit fester Größe
string[] symbols = new string[_elements.Count];
// Manuelle Population mit for-Schleife (imperativer Stil)
for (int i = 0; i < _elements.Count; i++)
{
symbols[i] = _elements[i].Symbol;
}
return symbols;
}
// ===== DELEGATION AN HELPER-KLASSEN =====
/// <summary>
/// Diese Methoden implementieren das Delegation Pattern
///
/// VORTEILE DER DELEGATION:
/// - Single Responsibility Principle: Jede Klasse hat einen klaren Zweck
/// - Code-Reduktion: Controller wird schlanker und übersichtlicher
/// - Testbarkeit: Helper-Klassen können isoliert getestet werden
/// - Wartbarkeit: Änderungen an Validierung/Statistik beeinflussen Controller nicht
///
/// C# KONZEPTE:
/// - Null-Conditional Operator (?.): Verhindert NullReferenceException
/// - Null-Coalescing Operator (??): Liefert Fallback-Werte wenn null
/// - Lambda Expressions: Kurze Syntax für einfache Delegationen
/// - Expression-bodied Members: => Syntax für einzeilige Methoden
///
/// FEHLERBEHANDLUNG:
/// - Alle Methoden handhaben null-Helper graceful mit Fallback-Werten
/// - Keine Exceptions, sondern sichere Default-Werte
/// </summary>
// ===== VALIDIERUNGS-METHODEN (delegiert an ElementValidator) =====
/// <summary>
/// Validiert die gesamte Element-Datenbasis
/// Delegation an ElementValidator.ValidateData()
/// Fallback: false bei null-Validator
/// </summary>
public bool ValidateData() => _validator?.ValidateData() ?? false;
/// <summary>
/// Validiert die Position eines spezifischen Elements im Periodensystem
/// Delegation an ElementValidator.ValidateElementPosition()
/// Fallback: false bei null-Validator
/// </summary>
public bool ValidateElementPosition(Element? element) => _validator?.ValidateElementPosition(element) ?? false;
// ===== STATISTIK-METHODEN (delegiert an ElementStatistics) =====
/// <summary>
/// Extrahiert alle Atomgewichte als Array
/// Delegation an ElementStatistics.GetAtomicWeights()
/// Fallback: leeres Array bei null-Statistics
/// </summary>
public double[] GetAtomicWeights() => _statistics?.GetAtomicWeights() ?? new double[0];
/// <summary>
/// Berechnet das durchschnittliche Atomgewicht
/// Delegation an ElementStatistics.GetAverageAtomicWeight()
/// Fallback: 0.0 bei null-Statistics
/// </summary>
public double GetAverageAtomicWeight() => _statistics?.GetAverageAtomicWeight() ?? 0.0;
/// <summary>
/// Sammelt alle einzigartigen Element-Serien
/// Delegation an ElementStatistics.GetAllSeries()
/// Fallback: leere Liste bei null-Statistics
/// </summary>
public List<string> GetAllSeries() => _statistics?.GetAllSeries() ?? new List<string>();
/// <summary>
/// Sammelt wichtige Statistiken in einem Tuple
/// Tuple Deconstruction: (int, int, double) für strukturierte Daten
/// Delegation an ElementStatistics.GetStatistics()
/// Fallback: (0, 0, 0.0) bei null-Statistics
/// </summary>
public (int totalElements, int uniqueSeries, double avgWeight) GetStatistics() => _statistics?.GetStatistics() ?? (0, 0, 0.0);
/// <summary>
/// Findet das leichteste und schwerste Element
/// Tuple mit nullable Elementen: (Element?, Element?)
/// Delegation an ElementStatistics.GetWeightExtremes()
/// Fallback: (null, null) bei null-Statistics
/// </summary>
public (Element? lightest, Element? heaviest) GetWeightExtremes() => _statistics?.GetWeightExtremes() ?? (null, null);
/// <summary>
/// Extrahiert grundlegende Informationen eines Elements
/// Tuple mit gemischten Typen: (string, string, double)
/// Delegation an ElementStatistics.GetElementInfo()
/// Fallback: ("?", "Unknown", 0.0) bei null-Statistics
/// </summary>
public (string symbol, string name, double weight) GetElementInfo(int atomicNumber) => _statistics?.GetElementInfo(atomicNumber) ?? ("?", "Unknown", 0.0);
/// <summary>
/// Ermittelt die Position eines Elements im Periodensystem
/// Tuple mit Koordinaten: (int row, int column)
/// Delegation an ElementStatistics.GetElementPosition()
/// Fallback: (-1, -1) für "nicht gefunden" bei null-Statistics
/// </summary>
public (int row, int column) GetElementPosition(int atomicNumber) => _statistics?.GetElementPosition(atomicNumber) ?? (-1, -1);
// ===== ELEMENT-SUCHFUNKTIONEN =====
/// <summary>
/// Sucht ein Element anhand seiner Ordnungszahl (Atomzahl)
///
/// ZWECK:
/// - Eindeutige Identifizierung von Elementen
/// - Ordnungszahl ist der Primärschlüssel im Periodensystem
///
/// C# KONZEPTE:
/// - LINQ FirstOrDefault(): Gibt erstes Element oder null zurück
/// - Lambda Expression: e => e.AtomicNumber == atomicNumber
/// - Nullable Return Type: Element? kann null sein
///
/// PARAMETER:
/// - atomicNumber: Ordnungszahl des gesuchten Elements (1-118)
///
/// RÜCKGABE:
/// - Element-Objekt wenn gefunden, null wenn nicht vorhanden
///
/// VERWENDUNG:
/// var hydrogen = controller.GetElementByAtomicNumber(1);
/// if (hydrogen != null) { /* Element verwenden */ }
/// </summary>
public Element? GetElementByAtomicNumber(int atomicNumber)
{
return _elements.FirstOrDefault(e => e.AtomicNumber == atomicNumber);
}
/// <summary>
/// Sucht ein Element anhand seines chemischen Symbols
///
/// ZWECK:
/// - Benutzerfreundliche Suche mit bekannten Symbolen (H, He, Li, etc.)
/// - Case-insensitive Suche für bessere Usability
///
/// C# KONZEPTE:
/// - String.IsNullOrWhiteSpace(): Robuste Null/Empty-Prüfung
/// - StringComparison.OrdinalIgnoreCase: Ignoriert Groß-/Kleinschreibung
/// - Early Return Pattern: Frühzeitiger Ausstieg bei ungültigen Eingaben
/// - Guard Clauses: Validierung am Methodenstart
///
/// PARAMETER:
/// - symbol: Chemisches Symbol (z.B. "H", "he", "LI")
///
/// RÜCKGABE:
/// - Element-Objekt wenn gefunden, null bei ungültigem Symbol oder nicht gefunden
///
/// FEHLERBEHANDLUNG:
/// - Null/Empty/Whitespace-Eingaben werden sicher behandelt
/// - Keine Exception bei ungültigen Eingaben
/// </summary>
public Element? GetElementBySymbol(string symbol)
{
// Guard Clause: Ungültige Eingaben frühzeitig abfangen
if (string.IsNullOrWhiteSpace(symbol)) return null;
// Case-insensitive Suche für bessere Benutzerfreundlichkeit
return _elements.FirstOrDefault(e => string.Equals(e.Symbol, symbol, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Filtert alle Elemente einer bestimmten chemischen Serie
///
/// ZWECK:
/// - Gruppierung von Elementen nach chemischen Eigenschaften
/// - Ermöglicht Analyse von Element-Familien (Metalle, Nichtmetalle, etc.)
///
/// C# KONZEPTE:
/// - LINQ Where(): Filtert Elemente basierend auf Bedingung
/// - ToList(): Konvertiert IEnumerable zu List für weitere Operationen
/// - Guard Clause: Schutz vor ungültigen Eingaben
///
/// PARAMETER:
/// - series: Name der chemischen Serie (z.B. "Alkali metal", "Noble gas")
///
/// RÜCKGABE:
/// - Liste aller Elemente der angegebenen Serie
/// - Leere Liste bei ungültiger Serie oder wenn keine Elemente gefunden
///
/// BEISPIEL:
/// var alkaliMetals = controller.GetElementsBySeries("Alkali metal");
/// // Enthält: Li, Na, K, Rb, Cs, Fr
/// </summary>
public List<Element> GetElementsBySeries(string series)
{
// Guard Clause: Ungültige Eingaben behandeln
if (string.IsNullOrWhiteSpace(series)) return new List<Element>();
// LINQ-Filterung mit case-insensitive Vergleich
return _elements.Where(e => string.Equals(e.Series, series, StringComparison.OrdinalIgnoreCase)).ToList();
}
// ===== DATEN-MANAGEMENT =====
/// <summary>
/// Lädt alle Elementdaten neu aus dem Persistence-Layer
///
/// ZWECK:
/// - Aktualisierung bei geänderten Datenquellen
/// - Recovery nach Datenfehlern
/// - Synchronisation mit externen Datenänderungen
///
/// ABLAUF:
/// 1. Benutzer über Neuladen informieren
/// 2. LoadElements() ruft gesamte Initialisierungslogik auf
/// 3. Helper-Klassen werden mit neuen Daten reinitialisiert
///
/// VERWENDUNG:
/// - Nach Datenimport
/// - Bei Corruption Recovery
/// - Für Entwicklungs-/Debug-Zwecke
/// </summary>
public void RefreshData()
{
Logger.Log("Daten werden neu geladen...");
LoadElements(); // Vollständige Reinitialisierung
}
/// <summary>
/// Gibt die aktuelle Anzahl geladener Elemente zurück
///
/// ZWECK:
/// - Schnelle Verfügbarkeitsprüfung
/// - UI-Statusanzeigen
/// - Validierung nach Datenoperationen
///
/// C# KONZEPT:
/// - Expression-bodied Member: => Syntax für einzeilige Properties
/// - Count Property: Effiziente Größenabfrage für List&lt;T&gt;
///
/// RÜCKGABE:
/// - Anzahl der verfügbaren Elemente (0 wenn keine geladen)
/// </summary>
public int GetElementCount() => _elements.Count;
// ===== NAVIGATION CONTROLLER METHODEN =====
/// <summary>
/// Diese Methoden implementieren den Command Pattern für UI-Aktionen
///
/// ARCHITEKTUR-PRINZIPIEN:
/// - Separation of Concerns: UI-Logik getrennt von Datenlogik
/// - Command Pattern: Jede Benutzeraktion wird als Method-Call gekapselt
/// - Exception Safety: Alle Navigation-Aktionen sind exception-safe
/// - Dependency Injection: NavigationService wird injiziert, nicht direkt instanziiert
///
/// C# KONZEPTE:
/// - Try-Catch Blocks: Robuste Fehlerbehandlung
/// - Null-Conditional Operator (?.): Sichere Methodenaufrufe
/// - Method Chaining: navigationService?.Method()
/// - Consistent Error Handling: Alle Methoden folgen demselben Pattern
///
/// LOGGING:
/// - Alle Aktionen werden geloggt für Debugging
/// - Exceptions werden mit Kontext geloggt
/// - Einheitliche Log-Messages für bessere Nachverfolgung
/// </summary>
/// <summary>
/// Navigiert zur Hauptansicht des Periodensystems
///
/// ZWECK:
/// - Wechsel zur interaktiven Periodensystem-Darstellung
/// - Hauptfunktion der Anwendung
///
/// FEHLERBEHANDLUNG:
/// - Exception wird geloggt, aber nicht weitergegeben
/// - Navigation Service kann null sein (optional dependency)
/// - Graceful Degradation bei Navigationsfehlern
/// </summary>
public void HandleNavigateToPeriodicTable()
{
try
{
Logger.Log("Controller: Navigation zum Periodensystem angefordert");
_navigationService?.NavigateToPeriodicTable();
}
catch (Exception ex)
{
Logger.LogException(ex, "HandleNavigateToPeriodicTable");
}
}
/// <summary>
/// Navigiert zur Informationsseite über die Anwendung
///
/// ZWECK:
/// - Anzeige von Anwendungsinformationen
/// - Credits und Versionsinformationen
/// - Hilfe und Dokumentation
/// </summary>
public void HandleNavigateToAbout()
{
try
{
Logger.Log("Controller: Navigation zu About angefordert");
_navigationService?.NavigateToAbout();
}
catch (Exception ex)
{
Logger.LogException(ex, "HandleNavigateToAbout");
}
}
/// <summary>
/// Navigiert zurück zur Startseite/Landing Page
///
/// ZWECK:
/// - Rückkehr zum Hauptmenü
/// - Reset der Anwendung zum Ausgangszustand
/// - Home-Button Funktionalität
/// </summary>
public void HandleNavigateToLanding()
{
try
{
Logger.Log("Controller: Navigation zur Landing Page angefordert");
_navigationService?.NavigateToLanding();
}
catch (Exception ex)
{
Logger.LogException(ex, "HandleNavigateToLanding");
}
}
/// <summary>
/// Wechselt zwischen hellen und dunklen UI-Themes
///
/// ZWECK:
/// - Benutzerfreundlichkeit durch Theme-Auswahl
/// - Accessibility (Dunkel-Modus für Augen-schonend)
/// - Moderne UI-Standards erfüllen
///
/// UI-PATTERN:
/// - Toggle-Funktionalität (Hell ↔ Dunkel)
/// - Persistierung des gewählten Themes
/// </summary>
public void HandleToggleTheme()
{
try
{
Logger.Log("Controller: Theme-Wechsel angefordert");
_navigationService?.ToggleTheme();
}
catch (Exception ex)
{
Logger.LogException(ex, "HandleToggleTheme");
}
}
/// <summary>
/// Startet den Datenexport-Prozess
///
/// ZWECK:
/// - Export der Elementdaten in verschiedene Formate
/// - Datensicherung und -portabilität
/// - Integration mit anderen Anwendungen
///
/// UI-WORKFLOW:
/// - Zeigt Bestätigungsdialog an
/// - Benutzer wählt Export-Optionen
/// - Export wird durchgeführt und bestätigt
/// </summary>
public void HandleExportData()
{
try
{
Logger.Log("Controller: Daten-Export angefordert");
_navigationService?.ShowExportConfirmation();
}
catch (Exception ex)
{
Logger.LogException(ex, "HandleExportData");
}
}
}
}