/*
 * Copyright © 2008 Ben Smith
 * Copyright © 2010-2011 Linaro Limited
 *
 * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
 *
 * glmark2 is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * glmark2 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * glmark2.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *  Ben Smith (original glmark benchmark)
 *  Alexandros Frantzis (glmark2)
 */
#include "mesh.h"
#include "model.h"
#include "vec.h"
#include "log.h"
#include "options.h"
#include "util.h"
#include "float.h"
#include "math.h"
#include <algorithm>
#include <fstream>
#include <sstream>
#include <memory>

using std::string;
using std::vector;
using LibMatrix::vec2;
using LibMatrix::vec3;
using LibMatrix::uvec3;

static inline void read_from_file(std::istream& file, void *dst, size_t nmemb, size_t size) {
    file.read(reinterpret_cast<char *>(dst), nmemb * size);
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
    char *p = reinterpret_cast<char *>(dst);
    for (size_t i = 0; i < nmemb; ++i) {
        if (size == 2) {
            std::swap(p[size*i], p[size*i+1]);
        } else if (size == 4) {
            std::swap(p[size*i], p[size*i+3]);
            std::swap(p[size*i+1], p[size*i+2]);
        }
    }
#endif
}

#define read_or_fail(file, dst, nmemb, size) do { \
    read_from_file((file), (dst), (nmemb), (size)); \
    if (file.gcount() < (std::streamsize)((nmemb)*(size))) { \
        Log::error("%s: %d: Failed to read %zd bytes from 3ds file (read %zd)\n", \
                   __FUNCTION__, __LINE__, \
                   (size_t)((nmemb)*(size)), file.gcount()); \
        return false; \
    } \
} while(0);

/**
 * Computes the bounding box for a Model::Object.
 *
 * @param object the Model object
 */
void
Model::compute_bounding_box(const Object& object)
{
    float minX(FLT_MAX);
    float maxX(FLT_MIN);
    float minY(FLT_MAX);
    float maxY(FLT_MIN);
    float minZ(FLT_MAX);
    float maxZ(FLT_MIN);
    for (vector<Vertex>::const_iterator vIt = object.vertices.begin(); vIt != object.vertices.end(); vIt++)
    {
        const vec3& curVtx = vIt->v;
        if (curVtx.x() < minX)
        {
            minX = curVtx.x();
        }
        if (curVtx.x() > maxX)
        {
            maxX = curVtx.x();
        }
        if (curVtx.y() < minY)
        {
            minY = curVtx.y();
        }
        if (curVtx.y() > maxY)
        {
            maxY = curVtx.y();
        }
        if (curVtx.z() < minZ)
        {
            minZ = curVtx.z();
        }
        if (curVtx.z() > maxZ)
        {
            maxZ = curVtx.z();
        }
    }
    maxVec_ = vec3(maxX, maxY, maxZ);
    minVec_ = vec3(minX, minY, minZ);
}

/**
 * Appends the vertices of a Model::Object to a Mesh.
 *
 * @param object the object to append
 * @param mesh the mesh to append to
 * @param p_pos the attribute position to use for the 'position' attribute
 * @param n_pos the attribute position to use for the 'normal' attribute
 * @param t_pos the attribute position to use for the 'texcoord' attribute
 */
void
Model::append_object_to_mesh(const Object &object, Mesh &mesh,
                             int p_pos, int n_pos, int t_pos,
                             int nt_pos, int nb_pos)
{
    for (vector<Face>::const_iterator faceIt = object.faces.begin();
         faceIt != object.faces.end();
         faceIt++)
    {
        // In some model file formats (OBJ in particular), the face description
        // may contain separate indices per-attribute.  So, we need to allow
        // for this when adding each vertex attribute to the mesh.
        const Face &face = *faceIt;
        const Vertex &v1 = object.vertices[face.v.x()];
        const Vertex &v2 = object.vertices[face.v.y()];
        const Vertex &v3 = object.vertices[face.v.z()];

        bool separate_t(face.which & Face::OBJ_FACE_T);

        const Vertex &t1 = object.vertices[separate_t ? face.t.x() : face.v.x()];
        const Vertex &t2 = object.vertices[separate_t ? face.t.y() : face.v.y()];
        const Vertex &t3 = object.vertices[separate_t ? face.t.z() : face.v.z()];

        bool separate_n(face.which & Face::OBJ_FACE_N);

        const Vertex &n1 = object.vertices[separate_n ? face.n.x() : face.v.x()];
        const Vertex &n2 = object.vertices[separate_n ? face.n.y() : face.v.y()];
        const Vertex &n3 = object.vertices[separate_n ? face.n.z() : face.v.z()];

        mesh.next_vertex();
        if (p_pos >= 0)
            mesh.set_attrib(p_pos, v1.v);
        if (n_pos >= 0)
            mesh.set_attrib(n_pos, n1.n);
        if (t_pos >= 0)
            mesh.set_attrib(t_pos, t1.t);
        if (nt_pos >= 0)
            mesh.set_attrib(nt_pos, v1.nt);
        if (nb_pos >= 0)
            mesh.set_attrib(nb_pos, v1.nb);

        mesh.next_vertex();
        if (p_pos >= 0)
            mesh.set_attrib(p_pos, v2.v);
        if (n_pos >= 0)
            mesh.set_attrib(n_pos, n2.n);
        if (t_pos >= 0)
            mesh.set_attrib(t_pos, t2.t);
        if (nt_pos >= 0)
            mesh.set_attrib(nt_pos, v2.nt);
        if (nb_pos >= 0)
            mesh.set_attrib(nb_pos, v2.nb);

        mesh.next_vertex();
        if (p_pos >= 0)
            mesh.set_attrib(p_pos, v3.v);
        if (n_pos >= 0)
            mesh.set_attrib(n_pos, n3.n);
        if (t_pos >= 0)
            mesh.set_attrib(t_pos, t3.t);
        if (nt_pos >= 0)
            mesh.set_attrib(nt_pos, v3.nt);
        if (nb_pos >= 0)
            mesh.set_attrib(nb_pos, v3.nb);
    }
}

/**
 * Converts a model to a mesh using the default attributes bindings.
 *
 * The default attributes and their order is: Position, Normal, Texcoord
 *
 * @param mesh the mesh to populate
 */
void
Model::convert_to_mesh(Mesh &mesh)
{
    std::vector<std::pair<AttribType, int> > attribs;

    attribs.push_back(std::pair<AttribType, int>(AttribTypePosition, 3));
    attribs.push_back(std::pair<AttribType, int>(AttribTypeNormal, 3));
    attribs.push_back(std::pair<AttribType, int>(AttribTypeTexcoord, 2));

    convert_to_mesh(mesh, attribs);
}

/**
 * Converts a model to a mesh using custom attribute bindings.
 *
 * The attribute bindings are pairs of <AttribType, dimensionality>.
 *
 * @param mesh the mesh to populate
 * @param attribs the attribute bindings to use
 */
void
Model::convert_to_mesh(Mesh &mesh,
                       const std::vector<std::pair<AttribType, int> > &attribs)
{
    std::vector<int> format;
    int p_pos = -1;
    int n_pos = -1;
    int t_pos = -1;
    int nt_pos = -1;
    int nb_pos = -1;

    mesh.reset();

    for (std::vector<std::pair<AttribType, int> >::const_iterator ai = attribs.begin();
         ai != attribs.end();
         ai++)
    {
        format.push_back(ai->second);
        if (ai->first == AttribTypePosition)
            p_pos = ai - attribs.begin();
        else if (ai->first == AttribTypeNormal)
            n_pos = ai - attribs.begin();
        else if (ai->first == AttribTypeTexcoord)
            t_pos = ai - attribs.begin();
        else if (ai->first == AttribTypeTangent)
            nt_pos = ai - attribs.begin();
        else if (ai->first == AttribTypeBitangent)
            nb_pos = ai - attribs.begin();
    }

    mesh.set_vertex_format(format);

    for (std::vector<Object>::const_iterator iter = objects_.begin();
         iter != objects_.end();
         iter++)
    {
        append_object_to_mesh(*iter, mesh, p_pos, n_pos, t_pos, nt_pos, nb_pos);
    }
}

void
Model::calculate_texcoords()
{
    if (gotTexcoords_)
        return;

    // Since the model didn't come with texcoords, and we don't actually know
    // if it came with normals, either, we'll use positional spherical mapping
    // to generate texcoords for the model.  See:
    // http://www.mvps.org/directx/articles/spheremap.htm for more details.
    vec3 centerVec = maxVec_ + minVec_;
    centerVec *= 0.5;
    
    for (std::vector<Object>::iterator iter = objects_.begin();
         iter != objects_.end();
         iter++)
    {
        Object &object = *iter;
        for (vector<Vertex>::iterator vertexIt = object.vertices.begin();
             vertexIt != object.vertices.end();
             vertexIt++)
        {
            Vertex& curVertex = *vertexIt;
            vec3 vnorm(curVertex.v - centerVec);
            vnorm.normalize();
            curVertex.t.x(asinf(vnorm.x()) / M_PI + 0.5);
            curVertex.t.y(asinf(vnorm.y()) / M_PI + 0.5);
        }
    }

    gotTexcoords_ = true;
}

/**
 * Calculates the normal vectors of the model vertices.
 */
void
Model::calculate_normals()
{
    if (gotNormals_)
        return;

    LibMatrix::vec3 n;

    for (std::vector<Object>::iterator iter = objects_.begin();
         iter != objects_.end();
         iter++)
    {
        Object &object = *iter;

        for (vector<Face>::const_iterator f_iter = object.faces.begin();
             f_iter != object.faces.end();
             f_iter++)
        {
            const Face &face = *f_iter;
            Vertex &a = object.vertices[face.v.x()];
            Vertex &b = object.vertices[face.v.y()];
            Vertex &c = object.vertices[face.v.z()];

            /* Calculate normal */
            n = LibMatrix::vec3::cross(b.v - a.v, c.v - a.v);
            n.normalize();
            a.n += n;
            b.n += n;
            c.n += n;

            LibMatrix::vec3 q1(b.v - a.v);
            LibMatrix::vec3 q2(c.v - a.v);
            LibMatrix::vec2 u1(b.t - a.t);
            LibMatrix::vec2 u2(c.t - a.t);
            float det = (u1.x() * u2.y() - u2.x() * u1.y());

            /* Calculate tangent */
            LibMatrix::vec3 nt;
            nt.x(det * (u2.y() * q1.x() - u1.y() * q2.x()));
            nt.y(det * (u2.y() * q1.y() - u1.y() * q2.y()));
            nt.z(det * (u2.y() * q1.z() - u1.y() * q2.z()));
            nt.normalize();
            a.nt += nt;
            b.nt += nt;
            c.nt += nt;

            /* Calculate bitangent */
            LibMatrix::vec3 nb;
            nb.x(det * (u1.x() * q2.x() - u2.x() * q1.x()));
            nb.y(det * (u1.x() * q2.y() - u2.x() * q1.y()));
            nb.z(det * (u1.x() * q2.z() - u2.x() * q1.z()));
            nb.normalize();
            a.nb += nb;
            b.nb += nb;
            c.nb += nb;
        }

        for (vector<Vertex>::iterator v_iter = object.vertices.begin();
             v_iter != object.vertices.end();
             v_iter++)
        {
            Vertex &v = *v_iter;
            /* Orthogonalize */
            v.nt = (v.nt - v.n * LibMatrix::vec3::dot(v.nt, v.n));
            v.n.normalize();
            v.nt.normalize();
            v.nb.normalize();
        }
    }

    gotNormals_ = true;
}

/**
 * Load a model from a 3DS file.
 *
 * @param filename the name of the file
 *
 * @return whether loading succeeded
 */
bool
Model::load_3ds(const std::string &filename)
{
    Object *object(0);

    Log::debug("Loading model from 3ds file '%s'\n", filename.c_str());

    const std::unique_ptr<std::istream> input_file_ptr(Util::get_resource(filename));
    std::istream& input_file(*input_file_ptr);

    if (!input_file) {
        Log::error("Could not open 3ds file '%s'\n", filename.c_str());
        return false;
    }

    // Loop to scan the whole file
    while (!input_file.eof()) {
        uint16_t chunk_id;
        uint32_t chunk_length;

        // Read the chunk header
        read_from_file(input_file, &chunk_id, 1, 2);
        if (input_file.gcount() == 0) {
            continue;
        }
        else if (input_file.gcount() < 2) {
            Log::error("%s: %d: Failed to read %zd bytes from 3ds file (read %zd)\n",
                       __FUNCTION__, __LINE__, 2, input_file.gcount());
            return false;
        }

        //Read the length of the chunk
        read_or_fail(input_file, &chunk_length, 1, 4);

        switch (chunk_id)
        {
            //----------------- MAIN3DS -----------------
            // Description: Main chunk, contains all the other chunks
            // Chunk ID: 4d4d
            // Chunk Length: 0 + sub chunks
            //-------------------------------------------
            case 0x4d4d:
                break;

            //----------------- EDIT3DS -----------------
            // Description: 3D Editor chunk, objects layout info
            // Chunk ID: 3d3d (hex)
            // Chunk Length: 0 + sub chunks
            //-------------------------------------------
            case 0x3d3d:
                break;

            //--------------- EDIT_OBJECT ---------------
            // Description: Object block, info for each object
            // Chunk ID: 4000 (hex)
            // Chunk Length: len(object name) + sub chunks
            //-------------------------------------------
            case 0x4000:
                {
                std::stringstream ss;
                unsigned char c = 1;

                for (int i = 0; i < 20 && c != '\0'; i++) {
                    read_or_fail(input_file, &c, 1, 1);
                    ss << c;
                }

                objects_.push_back(Object(ss.str()));
                object = &objects_.back();
                }
                break;

            //--------------- OBJ_TRIMESH ---------------
            // Description: Triangular mesh, contains chunks for 3d mesh info
            // Chunk ID: 4100 (hex)
            // Chunk Length: 0 + sub chunks
            //-------------------------------------------
            case 0x4100:
                break;

            //--------------- TRI_VERTEXL ---------------
            // Description: Vertices list
            // Chunk ID: 4110 (hex)
            // Chunk Length: 1 x unsigned short (number of vertices)
            //             + 3 x float (vertex coordinates) x (number of vertices)
            //             + sub chunks
            //-------------------------------------------
            case 0x4110:
                {
                uint16_t qty;
                read_or_fail(input_file, &qty, 1, sizeof(uint16_t));
                object->vertices.resize(qty);

                for (uint16_t i = 0; i < qty; i++) {
                    float f[3];
                    read_or_fail(input_file, f, 3, sizeof(float));
                    vec3& vertex = object->vertices[i].v;
                    vertex.x(f[0]);
                    vertex.y(f[1]);
                    vertex.z(f[2]);
                }
                }
                break;

            //--------------- TRI_FACEL1 ----------------
            // Description: Polygons (faces) list
            // Chunk ID: 4120 (hex)
            // Chunk Length: 1 x unsigned short (number of polygons)
            //             + 3 x unsigned short (polygon points) x (number of polygons)
            //             + sub chunks
            //-------------------------------------------
            case 0x4120:
                {
                uint16_t qty;
                read_or_fail(input_file, &qty, 1, sizeof(uint16_t));
                object->faces.resize(qty);
                for (uint16_t i = 0; i < qty; i++) {
                    uint16_t f[4];
                    read_or_fail(input_file, f, 4, sizeof(uint16_t));
                    uvec3& face = object->faces[i].v;
                    face.x(f[0]);
                    face.y(f[1]);
                    face.z(f[2]);
                }
                }
                break;

            //------------- TRI_MAPPINGCOORS ------------
            // Description: Vertices list
            // Chunk ID: 4140 (hex)
            // Chunk Length: 1 x unsigned short (number of mapping points)
            //             + 2 x float (mapping coordinates) x (number of mapping points)
            //             + sub chunks
            //-------------------------------------------
            case 0x4140:
                {
                uint16_t qty;
                read_or_fail(input_file, &qty, 1, sizeof(uint16_t));
                for (uint16_t i = 0; i < qty; i++) {
                    float f[2];
                    read_or_fail(input_file, f, 2, sizeof(float));
                    vec2& texcoord = object->vertices[i].t;
                    texcoord.x(f[0]);
                    texcoord.y(f[1]);
                }
                }
                gotTexcoords_ = true;
                break;

            //----------- Skip unknow chunks ------------
            //We need to skip all the chunks that currently we don't use
            //We use the chunk length information to set the file pointer
            //to the same level next chunk
            //-------------------------------------------
            default:
                input_file.seekg(chunk_length - 6, std::ios::cur);
        }
    }

    // Compute bounding box for perspective projection
    compute_bounding_box(*object);

    if (Options::show_debug) {
        for (std::vector<Object>::const_iterator iter = objects_.begin();
             iter != objects_.end();
             iter++)
        {
            Log::debug("    Object name: %s Vertex count: %d Face count: %d\n",
                       iter->name.c_str(), iter->vertices.size(), iter->faces.size());
        }
    }

    return true;
}


const unsigned int Model::Face::OBJ_FACE_V = 0x1;
const unsigned int Model::Face::OBJ_FACE_T = 0x2;
const unsigned int Model::Face::OBJ_FACE_N = 0x4;

/**
 * Parse 2-element vertex attribute from an OBJ file.
 *
 * @param source the source line to parse
 * @param v the vec2 to populate
 */
void
Model::obj_get_attrib(const string& source, vec2& v)
{
    // Our attribs are whitespace separated, so use a fuzzy split.
    vector<string> elements;
    Util::split(source, ' ', elements, Util::SplitModeFuzzy);

    // Find the first value...
    float x = Util::fromString<float>(elements[0]);
    // And the second value (there might be a third, but we don't care)...
    float y = Util::fromString<float>(elements[1]);
    v.x(x);
    v.y(y);
}

/**
 * Parse 3-element vertex attribute from an OBJ file.
 *
 * @param source the source line to parse
 * @param v the vec3 to populate
 */
void
Model::obj_get_attrib(const string& source, vec3& v)
{
    // Our attribs are whitespace separated, so use a fuzzy split.
    vector<string> elements;
    Util::split(source, ' ', elements, Util::SplitModeFuzzy);

    // Find the first value...
    float x = Util::fromString<float>(elements[0]);
    // Then the second value...
    float y = Util::fromString<float>(elements[1]);
    // And the third value (there might be a fourth, but we don't care)...
    float z = Util::fromString<float>(elements[2]);
    v.x(x);
    v.y(y);
    v.z(z);
}


void
Model::obj_face_get_index(const string& tuple, unsigned int& which,
    unsigned int& v, unsigned int& t, unsigned int& n)
{
    // We can use a normal split here as syntax requires no spaces around
    // the '/' delimiter for a face description.
    vector<string> elements;
    Util::split(tuple, '/', elements, Util::SplitModeNormal);

    if (elements.empty())
    {
        which = 0;
        return;
    }

    which = Face::OBJ_FACE_V;
    v = Util::fromString<unsigned int>(elements[0]);

    unsigned int num_elements = elements.size();

    if (num_elements > 1 && !elements[1].empty())
    {
        which |= Face::OBJ_FACE_T;
        t = Util::fromString<unsigned int>(elements[1]);
    }

    if (num_elements > 2 && !elements[2].empty())
    {
        which |= Face::OBJ_FACE_N;
        n = Util::fromString<unsigned int>(elements[2]);
    }

    return;
}

/**
 * Parse a face description from an OBJ file.
 * Faces always specify position, but optionally can also contain separate
 * indices for texcoords and normals.
 *
 * @param source the source line to parse
 * @param v the uvec3 to populate
 */
void
Model::obj_get_face(const string& source, Face& f)
{
    // Our indices are whitespace separated, so use a fuzzy split.
    vector<string> elements;
    Util::split(source, ' ', elements, Util::SplitModeFuzzy);

    // Find the first value...
    unsigned int which(0);
    unsigned int vx(0);
    unsigned int tx(0);
    unsigned int nx(0);
    obj_face_get_index(elements[0], which, vx, tx, nx);

    // Then the second value...
    unsigned int vy(0);
    unsigned int ty(0);
    unsigned int ny(0);
    obj_face_get_index(elements[1], which, vy, ty, ny);

    // And the third value (there might be a fourth, but we don't care)...
    unsigned int vz(0);
    unsigned int tz(0);
    unsigned int nz(0);
    obj_face_get_index(elements[2], which, vz, tz, nz);

    // OBJ models start absoluted indices at '1', so subtract to re-base to
    // '0'.  We do not handle relative indexing (negative indices).  
    f.which = which;
    f.v.x(vx - 1);
    f.v.y(vy - 1);
    f.v.z(vz - 1);
    if (which & Face::OBJ_FACE_T)
    {
        f.t.x(tx - 1);
        f.t.y(ty - 1);
        f.t.z(tz - 1);
    }
    if (which & Face::OBJ_FACE_N)
    {
        f.n.x(nx - 1);
        f.n.y(ny - 1);
        f.n.z(nz - 1);
    }
}

/**
 * Load a model from an OBJ file.
 *
 * @param filename the name of the file
 *
 * @return whether loading succeeded
 */
bool
Model::load_obj(const std::string &filename)
{
    Log::debug("Loading model from obj file '%s'\n", filename.c_str());

    const std::unique_ptr<std::istream> input_file_ptr(Util::get_resource(filename));
    std::istream& inputFile(*input_file_ptr);
    if (!inputFile)
    {
        Log::error("Failed to open '%s'\n", filename.c_str());
        return false;
    }

    vector<string> sourceVec;
    string curLine;
    while (getline(inputFile, curLine))
    {
        sourceVec.push_back(curLine);
    }

    // Give ourselves an object to populate.
    objects_.push_back(Object(string()));
    Object& object(objects_.back());

    static const string object_definition("o");
    static const string vertex_definition("v");
    static const string normal_definition("vn");
    static const string texcoord_definition("vt");
    static const string face_definition("f");
    vector<vec3> positions;
    vector<vec3> normals;
    vector<vec2> texcoords;
    for (vector<string>::const_iterator lineIt = sourceVec.begin();
         lineIt != sourceVec.end();
         lineIt++)
    {
        const string& curSrc = *lineIt;
        // Is it a vertex attribute, a face description, comment or other?
        // We only care about the first two, we ignore comments, group names,
        // smoothing groups, etc.
        string::size_type startPos(0);
        string::size_type spacePos = curSrc.find(" ", startPos);
        string::size_type num_chars(string::npos);
        string definition;
        if (spacePos != string::npos)
        {
            // Could be arbitrary whitespace between description type and
            // the data
            string::size_type defPos = curSrc.find_first_not_of(' ', spacePos);
            definition = string(curSrc, defPos);
            num_chars = spacePos - startPos;
        }
        string definitionType(curSrc, startPos, num_chars);

        if (definitionType == vertex_definition)
        {
            vec3 p;
            obj_get_attrib(definition, p);
            positions.push_back(p);
        }
        else if (definitionType == normal_definition)
        {
            vec3 n;
            obj_get_attrib(definition, n);
            normals.push_back(n);
        }
        else if (definitionType == texcoord_definition)
        {
            vec2 t;
            obj_get_attrib(definition, t);
            texcoords.push_back(t);
        }
        else if (definitionType == face_definition)
        {
            Face f;
            obj_get_face(definition, f);
            object.faces.push_back(f);
        }
        else if (definitionType == object_definition)
        {
            object.name = definition;
        }
    }

    if (!texcoords.empty())
    {
        gotTexcoords_ = true;
    }
    if (!normals.empty())
    {
        gotNormals_ = true;
    }
    unsigned int numVertices = positions.size();
    object.vertices.resize(numVertices);
    for (unsigned int i = 0; i < numVertices; i++)
    {
        Vertex& curVertex = object.vertices[i];
        curVertex.v = positions[i];
        if (gotTexcoords_)
        {
            curVertex.t = texcoords[i];
        }
        if (gotNormals_)
        {
            curVertex.n = normals[i];
        }
    }

    // Compute bounding box for perspective projection
    compute_bounding_box(object);

    Log::debug("Object name: %s Vertex count: %u Face count: %u\n",
        object.name.empty() ? "(none)" : object.name.c_str(), object.vertices.size(), object.faces.size());
    return true;
}

namespace ModelPrivate
{
ModelMap modelMap;
}

/**
 * Locate all available models.
 *
 * This method scans the built-in data paths and build a database of usable
 * models available to scenes.  Map is available on a read-only basis to scenes
 * that might find it useful for listing models, etc.
 *
 * @return a map containing information about the located models
 */
const ModelMap&
Model::find_models()
{
    if (!ModelPrivate::modelMap.empty())
    {
        return ModelPrivate::modelMap;
    }
    vector<string> pathVec;
    string dataDir(Options::data_path + "/models");
    Util::list_files(dataDir, pathVec);
#ifdef GLMARK_EXTRAS_PATH
    string extrasDir(GLMARK_EXTRAS_PATH"/models");
    Util::list_files(extrasDir, pathVec);
#endif

    // Now that we have a list of all of the model files available to us,
    // let's go through and pull out the names and what format they're in
    // so the scene can decide which ones to use.
    for(vector<string>::const_iterator pathIt = pathVec.begin();
        pathIt != pathVec.end();
        pathIt++)
    {
        const string& curPath = *pathIt;
        string::size_type namePos(0);
        string::size_type slashPos = curPath.rfind("/");
        if (slashPos != string::npos)
        {
            // Advance to the first character after the last slash
            namePos = slashPos + 1;
        }

        ModelFormat format(MODEL_INVALID);
        string::size_type extPos = curPath.rfind(".3ds");
        if (extPos == string::npos)
        {
            // It's not a 3ds model
            extPos = curPath.rfind(".obj");
            if (extPos == string::npos)
            {
                // It's not an obj model either, so skip it.
                continue;
            }
            format = MODEL_OBJ;
        }
        else
        {
            // It's a 3ds model
            format = MODEL_3DS;
        }

        string name(curPath, namePos, extPos - namePos);
        ModelDescriptor* desc = new ModelDescriptor(name, format, curPath);
        ModelPrivate::modelMap.insert(std::make_pair(name, desc));
    }

    return ModelPrivate::modelMap;
}

/**
 * Load a model by name.
 *
 * You must initialize the available model collection using
 * Model::find_models() before using this method.
 *
 * @param modelName the model name
 *
 * @return whether the operation succeeded
 */
bool
Model::load(const string& modelName)
{
    bool retVal(false);
    ModelMap::const_iterator modelIt = ModelPrivate::modelMap.find(modelName);
    if (modelIt == ModelPrivate::modelMap.end())
    {
        return retVal;
    }

    ModelDescriptor* desc = modelIt->second;
    switch (desc->format())
    {
        case MODEL_INVALID:
            break;
        case MODEL_3DS:
            retVal = load_3ds(desc->pathname());
            break;
        case MODEL_OBJ:
            retVal = load_obj(desc->pathname());
            break;
    }

    return retVal;
}
