#include "M3File.h"

#include "File.h"
#include "GraphicsApi.h"
#include "Model3D.h"

using namespace Framework;

// Content of the M3File class from M3File.h
// Constructor
M3File::M3File()
    : ReferenceCounter()
{
    modelName = 0;
    modelPos = 0;
}

// Constructor
//  pfad: the path to the file
M3File::M3File(const char* pfad)
    : M3File()
{
    this->pfad = pfad;
}

// Constructor
//  pfad: the path to the file
M3File::M3File(Text* pfad)
    : M3File(pfad->getText())
{
    pfad->release();
}

// Destructor
M3File::~M3File()
{
    if (modelName) modelName->release();
    if (modelPos) modelPos->release();
}

void M3File::saveKnochen(Bone* k, File* zDat)
{
    bool c = k != 0;
    zDat->write((char*)&c, 1);
    if (c)
    {
        int id = k->getId();
        zDat->write((char*)&id, 4);
        float f = k->getPosition().x;
        zDat->write((char*)&f, 4);
        f = k->getPosition().y;
        zDat->write((char*)&f, 4);
        f = k->getPosition().z;
        zDat->write((char*)&f, 4);
        f = k->getRotation().x;
        zDat->write((char*)&f, 4);
        f = k->getRotation().y;
        zDat->write((char*)&f, 4);
        f = k->getRotation().z;
        zDat->write((char*)&f, 4);
        saveKnochen(k->zFirstSibling(), zDat);
        saveKnochen(k->zFirstChild(), zDat);
    }
}

Bone* Framework::M3File::readKnochen(File* zDat) const
{
    bool c;
    zDat->read((char*)&c, 1);
    if (c)
    {
        int id;
        zDat->read((char*)&id, 4);
        Bone* k = new Bone(id);
        Vec3<float> pos;
        zDat->read((char*)&pos.x, 4);
        zDat->read((char*)&pos.y, 4);
        zDat->read((char*)&pos.z, 4);
        k->setPosition(pos);
        Vec3<float> rot;
        zDat->read((char*)&rot.x, 4);
        zDat->read((char*)&rot.y, 4);
        zDat->read((char*)&rot.z, 4);
        k->setRotation(rot);
        k->addSiblingBone(readKnochen(zDat));
        k->addChildBone(id, readKnochen(zDat));
        return k;
    }
    return 0;
}

// sets the path to the file
// pfad: the path
void M3File::setPfad(const char* pfad)
{
    this->pfad = pfad;
    if (modelName) modelName = (RCArray<Text>*)modelName->release();
    if (modelPos) modelPos = (Array<__int64>*)modelPos->release();
}

// reads header information from the file needed for its usage.
void M3File::readData()
{
    if (modelName) modelName = (RCArray<Text>*)modelName->release();
    if (modelPos) modelPos = (Array<__int64>*)modelPos->release();
    modelName = new RCArray<Text>();
    modelPos = new Array<__int64>();
    File d;
    d.setFile(pfad);
    if (!d.open(File::Style::read)) return;
    unsigned char anz = 0;
    d.read((char*)&anz, 1);
    for (int i = 0; i < anz; i++)
    {
        char len = 0;
        d.read(&len, 1);
        char* n = new char[len + 1];
        n[(int)len] = 0;
        d.read(n, len);
        modelName->add(new Text(n));
        delete[] n;
        __int64 p = 0;
        d.read((char*)&p, 8);
        modelPos->add(p);
    }
    d.close();
}

// Saves 3D model data to the file
//  zMdr: A pointer to the data to be saved without increased reference
//  counter name: The name under which the data is stored in the file
//  return: 1 if the model was saved. 0 if an error
//  occurred during saving
bool M3File::saveModel(Model3DData* zMdr, Text* name)
{
    bool ret = saveModel(zMdr, name->getText());
    name->release();
    return ret;
}

// Saves 3D model data to the file
//  zMdr: A pointer to the data to be saved without increased reference
//  counter name: The name under which the data is stored in the file
//  return: 1 if the model was saved. 0 if an error
//  occurred during saving
bool M3File::saveModel(Model3DData* zMdr, const char* name)
{
    if (!modelName || !pfad.getLength()) return 0;
    if (hasModel(name) && !removeModel(name)) return 0;
    int anz = modelName->getEntryCount();
    anz = modelName->getEntryCount();
    File d;
    d.setFile(pfad);
    d.open(File::Style::read);
    File neu;
    neu.setFile(pfad);
    neu.zPfad()->append("0");
    while (neu.exists())
        neu.zPfad()->append("0");
    if (!neu.open(File::Style::write))
    {
        if (d.isOpen()) 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.write(&tmp, 1);
    for (int i = 0; i < anz; i++)
    {
        char len = (char)modelName->z(i)->getLength();
        neu.write(&len, 1);
        neu.write(modelName->z(i)->getText(), len);
        __int64 pos = modelPos->get(i);
        neu.write((char*)&pos, 8);
    }
    if (d.exists())
    {
        d.setReadPosition(modelPos->get(0) - offs, 0);
        __int64 dl = d.getSize() - d.getReadPosition();
        char bytes[2048];
        while (dl)
        {
            int l = dl > 2048 ? 2048 : (int)dl;
            d.read(bytes, l);
            neu.write(bytes, l);
            dl -= l;
        }
    }
    d.close();
    int vAnz = zMdr->getVertexCount();
    neu.write((char*)&vAnz, 4);
    for (int i = 0; i < vAnz; i++)
    {
        neu.write((char*)&zMdr->zVertexBuffer()[i].knochenId, 4);
        neu.write((char*)&zMdr->zVertexBuffer()[i].pos.x, 4);
        neu.write((char*)&zMdr->zVertexBuffer()[i].pos.y, 4);
        neu.write((char*)&zMdr->zVertexBuffer()[i].pos.z, 4);
        neu.write((char*)&zMdr->zVertexBuffer()[i].tPos.x, 4);
        neu.write((char*)&zMdr->zVertexBuffer()[i].tPos.y, 4);
    }
    int pAnz = zMdr->getPolygonCount();
    neu.write((char*)&pAnz, 4);
    for (int p = 0; p < pAnz; p++)
    {
        Polygon3D* pol = zMdr->getPolygon(p);
        int anz = pol->indexAnz;
        neu.write((char*)&anz, 4);
        neu.write((char*)pol->indexList, anz * 4);
    }
    float factor = zMdr->getAmbientFactor();
    neu.write((char*)&factor, 4);
    factor = zMdr->getDiffusFactor();
    neu.write((char*)&factor, 4);
    factor = zMdr->getSpecularFactor();
    neu.write((char*)&factor, 4);
    Skeleton* skelet = zMdr->copySkeleton();
    if (skelet)
    {
        bool b = 1;
        neu.write((char*)&b, 1);
        int nId = skelet->getNextBoneId();
        neu.write((char*)&nId, 4);
        saveKnochen(skelet->zRootBone(), &neu);
        skelet->release();
    }
    else
    {
        bool b = 0;
        neu.write((char*)&b, 1);
    }
    d.remove();
    neu.close();
    neu.rename(pfad);
    return 1;
}

// Deletes a 3D model from the file
//  name: The name of the model
//  return: 1 if the model was deleted. 0 if the model was not found
//  or an error occurred during saving
bool M3File::removeModel(Text* name)
{
    bool res = removeModel(name->getText());
    name->release();
    return res;
}

// Deletes a 3D model from the file
//  name: The name of the model
//  return: 1 if the model was deleted. 0 if the model was not found
//  or an error occurred during saving
bool M3File::removeModel(const char* name)
{
    if (!modelName || !pfad.getLength()) return 0;
    if (!hasModel(name)) return 0;
    File d;
    d.setFile(pfad);
    if (!d.open(File::Style::read)) return 0;
    __int64 startPosition = modelPos->get(0);
    File neu;
    neu.setFile(pfad);
    neu.zPfad()->append("0");
    while (neu.exists())
        neu.zPfad()->append("0");
    if (!neu.open(File::Style::write))
    {
        d.close();
        return 0;
    }
    char anz = (char)(modelName->getEntryCount() - 1);
    neu.write(&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)->isEqual(name))
        {
            char len = (char)modelName->z(i)->getLength();
            neu.write(&len, 1);
            neu.write(modelName->z(i)->getText(), len);
            modelPos->set(modelPos->get(i) - offset, i);
            __int64 pos = modelPos->get(i);
            neu.write((char*)&pos, 8);
        }
        else
        {
            removedPosition = modelPos->get(i);
            removedIndex = i;
            if (modelName->getEntryCount() > i + 1)
            {
                removedLength = modelPos->get(i + 1) - modelPos->get(i);
                offset += removedLength;
            }
            else
            {
                removedLength = d.getSize() - removedPosition;
            }
        }
    }
    d.setReadPosition(startPosition, 0);
    __int64 dl = removedPosition - startPosition;
    char bytes[2048];
    while (dl)
    {
        int l = dl > 2048 ? 2048 : (int)dl;
        d.read(bytes, l);
        neu.write(bytes, l);
        dl -= l;
    }
    d.setReadPosition(removedPosition + removedLength, 0);
    dl = d.getSize() - removedPosition - removedLength;
    while (dl)
    {
        int l = dl > 2048 ? 2048 : (int)dl;
        d.read(bytes, l);
        neu.write(bytes, l);
        dl -= l;
    }
    d.close();
    d.remove();
    neu.close();
    neu.rename(pfad);
    modelName->remove(removedIndex);
    modelPos->remove(removedIndex);
    return 1;
}

// Loads a 3D model from the file
//  name: The name of the model to load
//  return: The loaded data
Model3DData* M3File::loadModel(
    Text* name, GraphicsApi* zApi, Text* uniqueName) const
{
    Model3DData* d = loadModel(name->getText(), zApi, uniqueName->getText());
    name->release();
    uniqueName->release();
    return d;
}

// Loads a 3D model from the file
//  name: The name of the model to load
//  return: The loaded data
Model3DData* M3File::loadModel(
    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->isEqual(name))
        {
            pos = p;
            break;
        }
    }
    if (pos > 0)
    {
        File d;
        d.setFile(pfad);
        if (!d.open(File::Style::read))
        {
            return 0;
        }
        d.setReadPosition(pos, 0);
        Model3DData* model;
        if (zApi)
        {
            model = zApi->createModel(uniqueName);
        }
        else
        {
            model = new Model3DData(0, 0, -1);
        }
        int vAnz;
        d.read((char*)&vAnz, 4);
        Vertex3D* vertices = new Vertex3D[vAnz];
        for (int i = 0; i < vAnz; i++)
        {
            d.read((char*)&vertices[i].knochenId, 4);
            d.read((char*)&vertices[i].pos.x, 4);
            d.read((char*)&vertices[i].pos.y, 4);
            d.read((char*)&vertices[i].pos.z, 4);
            d.read((char*)&vertices[i].tPos.x, 4);
            d.read((char*)&vertices[i].tPos.y, 4);
        }
        model->setVertecies(vertices, vAnz);
        int pAnz;
        d.read((char*)&pAnz, 4);
        for (int i = 0; i < pAnz; i++)
        {
            Polygon3D* p = new Polygon3D();
            d.read((char*)&p->indexAnz, 4);
            p->indexList = new int[p->indexAnz];
            d.read((char*)p->indexList, p->indexAnz * 4);
            model->addPolygon(p);
        }
        float factor;
        d.read((char*)&factor, 4);
        model->setAmbientFactor(factor);
        d.read((char*)&factor, 4);
        model->setDiffusFactor(factor);
        d.read((char*)&factor, 4);
        model->setSpecularFactor(factor);
        bool b;
        d.read((char*)&b, 1);
        if (b)
        {
            Skeleton* s = new Skeleton();
            int nId;
            d.read((char*)&nId, 4);
            s->nextId = nId;
            s->rootBone = readKnochen(&d);
            model->setSkeletonZ(s);
        }
        model->calculateNormals();
        d.close();
        return model;
    }
    return 0;
}

// Checks whether a specific 3D model exists in the file
//  name: The name of the 3D model to search for
//  return: 1 if the model was found. 0 otherwise
bool M3File::hasModel(const char* name) const
{
    if (!modelName || !pfad.getLength()) return 0;
    for (auto n = modelName->begin(); n; n++)
    {
        if (n->isEqual(name)) return 1;
    }
    return 0;
}

// Returns the number of stored models
int M3File::getModelCount() const
{
    if (!modelName || !pfad.getLength()) return 0;
    return modelName->getEntryCount();
}

// Returns the name of a specific model
//  i: The index of the model
//  return: A pointer to the name of the model without increased reference
//  counter
Text* M3File::zModelName(int i) const
{
    if (!modelName || !pfad.getLength()) return 0;
    return modelName->z(i);
}