#include "M3Datei.h"

#include "Datei.h"
#include "GraphicsApi.h"
#include "Model3D.h"

using namespace Framework;

// Inhalt der M3Datei Klasse
// Konstruktor
M3Datei::M3Datei()
    : ReferenceCounter()
{
    modelName = 0;
    modelPos = 0;
}

// Konstruktor
//  pfad: Der Pfad zur Datei
M3Datei::M3Datei(const char* pfad)
    : M3Datei()
{
    this->pfad = pfad;
}

// Konstruktor
//  pfad: Der Pfad zur Datei
M3Datei::M3Datei(Text* pfad)
    : M3Datei(pfad->getText())
{
    pfad->release();
}

// Destruktor
M3Datei::~M3Datei()
{
    if (modelName) modelName->release();
    if (modelPos) modelPos->release();
}

void M3Datei::saveKnochen(Knochen* k, Datei* zDat)
{
    bool c = k != 0;
    zDat->schreibe((char*)&c, 1);
    if (c)
    {
        int id = k->getId();
        zDat->schreibe((char*)&id, 4);
        float f = k->getPosition().x;
        zDat->schreibe((char*)&f, 4);
        f = k->getPosition().y;
        zDat->schreibe((char*)&f, 4);
        f = k->getPosition().z;
        zDat->schreibe((char*)&f, 4);
        f = k->getDrehung().x;
        zDat->schreibe((char*)&f, 4);
        f = k->getDrehung().y;
        zDat->schreibe((char*)&f, 4);
        f = k->getDrehung().z;
        zDat->schreibe((char*)&f, 4);
        saveKnochen(k->zGeschwister(), zDat);
        saveKnochen(k->zKind(), zDat);
    }
}

Knochen* Framework::M3Datei::readKnochen(Datei* zDat) const
{
    bool c;
    zDat->lese((char*)&c, 1);
    if (c)
    {
        int id;
        zDat->lese((char*)&id, 4);
        Knochen* k = new Knochen(id);
        Vec3<float> pos;
        zDat->lese((char*)&pos.x, 4);
        zDat->lese((char*)&pos.y, 4);
        zDat->lese((char*)&pos.z, 4);
        k->setPosition(pos);
        Vec3<float> rot;
        zDat->lese((char*)&rot.x, 4);
        zDat->lese((char*)&rot.y, 4);
        zDat->lese((char*)&rot.z, 4);
        k->setDrehung(rot);
        k->addGeschwisterKnochen(readKnochen(zDat));
        k->addKind(id, readKnochen(zDat));
        return k;
    }
    return 0;
}

// Setzt den Pfad zur Datei

// pfad: Pfad zur Datei
void M3Datei::setPfad(const char* pfad)
{
    this->pfad = pfad;
    if (modelName) modelName = (RCArray<Text>*)modelName->release();
    if (modelPos) modelPos = (Array<__int64>*)modelPos->release();
}

// Ließt grundlegende Informationen aus der Datei, die für ihre Verwendung
// benötigt werden
void M3Datei::leseDaten()
{
    if (modelName) modelName = (RCArray<Text>*)modelName->release();
    if (modelPos) modelPos = (Array<__int64>*)modelPos->release();
    modelName = new RCArray<Text>();
    modelPos = new Array<__int64>();
    Datei d;
    d.setDatei(pfad);
    if (!d.open(Datei::Style::lesen)) return;
    unsigned char anz = 0;
    d.lese((char*)&anz, 1);
    for (int i = 0; i < anz; i++)
    {
        char len = 0;
        d.lese(&len, 1);
        char* n = new char[len + 1];
        n[(int)len] = 0;
        d.lese(n, len);
        modelName->add(new Text(n));
        delete[] n;
        __int64 p = 0;
        d.lese((char*)&p, 8);
        modelPos->add(p);
    }
    d.close();
}

// Speichert 3D Modell Daten in der Datei
//  zMdr: Ein Zeiger auf die zu speichernden Daten ohne erhöhtem Reference
//  Counter name: Der Name, unter dem die Daten in der Datei gespeichert werden
//  sollen return: 1, falls das Modell gespeichert wurde. 0, falls ein fehler
//  beim speichern auftrat
bool M3Datei::saveModel(Model3DData* zMdr, Text* name)
{
    bool ret = saveModel(zMdr, name->getText());
    name->release();
    return ret;
}

// Speichert 3D Modell Daten in der Datei
//  zMdr: Ein Zeiger auf die zu speichernden Daten ohne erhöhtem Reference
//  Counter name: Der Name, unter dem die Daten in der Datei gespeichert werden
//  sollen return: 1, falls das Modell gespeichert wurde. 0, falls ein fehler
//  beim speichern auftrat
bool M3Datei::saveModel(Model3DData* zMdr, const char* name)
{
    if (!modelName || !pfad.getLength()) return 0;
    if (hatModel(name) && !removeModel(name)) return 0;
    int anz = modelName->getEintragAnzahl();
    anz = modelName->getEintragAnzahl();
    Datei d;
    d.setDatei(pfad);
    d.open(Datei::Style::lesen);
    Datei neu;
    neu.setDatei(pfad);
    neu.zPfad()->append("0");
    while (neu.existiert())
        neu.zPfad()->append("0");
    if (!neu.open(Datei::Style::schreiben))
    {
        if (d.istOffen()) d.close();
        return 0;
    }
    modelName->add(new Text(name));
    int offs = textLength(name) + 9;
    for (int i = 0; i < anz; i++)
        modelPos->set(modelPos->get(i) + offs, i);
    if (d.getSize() < 0)
        modelPos->add(offs + 1);
    else
        modelPos->add(d.getSize() + offs);
    anz++;
    char tmp = (char)anz;
    neu.schreibe(&tmp, 1);
    for (int i = 0; i < anz; i++)
    {
        char len = (char)modelName->z(i)->getLength();
        neu.schreibe(&len, 1);
        neu.schreibe(modelName->z(i)->getText(), len);
        __int64 pos = modelPos->get(i);
        neu.schreibe((char*)&pos, 8);
    }
    if (d.existiert())
    {
        d.setLPosition(modelPos->get(0) - offs, 0);
        __int64 dl = d.getSize() - d.getLPosition();
        char bytes[2048];
        while (dl)
        {
            int l = dl > 2048 ? 2048 : (int)dl;
            d.lese(bytes, l);
            neu.schreibe(bytes, l);
            dl -= l;
        }
    }
    d.close();
    int vAnz = zMdr->getVertexAnzahl();
    neu.schreibe((char*)&vAnz, 4);
    for (int i = 0; i < vAnz; i++)
    {
        neu.schreibe((char*)&zMdr->zVertexBuffer()[i].knochenId, 4);
        neu.schreibe((char*)&zMdr->zVertexBuffer()[i].pos.x, 4);
        neu.schreibe((char*)&zMdr->zVertexBuffer()[i].pos.y, 4);
        neu.schreibe((char*)&zMdr->zVertexBuffer()[i].pos.z, 4);
        neu.schreibe((char*)&zMdr->zVertexBuffer()[i].tPos.x, 4);
        neu.schreibe((char*)&zMdr->zVertexBuffer()[i].tPos.y, 4);
    }
    int pAnz = zMdr->getPolygonAnzahl();
    neu.schreibe((char*)&pAnz, 4);
    for (int p = 0; p < pAnz; p++)
    {
        Polygon3D* pol = zMdr->getPolygon(p);
        int anz = pol->indexAnz;
        neu.schreibe((char*)&anz, 4);
        neu.schreibe((char*)pol->indexList, anz * 4);
    }
    float factor = zMdr->getAmbientFactor();
    neu.schreibe((char*)&factor, 4);
    factor = zMdr->getDiffusFactor();
    neu.schreibe((char*)&factor, 4);
    factor = zMdr->getSpecularFactor();
    neu.schreibe((char*)&factor, 4);
    Skelett* skelet = zMdr->copySkelett();
    if (skelet)
    {
        bool b = 1;
        neu.schreibe((char*)&b, 1);
        int nId = skelet->zNextKnochenId();
        neu.schreibe((char*)&nId, 4);
        saveKnochen(skelet->zKnochen(), &neu);
        skelet->release();
    }
    else
    {
        bool b = 0;
        neu.schreibe((char*)&b, 1);
    }
    d.remove();
    neu.close();
    neu.umbenennen(pfad);
    return 1;
}

// Löscht ein 3D Modell aus der Datei
//  name: Der Name des Modells
//  return: 1, wenn das Modell gelöscht wurde. 0, wenn das Modell nicht gefunden
//  wurde, oder ein fehler beim speichern auftrat
bool M3Datei::removeModel(Text* name)
{
    bool res = removeModel(name->getText());
    name->release();
    return res;
}

// Löscht ein 3D Modell aus der Datei
//  name: Der Name des Modells
//  return: 1, wenn das Modell gelöscht wurde. 0, wenn das Modell nicht gefunden
//  wurde, oder ein fehler beim speichern auftrat
bool M3Datei::removeModel(const char* name)
{
    if (!modelName || !pfad.getLength()) return 0;
    if (!hatModel(name)) return 0;
    Datei d;
    d.setDatei(pfad);
    if (!d.open(Datei::Style::lesen)) return 0;
    __int64 startPosition = modelPos->get(0);
    Datei neu;
    neu.setDatei(pfad);
    neu.zPfad()->append("0");
    while (neu.existiert())
        neu.zPfad()->append("0");
    if (!neu.open(Datei::Style::schreiben))
    {
        d.close();
        return 0;
    }
    char anz = (char)(modelName->getEintragAnzahl() - 1);
    neu.schreibe(&anz, 1);
    __int64 offset = textLength(name) + 9;
    __int64 removedLength = 0;
    __int64 removedPosition = 0;
    int removedIndex = 0;
    for (int i = 0; i < anz + 1; i++)
    {
        if (!modelName->z(i)->istGleich(name))
        {
            char len = (char)modelName->z(i)->getLength();
            neu.schreibe(&len, 1);
            neu.schreibe(modelName->z(i)->getText(), len);
            modelPos->set(modelPos->get(i) - offset, i);
            __int64 pos = modelPos->get(i);
            neu.schreibe((char*)&pos, 8);
        }
        else
        {
            removedPosition = modelPos->get(i);
            removedIndex = i;
            if (modelName->getEintragAnzahl() > i + 1)
            {
                removedLength = modelPos->get(i + 1) - modelPos->get(i);
                offset += removedLength;
            }
            else
            {
                removedLength = d.getSize() - removedPosition;
            }
        }
    }
    d.setLPosition(startPosition, 0);
    __int64 dl = removedPosition - startPosition;
    char bytes[2048];
    while (dl)
    {
        int l = dl > 2048 ? 2048 : (int)dl;
        d.lese(bytes, l);
        neu.schreibe(bytes, l);
        dl -= l;
    }
    d.setLPosition(removedPosition + removedLength, 0);
    dl = d.getSize() - removedPosition - removedLength;
    while (dl)
    {
        int l = dl > 2048 ? 2048 : (int)dl;
        d.lese(bytes, l);
        neu.schreibe(bytes, l);
        dl -= l;
    }
    d.close();
    d.remove();
    neu.close();
    neu.umbenennen(pfad);
    modelName->remove(removedIndex);
    modelPos->remove(removedIndex);
    return 1;
}

// Lähd ein 3D Modell aus der Datei
//  name: Der name des zu ladenden Modells
//  return: Die geladenen Daten
Model3DData* M3Datei::ladeModel(
    Text* name, GraphicsApi* zApi, Text* uniqueName) const
{
    Model3DData* d = ladeModel(name->getText(), zApi, uniqueName->getText());
    name->release();
    uniqueName->release();
    return d;
}

// Lähd ein 3D Modell aus der Datei
//  name: Der name des zu ladenden Modells
//  return: Die geladenen Daten
Model3DData* M3Datei::ladeModel(
    const char* name, GraphicsApi* zApi, const char* uniqueName) const
{
    if (!modelName || !pfad.getLength()) return 0;
    __int64 pos = -1;
    auto p = modelPos->begin();
    for (auto n = modelName->begin(); n && p; n++, p++)
    {
        if (n->istGleich(name))
        {
            pos = p;
            break;
        }
    }
    if (pos > 0)
    {
        Datei d;
        d.setDatei(pfad);
        if (!d.open(Datei::Style::lesen))
        {
            return 0;
        }
        d.setLPosition(pos, 0);
        Model3DData* model = zApi->createModel(uniqueName);
        int vAnz;
        d.lese((char*)&vAnz, 4);
        Vertex3D* vertices = new Vertex3D[vAnz];
        for (int i = 0; i < vAnz; i++)
        {
            d.lese((char*)&vertices[i].knochenId, 4);
            d.lese((char*)&vertices[i].pos.x, 4);
            d.lese((char*)&vertices[i].pos.y, 4);
            d.lese((char*)&vertices[i].pos.z, 4);
            d.lese((char*)&vertices[i].tPos.x, 4);
            d.lese((char*)&vertices[i].tPos.y, 4);
        }
        model->setVertecies(vertices, vAnz);
        int pAnz;
        d.lese((char*)&pAnz, 4);
        for (int i = 0; i < pAnz; i++)
        {
            Polygon3D* p = new Polygon3D();
            d.lese((char*)&p->indexAnz, 4);
            p->indexList = new int[p->indexAnz];
            d.lese((char*)p->indexList, p->indexAnz * 4);
            model->addPolygon(p);
        }
        float factor;
        d.lese((char*)&factor, 4);
        model->setAmbientFactor(factor);
        d.lese((char*)&factor, 4);
        model->setDiffusFactor(factor);
        d.lese((char*)&factor, 4);
        model->setSpecularFactor(factor);
        bool b;
        d.lese((char*)&b, 1);
        if (b)
        {
            Skelett* s = new Skelett();
            int nId;
            d.lese((char*)&nId, 4);
            s->setNextKnochenId(nId);
            s->addKnochen(readKnochen(&d));
            model->setSkelettZ(s);
        }
        model->calculateNormals();
        d.close();
        return model;
    }
    return 0;
}

// überprft, ob ein bestimmtes 3D Modell in der Datei existiert
//  name: Der Name des zu suchenden 3D Modells
//  return: 1, wenn das Modell gefunden wurde. 0 sonst
bool M3Datei::hatModel(const char* name) const
{
    if (!modelName || !pfad.getLength()) return 0;
    for (auto n = modelName->begin(); n; n++)
    {
        if (n->istGleich(name)) return 1;
    }
    return 0;
}

// ügibt die Anzahl der gespeicherten Modelle zurück
int M3Datei::getModelAnzahl() const
{
    if (!modelName || !pfad.getLength()) return 0;
    return modelName->getEintragAnzahl();
}

// Gibt den Namen eines Bestimmten Modells zurück
//  i: Der Index des Modells
//  return: Ein Zeiger aud den Namen des Modells ohne erhöhten Reference Counter
Text* M3Datei::zModelName(int i) const
{
    if (!modelName || !pfad.getLength()) return 0;
    return modelName->z(i);
}