module;
#include <iostream>
#include <cstring>
#include <compare>

export module CString;

// Klassendefinition
export class CString
{
    char* pString;          // String
public:
    // ctor
    CString();
    CString(const char* const pS);
    CString(const CString& source);
    // dtor
    ~CString();
    // String Zuweisungen
    CString& operator = (this CString& self, const char* const pS);
    CString& operator = (this CString& self, const CString& add);
    // Strings addieren
    CString operator + (const char* const pS) const;
    CString operator + (const CString& op2) const;
    // Strings vergleichen
    auto operator <=> (const CString& rhs) const;
    bool operator == (const CString& rhs) const;
    // String zurueckgeben
    const char* GetString() const
    {
        return pString;
    }
    // Überladener Indexoperator
    char& operator [] (const int index);
    // Überladener Ausgabe- und Eingabeoperator
    friend std::istream& operator >> (std::istream& is, CString& op2);
};

// Definition der Memberfunktionen
// Delegierender Konstruktor
CString::CString() :
    CString("")
{
}
// copy-ctor, delegierender ctor
CString::CString(const CString& source) :
    CString(source.GetString())
{
}
// Target ctor
CString::CString(const char* const pS)
{
    // Nun neuen Speicherplatz für String anfordern
    // Die abschliessende binäre 0 dabei nicht vergessen!
    auto len = strlen(pS) + 1;
    pString = new char[len];
    // String umkopieren
    strcpy_s(pString, len, pS);
}
// Destruktor
CString::~CString()
{
    // Speicher für char-Feld freigeben!
    delete[] pString;
}
// Weist einen C-String zu
CString& CString::operator = (this CString& self, const char* const pS)
{
    delete[] self.pString;
    self.pString = new char[strlen(pS) + 1];
    strcpy_s(self.pString, strlen(pS) + 1, pS);
    return self;
}
// Weist ein CString Objekt zu
CString& CString::operator = (this CString& self, const CString& add)
{
    // Gleicher Ablauf wie operator=(char*)
    return self.operator = (add.GetString());
}
// Fügt den über den char-Zeiger übergebenen String hinzu
CString CString::operator + (const char* const pS) const
{
    // Temp. Objekt
    CString temp;

    // Zuerst den Speicher des temp. Objekts freigeben!
    delete[] temp.pString;

    // Nun Speicher für neuen String reservieren
    auto len = strlen(pString) + strlen(pS) + 1;
    temp.pString = new char[len];
    // Zuerst den ursprünglichen String umkopieren
    strcpy_s(temp.pString, len, pString);
    // dann den String des 2. Operanden hinzufügen
    strcat_s(temp.pString, len, pS);
    return temp;
}
// Fügt den über die String-Referenz übergebenen String hinzu
CString CString::operator + (const CString& op2) const
{
    return operator + (op2.pString);
}
// Strings vergleichen
auto CString::operator <=> (const CString& rhs) const
{
    return *pString <=> *(rhs.pString);
}
bool CString::operator == (const CString& rhs) const
{
    return strcmp(pString, rhs.pString) == 0;
}

// Der Operator[] soll den Zugriff auf ein bestimmtes Zeichen im Text
// erlauben. Ist der Index grösser als die Stringlänge wird eine
// Referenz auf die abschliessende binäre 0 zurückgegeben. Damit kann
// im Programm mit der Anweisung
// if (CStringObj[nIndex] == 0)
// abgefragt werden, ob über den String hinaus zugegriffen wird!
// Als Returnwert muss hier eine Referenz auf das entsprechende Zeichen
// angegeben werden und nicht das Zeichen selbst da sonst der Indexoperator
// nicht links vom Gleichheitszeichen stehen darf!
char& CString::operator [] (const int index)
{
    // Falls über den String hinaus zugegriffen wird
    if (index >= static_cast<int>(strlen(pString)) || (index < 0))
        // Referenz auf die abschliessende binäre 0 zurückgeben
        return pString[strlen(pString)];
    // Referenz auf das gewünschte Zeichen liefern
    return pString[index];
}
// Überladene Operatorfunktion zum Einlesen eines String Objekts
export std::istream& operator >> (std::istream& in, CString& op2)
{

    // Temp. Puffer für die Eingabe
    constexpr int BSIZE = 80;
    char acBuffer[BSIZE];
    // Eine Zeile einlesen
    in.getline(acBuffer, sizeof(acBuffer));
    // alten String freigeben
    delete[] op2.pString;
    // Platz für neuen String anfordern
    auto len = strlen(acBuffer) + 1;
    op2.pString = new char[len];
    // String jetzt umkopieren
    strcpy_s(op2.pString, len, acBuffer);
    // Referenz auf Stream zurückgeben
    return in;
}
