
#include <WFX/scene/StudioLoader.h>

#include <WFX/scene/TriMesh.h>
#include <WFX/scene/TextureManager.h>
#include <WFX/scene/MaterialManager.h>

using namespace WFX;
using namespace WFX::lang;
using namespace WFX::scene;
using namespace WFX::math;
using namespace std;

StudioLoader::StudioLoader() {
    Output = new Node();
    CurrentMesh = NULL;
    CurrentMaterial = NULL;
    MasterScale = 1.0f;
    Rotate = false;
}

Node* StudioLoader::loadFromFile(const WFX::io::File& f, bool r) {
    Rotate = r;
    LOG("Loading 3DS File: " << f.toString());
    //open file
    if (!f.isFile())
        THROW("not a File: " << f.toString());

    Input = new ifstream(f.toString().data(), ios::binary);
    if(!Input)
        THROW("unable to open File: " << f.toString());

    //start parsing
    while (parseChunk());
    if (CurrentMaterial)
        MaterialManager::getInstance().regMaterial(CurrentMaterial);
    finalizeMesh();

    delete Input;
    return Output;
}

void StudioLoader::checkVersion() {
    UInt v = 0;
    readBlock((Char*)&v, sizeof(UInt));
    if (v > 3)
        THROW("3DS Version > 3");
}

void StudioLoader::newMesh() {
    finalizeMesh();
    CurrentMesh = new TriMesh();
}

void StudioLoader::newMaterial() {
    if (CurrentMaterial) {
        MaterialManager::getInstance().regMaterial(CurrentMaterial);
        LOG("Material " << CurrentMaterial->getName());
    }
    CurrentMaterial = new Material();
}

void StudioLoader::finalizeMesh() {
    if (!CurrentMesh)
        return;

    LOG("finalize " << MatGroups.size());
    std::vector<MatGroup3DS>::iterator it;
    if (MatGroups.size() == 0) {
        CurrentMesh->setMaterial(CurrentMaterial);
        CurrentMesh->setLocalScale(MasterScale);
        CurrentMesh->calcNormals();
        CurrentMesh->calcModelBound();
        Output->addChild(CurrentMesh);
    } else if (MatGroups.size() == 1) {
        CurrentMesh->setMaterial(MaterialManager::getInstance().getMaterial((*MatGroups.begin()).MatName));
        CurrentMesh->setLocalScale(MasterScale);
        CurrentMesh->calcNormals();
        CurrentMesh->calcModelBound();
        Output->addChild(CurrentMesh);
    } else {
        //iterates over Material Groups and splits the Mesh - this is slow and ugly, hence the warning
        LOG("WARNING! Using multiple Materials in one mesh takes a very long time to convert and still is b0rked!");
        for (it = MatGroups.begin(); it != MatGroups.end(); it++) {
            Short vc = (*it).VertexCount;
            std::string mn = (*it).MatName;
            TriMesh* m = new TriMesh();
            m->VertexCount = vc;
            m->Vertices = new Vector3[vc];
            std::map<Short, Short> usedVerts;    //saves used vertices
            for (Short i=0; i<vc; i++) {
                Short idx = (*it).VertIndices[i];
                usedVerts[idx] = i;
                m->Vertices[i] = CurrentMesh->Vertices[idx];
            }
            //iterate over Faces
            std::vector<UInt> newConn;
            for (UInt i=0; i<CurrentMesh->FaceCount; i++) {
                //LOG("Face: " << CurrentMesh->Connectivity[i*3] << ", "  << CurrentMesh->Connectivity[i*3+1] << ", "  << CurrentMesh->Connectivity[i*3+2]);
                if ((usedVerts.find(CurrentMesh->Connectivity[i*3]) != usedVerts.end())
                  &&(usedVerts.find(CurrentMesh->Connectivity[i*3+1]) != usedVerts.end())
                  &&(usedVerts.find(CurrentMesh->Connectivity[i*3+2]) != usedVerts.end())) {
                    //LOG("found! " << usedVerts[CurrentMesh->Connectivity[i*3]] << ", "  << usedVerts[CurrentMesh->Connectivity[i*3+1]] << ", "  << usedVerts[CurrentMesh->Connectivity[i*3+2]]);
                    newConn.push_back((UInt)usedVerts[CurrentMesh->Connectivity[i*3]]);
                    newConn.push_back((UInt)usedVerts[CurrentMesh->Connectivity[i*3+1]]);
                    newConn.push_back((UInt)usedVerts[CurrentMesh->Connectivity[i*3+2]]);
                }
            }
            //now copy new Connectivity
            if (newConn.size() > 0) {
                std::vector<UInt>::iterator conIt;
                m->FaceCount = newConn.size()/3;
                m->Connectivity = new UInt[newConn.size()];
                UInt k = 0;
                for (conIt = newConn.begin(); conIt!=newConn.end(); conIt++) {
                    m->Connectivity[k] = (*conIt); 
                    k++;
                }
                m->setMaterial(MaterialManager::getInstance().getMaterial(mn));
                m->calcNormals();
                m->calcModelBound();
                Output->addChild(m);
            } else {
                delete m;
            }
        }
        delete CurrentMesh;
    }
    //clean up MatGroups
    for (it = MatGroups.begin(); it != MatGroups.end(); it++) {
        delete[] (*it).VertIndices;
    }
    
    MatGroups.clear();
}

bool StudioLoader::parseChunk() {
    //read chunk description
    if (!readBlock((Char*)&CurrentChunk, sizeof(ChunkDesc)))
        return false;
   
    //cout.flags(ios_base::hex);
    //LOGN("--> Flag " <<  CurrentChunk.Flag);
    //cout.flags(ios_base::dec);
    //LOG(" --> Chunk Size: " << CurrentChunk.Size);
   
    //call functions according to flags
    switch(CurrentChunk.Flag) {
        case CT_VERSION:
            checkVersion();
            break;
        case CT_TRIMESH:
            newMesh();
            break;
        case CT_MASTERSCALE: 
            MasterScale = readReal(); 
            break;
        case CT_NAMEDOBJECT:
            LOG("named object " << readStr());
            break;
        case CT_POINTARRAY:
            readPointArray();
            break;
        case CT_FACEARRAY:
            readFaces();
            break;
        case CT_MATNAME:
            CurrentMaterial->setName(readStr());
            break;
        case CT_MATAMBIENT:
            CurrentMaterial->setAmbient(readColor());
            break;
        case CT_MATDIFFUSE:
            CurrentMaterial->setDiffuse(readColor());
            break;
        case CT_MATSPECULAR:
            CurrentMaterial->setSpecular(readColor());
            break;
        case CT_MATSHININESS:
            CurrentMaterial->setShininess(readPercent());
            break;
        case CT_MATSHINSTR:
            //multiply?
            CurrentMaterial->setShininess(CurrentMaterial->getShininess()*readPercent());
            break;
        case CT_MATALPHA:
            CurrentMaterial->setAlpha(readPercent());
            break;
        case CT_MATTEXMAP:
            readPercent();
            break;
        case CT_MATMAPFILE:
//            CurrentMaterial->setTexture(TextureManager::getInstance().regTexture(WFX::io::File(readStr())));
            CurrentMaterial->setTexture(TextureManager::getInstance().regTexture(readStr()));
            break;
        case CT_TEXVERTS:
            readUVArray();
            break;
        case CT_MATENTRY:
            newMaterial();
            break;
        case CT_MESHMATGRP:
            readMatGroup();
            break;
        case CT_MAIN:
        case CT_OBJMESH:
            //just read on...
            //LOG("read over " << CurrentChunk.Flag);
            break;
        case CT_MESHVER:
        case CT_MESHMATRIX:
        case CT_SMOOTHGROUP:
        case CT_AMBIENTLIGHT:
        case CT_KFDATA:
        case CT_MATALPHAFALLOFF:
        case CT_REFLECTBLUR:
        case CT_MATSHADING:
        case CT_MATSELFILPCT:
        case CT_MATALPHAFALLOFFIN:
        case CT_MATWIRESIZE:
        case CT_MATMAPTILING:
        case CT_MATMAPBLUR:
        case CT_LINCOLORBYTE:
            //ignore
            skipChunk();
            break;
        default:
            cout.flags(ios_base::hex);
            LOG("Pos " << Input->tellg() << " Size " << CurrentChunk.Size);
            LOG("unknown/ignored Flag " <<  CurrentChunk.Flag);
            cout.flags(ios_base::dec);
            skipChunk();
    }
  
    return true;
}

void StudioLoader::readMatGroup() {
    MatGroup3DS grp;
    grp.MatName = readStr();
    //LOG("new MatGRP " << grp.MatName);
    grp.VertexCount = readShort();
    grp.VertIndices = new Short[grp.VertexCount];
    readBlock((Char*)grp.VertIndices, grp.VertexCount*sizeof(Short));
    MatGroups.push_back(grp);
}

Color StudioLoader::readColor() {
    Color c;
    ChunkDesc d;
    if (!readBlock((Char*)&d, sizeof(ChunkDesc)))
        THROW("Error reading color information");
    switch (d.Flag) {
        case CT_COLORBYTE:
            //Char R, G, B 
            c.R = (Real)readUChar()/255;
            c.G = (Real)readUChar()/255;
            c.B = (Real)readUChar()/255;
            break;
        default:
            cout.flags(ios_base::hex);
            LOG(" !!!! unknown color: " << d.Flag);
            cout.flags(ios_base::dec);
            skipChunk();
    }
    return c;
}

Real StudioLoader::readPercent() {
    Real r;
    ChunkDesc d;
    if (!readBlock((Char*)&d, sizeof(ChunkDesc)))
        THROW("Error reading percent information");
    switch (d.Flag) {
        case CT_PERCENTINT:
            r = (Real)readShort()/100;
            break;
        default:
            cout.flags(ios_base::hex);
            LOG(" !!!! unknown percent: " << d.Flag);
            cout.flags(ios_base::dec);
            skipChunk();
    }
    return r;
}

void StudioLoader::readFaces() {
    Short n = readShort();
    CurrentMesh->FaceCount = n;
    CurrentMesh->Connectivity = new UInt[n*3];
    Face3DS f;
    for (Int i=0; i<n; i++) {
        readBlock((Char*)&f, sizeof(Face3DS));
        CurrentMesh->Connectivity[3*i] = f.vert1;
        //change indices
        if (Rotate) {
            CurrentMesh->Connectivity[3*i+1] = f.vert3;
            CurrentMesh->Connectivity[3*i+2] = f.vert2;
        } else {
            CurrentMesh->Connectivity[3*i+1] = f.vert2;
            CurrentMesh->Connectivity[3*i+2] = f.vert3;
        }
    }
}

void StudioLoader::readPointArray() {
    Short n = readShort();
    CurrentMesh->VertexCount = n;
    CurrentMesh->Vertices = new Vector3[n];
    readBlock((Char*)(CurrentMesh->Vertices), n*sizeof(Vector3));
    if (Rotate) {
        //invert Y and Z
        Real tmp;
        for (Int i=0; i<n; i++) {
            tmp = CurrentMesh->Vertices[i].Y; 
            CurrentMesh->Vertices[i].Y = CurrentMesh->Vertices[i].Z; 
            CurrentMesh->Vertices[i].Z = tmp;
        }
    }
}

void StudioLoader::readUVArray() {
    Short n = readShort();
    CurrentMesh->TexCoords = new Vector2[n];
    readBlock((Char*)(CurrentMesh->TexCoords), n*sizeof(Vector2));
}

Real StudioLoader::readReal() {
    Real r;
    readBlock((Char*)&r, sizeof(Real));
    return r;
}

Short StudioLoader::readShort() {
    Short r;
    readBlock((Char*)&r, sizeof(Short));
    return r;
}

Int StudioLoader::readInt() {
    Int r;
    readBlock((Char*)&r, sizeof(Int));
    return r;
}

UChar StudioLoader::readUChar() {
    UChar r;
    readBlock((Char*)&r, sizeof(UChar));
    return r;
}

std::string StudioLoader::readStr() {
    std::string r;
    Char c;
    // read until we encounter a \0
    while (c = Input->get()) {
        r += c;
    }
    return r;
}

bool StudioLoader::readBlock(Char* dest, Long size) {
    if (!Input->read(dest, size))
        return false;
    return true;
}

void StudioLoader::skipChunk() {
    ULong npos, pos;
    pos = npos = Input->tellg();
    npos += CurrentChunk.Size - sizeof(ChunkDesc);
    Input->seekg(npos);
}


