/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright © 2000-2002 The OGRE Team
Also see acknowledgements in Readme.html

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
#include "Q3BSP.h"
#include "SimpleTextUtils.h"

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <fstream>

float bzpScale = 0.25f;

Quake3Level::Quake3Level()
{
    data = NULL;
}
Quake3Level::~Quake3Level()
{
    if (data)
        free (data);
}

void Quake3Level::loadFromFile ( const char* fileName)
{
    if (!fileName)
        return;

    theFileName = fileName;

    initialise();

#ifdef _DEBUG
    dumpContents();
#endif

}

// byte swapping functions
void SwapFourBytes(unsigned long *dw)
{
    unsigned long tmp;
    tmp =  (*dw & 0x000000FF);
    tmp = ((*dw & 0x0000FF00) >> 0x08) | (tmp << 0x08);
    tmp = ((*dw & 0x00FF0000) >> 0x10) | (tmp << 0x08);
    tmp = ((*dw & 0xFF000000) >> 0x18) | (tmp << 0x08);
    memcpy (dw, &tmp, sizeof(unsigned long));
}

void SwapFourBytesGrup (unsigned long *src, int size)
{
    unsigned long *ptr = (unsigned long *)src;
    int i;
    for (i = 0; i < size/4; ++i)
        SwapFourBytes (&ptr[i]);
}

bool Quake3Level::dumpToModel ( CModel &model )
{
    model.clear();

    for ( int i = 0; i < mNumShaders; i++)
    {
        CMaterial   material;

        char    temp[128];
        strcpy(temp, mShaders[i].name);
        if (strrchr(temp,'.'))
            *(strrchr(temp,'.')) = 0;

        material.texture = temp;
        material.texture += ".png";

        model.materials[std::string(temp)] = material;
    }

    for ( int i = 0; i < mNumFaces; i++ )
    {
        switch ( mFaces[i].type )
        {
        case 1: // polygon face
        case 3: // mesh face
        {
            CMesh mesh;

            // add in the verts and normals and UVs
            for ( int j = 0; j < mFaces[i].vert_count; j++ )
            {
                CVertex vert;
                CVertex norm;
                CTexCoord   coord;

                int index = mFaces[i].vert_start + j;
                if ( index > 0 && index < mNumVertices)
                {
                    vert.x = mVertices[index].point[0]*bzpScale;
                    vert.y = mVertices[index].point[1]*bzpScale;
                    vert.z = mVertices[index].point[2]*bzpScale;

                    norm.x = mVertices[index].normal[0];
                    norm.y = mVertices[index].normal[1];
                    norm.z = mVertices[index].normal[2];

                    coord.u = mVertices[index].texture[0];
                    coord.v = mVertices[index].texture[1];
                }
                mesh.verts.push_back(vert);
                mesh.normals.push_back(norm);
                mesh.texCoords.push_back(coord);
            }

            // now do the face
            CFace   face;

            bool skipFace = false;

            if ( mFaces[i].shader > 0 && mFaces[i].shader < mNumShaders)
            {
                char    temp[128];
                if (strlen(mShaders[mFaces[i].shader].name) )
                {
                    strcpy(temp, mShaders[mFaces[i].shader].name);
                    char *p = strrchr(temp,'.');
                    if (p)
                        *p = 0;

                    if (strcmp(temp,"noshader") != 0)
                        face.material = temp;
                }
                for ( unsigned int t =0; t< bspMaterialSkips.size(); t++)
                {
                    if (face.material == bspMaterialSkips[t])
                        skipFace = true;
                }
            }

            if (mFaces[i].type == 1)
            {

                if (0)
                {
                    if (mFaces[i].vert_count > 2)
                    {
                        for ( int j = 2; j < mFaces[i].vert_count; j++)
                        {
                            face.clear();

                            face.verts.push_back(j);
                            face.normals.push_back(j);
                            face.texCoords.push_back(j);

                            face.verts.push_back(j-1);
                            face.normals.push_back(j-1);
                            face.texCoords.push_back(j-1);

                            face.verts.push_back(0);
                            face.normals.push_back(0);
                            face.texCoords.push_back(0);

                            mesh.faces.push_back(face);
                        }
                    }
                }
                else
                {
                    face.clear();
                    for ( int j = 0; j < mFaces[i].vert_count; j++)
                    {
                        face.verts.push_back(j);
                        face.normals.push_back(j);
                        face.texCoords.push_back(j);
                    }
                    mesh.faces.push_back(face);
                }
            }
            else    // it's a mesh
            {
                if (0)
                {
                    if (mFaces[i].vert_count > 2)
                    {
                        face.clear();
                        for ( int j = 0; j < mFaces[i].vert_count; j+= 1)
                        {
                            face.verts.push_back(j);
                            face.normals.push_back(j);
                            face.texCoords.push_back(j);

                            face.verts.push_back(j+1);
                            face.normals.push_back(j+1);
                            face.texCoords.push_back(j+1);

                            face.verts.push_back(j+2);
                            face.normals.push_back(j+2);
                            face.texCoords.push_back(j+2);

                        }
                        mesh.faces.push_back(face);
                    }
                }
                else
                {
                    face.clear();
                    for ( int j = 0; j < mFaces[i].vert_count; j++)
                    {
                        face.verts.push_back(j);
                        face.normals.push_back(j);
                        face.texCoords.push_back(j);
                    }
                    mesh.faces.push_back(face);
                }
            }

            if ( !skipFace && mesh.faces.size())
                model.meshes.push_back(mesh);
        }
        break;

        case 2: // surface
        {
            CMesh mesh;
            CFace   face;

            // add in the verts and normals and UVs
            for ( int j = mFaces[i].vert_count-1; j >= 0; j-- )
            {
                CVertex vert;
                CVertex norm;
                CTexCoord   coord;

                int index = mFaces[i].vert_start + j;
                if ( index > 0 && index < mNumVertices)
                {
                    vert.x = mVertices[index].point[0]*bzpScale;
                    vert.y = mVertices[index].point[1]*bzpScale;
                    vert.z = mVertices[index].point[2]*bzpScale;

                    norm.x = mVertices[index].normal[0];
                    norm.y = mVertices[index].normal[1];
                    norm.z = mVertices[index].normal[2];

                    coord.u = mVertices[index].texture[0];
                    coord.v = mVertices[index].texture[1];
                }
                mesh.verts.push_back(vert);
                mesh.normals.push_back(norm);
                mesh.texCoords.push_back(coord);
            }

            // now do the face

            bool skipFace = false;

            if ( mFaces[i].shader > 0 && mFaces[i].shader < mNumShaders)
            {
                char    temp[128];
                if (strlen(mShaders[mFaces[i].shader].name) )
                {
                    strcpy(temp, mShaders[mFaces[i].shader].name);
                    char *p = strrchr(temp,'.');
                    if (p)
                        *p = 0;

                    face.material = temp;
                }

                for ( unsigned int t =0; t< bspMaterialSkips.size(); t++)
                {
                    if (face.material == bspMaterialSkips[t])
                        skipFace = true;
                }
            }

            if (mFaces[i].mesh_cp[1]*mFaces[i].mesh_cp[0] + mFaces[i].vert_start < mNumVertices)
            {
                for ( int y = 0; y < mFaces[i].mesh_cp[1]-1; y++)
                {
                    for ( int x = 0; x < mFaces[i].mesh_cp[0]-1; x++)
                    {
                        face.clear();
                        int index = (y*mFaces[i].mesh_cp[1])+x;
                        face.verts.push_back(index);
                        face.normals.push_back(index);
                        face.texCoords.push_back(index);

                        index = (y+1*mFaces[i].mesh_cp[1])+x;
                        face.verts.push_back(index);
                        face.normals.push_back(index);
                        face.texCoords.push_back(index);

                        index = (y+1*mFaces[i].mesh_cp[1])+x+1;
                        face.verts.push_back(index);
                        face.normals.push_back(index);
                        face.texCoords.push_back(index);

                        mesh.faces.push_back(face);


                        index = (y*mFaces[i].mesh_cp[1])+x;
                        face.verts.push_back(index);
                        face.normals.push_back(index);
                        face.texCoords.push_back(index);

                        index = (y+1*mFaces[i].mesh_cp[1])+x+1;
                        face.verts.push_back(index);
                        face.normals.push_back(index);
                        face.texCoords.push_back(index);

                        index = (y*mFaces[i].mesh_cp[1])+x+1;
                        face.verts.push_back(index);
                        face.normals.push_back(index);
                        face.texCoords.push_back(index);

                        mesh.faces.push_back(face);

                    }
                }
            }
            if ( !skipFace && mesh.faces.size())
                model.meshes.push_back(mesh);
        }
        break;

        case 4: //billboard
            break;

        default:
            // something else
            break;
        }
    }

    // parse out the custom features

    if (mEntities)
    {
        std::string ents = (char*)mEntities;
        std::vector<std::string> entList = SimpleTextUtils::tokenize(ents,std::string("\n"));

        CCustomObject   cObject;
        bool hasPos = false;

        bool inTag = false;
        for ( unsigned int i = 0; i < entList.size(); i++)
        {
            std::string theLine = entList[i];
            if ( entList[i] == "{" )
            {
                hasPos = false;
                cObject.clear();
                inTag = true;
            }
            else if (entList[i] == "}" )
            {
                inTag = false;
                if ( cObject.name.size() &&  hasPos)
                {
                    // we only want lights and object spawns
                    if ( cObject.name != "light" && cObject.name != "misc_model" )
                        cObject.name = "spawn";

                    //if (cObject.name  == "spawn" )
                    //  model.customObjects.push_back(cObject);
                }
            }
            else
            {
                if (inTag)
                {
                    // parse it up
                    std::vector<std::string> nubs = SimpleTextUtils::tokenize(theLine,std::string(" "),0,true);
                    if ( SimpleTextUtils::tolower(nubs[0]) == "classname" )
                    {
                        if (nubs.size()>1)
                            cObject.name = nubs[1];
                    }
                    else if ( SimpleTextUtils::tolower(nubs[0]) == "origin" )
                    {
                        hasPos = true;
                        float pos[3] = {0,0,0};
                        sscanf(nubs[1].c_str(),"%f %f %f",&pos[0],&pos[1],&pos[2]);

                        pos[0] *= bzpScale;
                        pos[0] *= globalScale;
                        pos[0] += globalShift[0];

                        pos[1] *= bzpScale;
                        pos[1] *= globalScale;
                        pos[1] += globalShift[1];

                        pos[2] *= bzpScale;
                        pos[2] *= globalScale;
                        pos[2] += globalShift[2];

                        std::string line = "position " + SimpleTextUtils::format("%f %f %f",pos[0],pos[1],pos[2]);
                        cObject.params.push_back(line);
                    }
                    else
                    {
                        std::string line;

                        unsigned int nubsize = (unsigned int)nubs.size();
                        for ( unsigned int j=0; j < nubsize; j++)
                        {
                            line += nubs[j];
                            if ( j != nubs.size()-1)
                                line += " ";
                        }
                        cObject.params.push_back(line);
                    }
                }
            }
        }
    }

    return model.meshes.size() > 0;
}

void Quake3Level::initialise(void)
{
    FILE *fp = fopen(theFileName.c_str(),"rb");
    if (!fp)
        return;

    fseek(fp,0,SEEK_END);
    unsigned int size = ftell(fp);
    fseek(fp,0,SEEK_SET);
    if (data)
        free(data);

    data = (char*)malloc(size);
    fread(data,size,1,fp);
    fclose(fp);

    mHeader = (bsp_header_t*)data;
    mLumpStart = ((unsigned char*)mHeader) + sizeof(mHeader);

    mEntities = (unsigned char*)getLump(BSP_ENTITIES_LUMP);
    mNumEntities = getLumpSize(BSP_ENTITIES_LUMP);

    mElements = (int*)getLump(BSP_ELEMENTS_LUMP);
    mNumElements = getLumpSize(BSP_ELEMENTS_LUMP) / sizeof(int);

    mFaces = (bsp_face_t*)getLump(BSP_FACES_LUMP);
    mNumFaces = getLumpSize(BSP_FACES_LUMP) / sizeof(bsp_face_t);

    mLeafFaces = (int*)getLump(BSP_LFACES_LUMP);
    mNumLeafFaces = getLumpSize(BSP_LFACES_LUMP) / sizeof(int);

    mLeaves = (bsp_leaf_t*)getLump(BSP_LEAVES_LUMP);
    mNumLeaves = getLumpSize(BSP_LEAVES_LUMP) / sizeof(bsp_leaf_t);

    mLightmaps = (unsigned char*)getLump(BSP_LIGHTMAPS_LUMP);
    mNumLightmaps = getLumpSize(BSP_LIGHTMAPS_LUMP)/BSP_LIGHTMAP_BANKSIZE;

    mModels = (bsp_model_t*)getLump(BSP_MODELS_LUMP);
    mNumModels = getLumpSize(BSP_MODELS_LUMP) / sizeof(bsp_model_t);

    mNodes = (bsp_node_t*)getLump(BSP_NODES_LUMP);
    mNumNodes = getLumpSize(BSP_NODES_LUMP) / sizeof(bsp_node_t);

    mPlanes = (bsp_plane_t*) getLump(BSP_PLANES_LUMP);
    mNumPlanes = getLumpSize(BSP_PLANES_LUMP)/sizeof(bsp_plane_t);

    mShaders = (bsp_shader_t*) getLump(BSP_SHADERS_LUMP);
    mNumShaders = getLumpSize(BSP_SHADERS_LUMP)/sizeof(bsp_shader_t);

    mVis = (bsp_vis_t*)getLump(BSP_VISIBILITY_LUMP);

    mVertices = (bsp_vertex_t*) getLump(BSP_VERTICES_LUMP);
    mNumVertices = getLumpSize(BSP_VERTICES_LUMP)/sizeof(bsp_vertex_t);

    mLeafBrushes = (int*)getLump(BSP_LBRUSHES_LUMP);
    mNumLeafBrushes = getLumpSize(BSP_LBRUSHES_LUMP)/sizeof(int);

    mBrushes = (bsp_brush_t*) getLump(BSP_BRUSH_LUMP);
    mNumBrushes = getLumpSize(BSP_BRUSH_LUMP)/sizeof(bsp_brush_t);

    mBrushSides = (bsp_brushside_t*) getLump(BSP_BRUSHSIDES_LUMP);
    mNumBrushSides = getLumpSize(BSP_BRUSHSIDES_LUMP)/sizeof(bsp_brushside_t);

#ifdef __APPLE___
    int i=0;

    // swap header
    SwapFourBytes (&mHeader->version);
    SwapFourBytesGrup ((unsigned long*)mElements, mNumElements*sizeof(int));
    SwapFourBytesGrup ((unsigned long*)mFaces, mNumFaces*sizeof(bsp_face_t));
    SwapFourBytesGrup ((unsigned long*)mLeafFaces, mNumLeafFaces*sizeof(int));
    SwapFourBytesGrup ((unsigned long*)mLeaves, mNumLeaves*sizeof(bsp_leaf_t));
    SwapFourBytesGrup ((unsigned long*)mModels, mNumModels*sizeof(bsp_model_t));
    SwapFourBytesGrup ((unsigned long*)mNodes, mNumNodes*sizeof(bsp_node_t));
    SwapFourBytesGrup ((unsigned long*)mPlanes, mNumPlanes*sizeof(bsp_plane_t));
    for (i=0; i < mNumShaders; ++i)
    {
        SwapFourBytes(&mShaders[i].surface_flags);
        SwapFourBytes(&mShaders[i].content_flags);
    }
    SwapFourBytes(&mVis->cluster_count);
    SwapFourBytes(&mVis->row_size);
    SwapFourBytesGrup ((unsigned long*)mVertices, mNumVertices*sizeof(bsp_vertex_t));
    SwapFourBytesGrup ((unsigned long*)mLeafBrushes, mNumLeafBrushes*sizeof(int));
    SwapFourBytesGrup ((unsigned long*)mBrushes,  mNumBrushes*sizeof(bsp_brush_t));
    SwapFourBytesGrup ((unsigned long*)mBrushSides, mNumBrushSides*sizeof(bsp_brushside_t));
#endif
}

void* Quake3Level::getLump(int lumpType)
{

#ifdef __APPLE___
    // swap lump offset
    SwapFourBytes (&mHeader->lumps[lumpType].offset);
#endif
    return (unsigned char*)mHeader + mHeader->lumps[lumpType].offset;
}

int Quake3Level::getLumpSize(int lumpType)
{

#ifdef __APPLE___
    // swap lump size
    SwapFourBytes (&mHeader->lumps[lumpType].size);
#endif
    return mHeader->lumps[lumpType].size;
}

void Quake3Level::dumpContents(void)
{
    std::ofstream of;
    of.open("Quake3Level.log");


    of << "Quake3 level statistics" << std::endl;
    of << "-----------------------" << std::endl;
    of << "Entities     : " << mNumEntities << std::endl;
    of << "Faces	: " << mNumFaces << std::endl;
    of << "Leaf Faces   : " << mNumLeafFaces << std::endl;
    of << "Leaves       : " << mNumLeaves << std::endl;
    of << "Lightmaps    : " << mNumLightmaps << std::endl;
    of << "Elements     : " << mNumElements << std::endl;
    of << "Models       : " << mNumModels << std::endl;
    of << "Nodes	: " << mNumNodes << std::endl;
    of << "Planes       : " << mNumPlanes << std::endl;
    of << "Shaders      : " << mNumShaders << std::endl;
    of << "Vertices     : " << mNumVertices << std::endl;
//  of << "Vis Clusters : " << mVis->cluster_count << std::endl;

    of << std::endl;
    of << "-= Shaders =-";
    of << std::endl;
    for (int i = 0; i < mNumShaders; ++i)
        of << "Shader " << i << ": " << mShaders[i].name << std::endl;

    of << std::endl;
    of << "-= Entities =-";
    of << std::endl;
    char* strEnt = strtok((char*)mEntities, "\0");
    while (strEnt != 0)
    {
        of << strEnt << std::endl;
        strEnt = strtok(0, "\0");
    }
    of.close();
}

void Quake3Level::extractLightmaps(void) const
{
    // Lightmaps are always 128x128x24 (RGB)
//  unsigned char* pLightmap = mLightmaps;
    for (int i = 0; i < mNumLightmaps; ++i)
    {
        char name[32];
        sprintf(name, "@lightmap%d", i);

        /*  // Load, no mipmaps, brighten by factor 2.5
            Image img; img.loadRawData( DataChunk( pLightmap, 128 * 128 * 3 ), 128, 128, PF_R8G8B8 );
            TextureManager::getSingleton().loadImage( name, img, TEX_TYPE_2D, 0, 4.0f );
            pLightmap += BSP_LIGHTMAP_BANKSIZE;
        */
    }
}

