#ifndef _tmemtpl_01_h
#define _tmemtpl_01_h

#include <fstream>
#include <typeinfo>
#include <string>
#include <print>
#include <charconv>

using namespace std::string_literals;

// SaveFile implementiert eine Klasse zum sicheren Schreiben
// und Lesen von Daten aus eine Datei. Beim Einlesen der Daten
// wird der in der Datei mit abgelegte Datentyp mit dem einzulesenden
// Datentyp verglichen. Sind die Datentypen unterschiedlich,
// so wird eine Ausnahme ausgeloest
class SaveFile
{
    std::fstream ioFile;
public:
    // ctor, oeffnet Datei zum Lesen und Schreiben
    SaveFile(const std::string& fileName)
    {
        ioFile.open(fileName, std::ios::in | std::ios::out | std::ios::trunc);
        if (!ioFile)
            throw "Fehler beim Datei oeffnen!"s;
    }
    // dtor, schliesst Datei
    ~SaveFile()
    {
        ioFile.close();
    }
    // Reset setzt den Schreib- und Lesezeiger zurueck
    void Reset()
    {
        ioFile.seekg(0);
        ioFile.seekp(0);
    }
    // Member-Template zum Schreiben des Datum
    template <typename T> void Write(const T& value);
    // Member-Template zum Einlesen eines Datums
    template <typename T> void Read(T& value);
};

// Member-Template zum Schreiben des Datum
// Vor dem eigentlichen Datum wird der Datentyp mit
// in die Datei geschrieben. Trennzeichen zwischen
// Datentyp und Datum ist '@'
template <typename T>
void SaveFile::Write(const T& value)
{
    std::string toWrite = std::format("{}@{}\n", typeid(T).name(), value);
    ioFile << toWrite;
}
// Member-Template zum Einlesen eines Datums
template <typename T>
void SaveFile::Read(T& value)
{
    // Datentyp und Datum einlesen
    std::string inputLine;
    std::getline(ioFile, inputLine);
    // Datentyp extrahieren, Trenner ist @
    // Kann spaeter vereinfacht werden, wenn die string-Funktion
    // find() eingefuehrt ist
    size_t index = 0;
    std::string inputDType;     // Enthaelt nachher den Datentyp
    for (; index < inputLine.length(); index++)
    {
        // Wenn Trennzeichen gefunden, fertig
        if (inputLine[index] == '@')
            break;
        else
            inputDType += inputLine[index];
    }
    if (index == inputLine.length())
        throw "Datentyp konnte nicht bestimmt werden!"s;
    // Falls einzulesender Datentyp und Dateidatentyp nicht
    // identisch sind, Exception auslösen
    if (inputDType != typeid(T).name())
    {
        auto err = std::format("Falscher Datentyp -> ist: {}, soll: {}", inputDType, typeid(T).name());
        throw err;
    }
    // Mithilfe des if constexpr Ausdrucks erzeugt der Compiler nur
    // den notwendigen Code, wenn die Bedingung erfuellt ist
    // Pruefen ob Datum ein char -> nur 1 Zeichen uebernehmen
    if constexpr (typeid(T)==typeid(char))
        value = inputLine[index+1];
    // Pruefen ob Datum ein string -> Rest des Strings uebernehmen
    else if constexpr (typeid(T) == typeid(std::string))
        value = inputLine.data()+index+1;
    // Pruefen ob Datum numerisch ist, dann string nach binaer konvertieren
    else if constexpr (std::is_arithmetic_v<T>)
    {
        auto res = std::from_chars(inputLine.c_str() + index + 1,
                                   inputLine.c_str() + inputLine.length(), value);
        if (res.ec != std::errc())
            throw "Fehler beim Einlesen des Datums"s;
    }
    // Ansonsten enthaelt Datum einen unzulaessigen Datentyp
    else
       throw "Unzulaessiger Datentyp"s;
}
#endif
