// Globales Modul fuer include-Anweisungen
module;
#include <print>
#include <string>
#include <charconv>
#include <fstream>
#include <sstream>

// Modulnamen exportieren
export module Chart;

constexpr unsigned short INVALID{ 9999U };      // Merker fuer ungueltige Position


// =====================================================
// Klasse fuer einen Hitparadeneintrag
// wird nur intern verwendet und nicht exportiert
// daher als struct definiert (alles public!)
// =====================================================
struct Entry
{
    std::string title;                      // Titel
    std::string artist;                     // Interpret
    unsigned short *positions;              // Feld mit den Hitparadenpositionen
    // Feldgroesse fuer positions
    // als statische Eigenschaft da fuer alle Objekte gleich
    inline static unsigned short posSize{0};
    // ctor
    Entry();
    // dtor
    ~Entry();
    // Anzahl der Hitparadenjahre (=Anzahl Positionen) ermitteln
    static bool CalcPositions(const std::string& data);
    // Titeldaten aus String extrahieren
    bool ReadData(const std::string& data);
    // Titel und Interpret zurueckgeben
    unsigned short GetMaxPosition() const;
};

// ctor
// Setzt den Zeiger auf das Positionsfeld auf einen gueltigen Wert
// Das Feld wird erst beim Ablegen der Titeldaten reserviert
Entry::Entry()
{
    positions = nullptr;
}

// dtor
// Gibt den Speicher fuers Positionsfeld wieder frei
Entry::~Entry()
{
    delete [] positions;
}
// Anzahl der Hitparadenjahre (=Anzahl Positionen) ermitteln
bool Entry::CalcPositions(const std::string& data)
{
    // Anzahl der Semikolon zaehlen zur Bestimmung der Anzahl der Hitparadenjahre
    // Einfacher geht's spaeter mit einer Bibliotheksfunktion
    for (auto elem: data)
    {
        if (elem == ';')
            posSize++;
    }
    // true wenn Semikolons gefunden wurden
    return (posSize--) != 0;
}

// Extrahiert aus dem uebergebenen String die Titeldaten
// Der 1. Aufruf extrahiert die Jahreszahlen der Hitparade
// und alle folgenden Aufrufe die Positionen des jeweiligen Titels
// data: String mit den Titeldaten (Titel,Interpret und Positionen)
// return: true = Einlesen erfolgreich
bool Entry::ReadData (const std::string& data)
{
    // Fall Anzahl der Positionen nicht gesetzt wurde, Abbruch
    if (posSize == 0)
        return false;
    // Datenfeld fuer die Positionen reservieren
    positions = new unsigned short[posSize];

    // String mit den Daten fuer die Extraktion einem istringstream zuweisen
    std::istringstream inString(data);

    // Titel einlesen, bei Fehler abbrechen
    std::getline(inString, title, ';');
    if (!inString)
        return false;
    // Es wird davon ausgegangen, dass bei erfolgreichem Einlesen des
    // Titels die weiteren Daten plausibel sind
    std::getline(inString, artist, ';');

    // Rest von data enthaelt nur noch die Jahrszahlen
    // bzw. Positionen und wird nach strPos kopiert
    std::string strPos;
    std::getline(inString, strPos);
    // Zeiger auf letzte Position im String
    // (wird fuer from_chars() benoetigt
    auto stringEnd = strPos.c_str() + strPos.length();

    auto posIndex = 0;  // Index ins Positionsfeld
    // 1. Wert explizit konverierten
    auto res = std::from_chars(strPos.c_str(), stringEnd, positions[posIndex++]);
    // Solange konvertieren bis Stringende erreicht ists
    while (res.ptr != stringEnd)
        res = std::from_chars(res.ptr+1, stringEnd, positions[posIndex++]);
    return true;    // Einlesen  war erfolgreich, Daten sind gueltig
}

// Sucht dem Index im Positionsfeld mit der hoechsten Position
// (kleinste Position != 0) im Hitparaden-Eintrag
unsigned short Entry::GetMaxPosition() const
{
    unsigned short pos = INVALID;           // hoechste Position
    unsigned short ind = INVALID;           // Index mit der hoechsten Position

    // Alle Positionen durchlaufen
    for (auto index=0; index != posSize; ++index)
    {
        // Wenn neue hoechste Position gefunden, diese merken
        if ((positions[index] != 0) && (positions[index] < pos))
        {
            pos = positions[index];
            ind = index;
        }
    }
    // Index mit der hoechsten Position zurueckgeben.
    // Wenn keine gefunden (alle Positionen sind 0), wird INVALID zurueckgeben
    return ind;
}

// =====================================================
// Exportierte Klasse fuer die Hitparade
// =====================================================
export class Chart
{
    Entry *entries;             // Hitparaden-Eintraege
    Entry header;               // Tabellenkopf
    unsigned short noOfEntries; // Anzahl der zu lesenden Eintraege
    std::string fileName;       // Hitparaden-Datei
public:
    // ctor
    // fname: Name der Hitparaden-Datei
    // noe: Anzahl der einzulesenden Eintraege
    Chart(const std::string fname, unsigned short noe): fileName(fname)
    {
        noOfEntries = noe;
        entries = nullptr;  // Objektfeld wird beim Einlesen reserviert
    }
    // dtor
    // Loescht Objektfeld fuer die Eintraege
    ~Chart()
    {
        // Fuehrt zum mehrfachen Aufruf des ctors von Entry,
        // der seinerseits sein positions-Feld löscht!
        delete[] entries;
    }
    // Eintraege einlesen
    bool ReadChart();
    // Eintraege ausgeben
    void PrintEntries();
};

// Eintraege einlesen
bool Chart::ReadChart ()
{
    // Datei oeffnen
    std::ifstream infile(fileName);
    if (!infile)
        return false;

    // Kopfzeile einlesen
    std::string line;
    std::getline(infile, line);
    // Anzahl der Positionen pro Titel ermitteln
    // Bei Fehler: Abbruch
    if (!Entry::CalcPositions(line))
    {
        infile.close();
        return false;
    }
    // Daten in Entry-Struktur uebernehmen
    // header.positions enthaelt danach die Jahreszahlen
    header.ReadData(line);

    // Feld fuer Hitparaden-Eintraege reservieren
    entries = new Entry[noOfEntries];
    // Alle Eintraege durchlaufen
    for (auto index=0; index!= noOfEntries; ++index)
    {
        // Wenn beim Einlesen eines Eintrags ein Fehler auftritt
        // Datei schliessen und Abbruch
        if (!std::getline(infile, line))
        {
            infile.close();
            return false;
        }
        // Daten in Entry-Struktur uebernehmen und im Fehlerfall
        // wieder Datei schliessen und Abbruch
        if (!entries[index].ReadData(line))
        {
            infile.close();
            return false;
        }
    }
    // Hier lief alles korrekt
    return true;
}

// Eintraege ausgeben
void Chart::PrintEntries()
{
    unsigned short ind;         // Index der hoechsten Platzierung

    // Alle Eintraege durchlaufen
    for (auto index=0U; index!=noOfEntries; ++index)
    {
        // Index der hoechsten Platzierung ermitteln
        ind = entries[index].GetMaxPosition();
        // Wenn Index gefunden
        if (ind != INVALID)
            // Daten ausgeben (header enthaelt Jahreszahlen)
            std::println("'{}' von {}, bester Platz ({}:{})",
                    entries[index].title, entries[index].artist,
                    entries[index].positions[ind], header.positions[ind]);
        else
            // Ansonsten Fehlermeldung und mit naechsten Eintrag fortfahren
            std::println("{} von {}, keine Platzierung!",
                        entries[index].title, entries[index].artist);
    }
}
