// -*-c++-*-

#include "ReaderWriterVRML2.h"


#if defined(_MSC_VER)
#   pragma warning(disable: 4250)
#   pragma warning(disable: 4290)
#   pragma warning(disable: 4800)
#endif

#include <openvrml/node.h>

#include <osg/CullFace>

osg::ref_ptr<osg::Geometry> ReaderWriterVRML2::convertVRML97IndexedFaceSet(openvrml::node *vrml_ifs) const
{
    osg::ref_ptr<osg::Geometry> osg_geom = new osg::Geometry();

    osg_geom->addPrimitiveSet(new osg::DrawArrayLengths(osg::PrimitiveSet::POLYGON));

    // get array of vertex coordinate_nodes
    if(vrml_ifs->type().id() == "IndexedFaceSet")
    {
        std::auto_ptr<openvrml::field_value> fv = vrml_ifs->field("coord");
        const openvrml::sfnode *sfn = dynamic_cast<const openvrml::sfnode *>(fv.get());

        openvrml::coordinate_node *vrml_coord_node = dynamic_cast<openvrml::coordinate_node *>((sfn->value()).get());
        const std::vector<openvrml::vec3f> &vrml_coord = vrml_coord_node->point();

        osg::ref_ptr<osg::Vec3Array> osg_vertices = new osg::Vec3Array();

        unsigned i;
        for (i = 0; i < vrml_coord.size(); i++)
        {
            openvrml::vec3f vec = vrml_coord[i];
            osg_vertices->push_back(osg::Vec3(vec[0], vec[1], vec[2]));
        }

        osg_geom->setVertexArray(osg_vertices.get());

        // get array of vertex indices
        std::auto_ptr<openvrml::field_value> fv2 = vrml_ifs->field("coordIndex");
        const openvrml::mfint32 *vrml_coord_index = dynamic_cast<const openvrml::mfint32 *>(fv2.get());

        osg::ref_ptr<osg::IntArray> osg_vert_index = new osg::IntArray();

        int num_vert = 0;
        for (i = 0; i < vrml_coord_index->value().size(); i++)
        {
            int index = vrml_coord_index->value()[i];
            if (index == -1)
            {
                static_cast<osg::DrawArrayLengths*>(osg_geom->getPrimitiveSet(0))->push_back(num_vert);
                num_vert = 0;
            }
            else
            {
                osg_vert_index->push_back(index);
                ++num_vert;
            }
        }

        if (num_vert)
        {
            //GvdB: Last coordIndex wasn't -1
            static_cast<osg::DrawArrayLengths*>(osg_geom->getPrimitiveSet(0))->push_back(num_vert);
        }

        osg_geom->setVertexIndices(osg_vert_index.get());
    }

    {
        // get texture coordinate_nodes
        std::auto_ptr<openvrml::field_value> fv = vrml_ifs->field("texCoord");
        const openvrml::sfnode *sfn = dynamic_cast<const openvrml::sfnode *>(fv.get());
        openvrml::texture_coordinate_node *vrml_tex_coord_node = dynamic_cast<openvrml::texture_coordinate_node *>(sfn->value().get());

        if (vrml_tex_coord_node != 0) // if no texture, node is NULL pointer
        {
            const std::vector<openvrml::vec2f> &vrml_tex_coord = vrml_tex_coord_node->point();
            osg::ref_ptr<osg::Vec2Array> osg_texcoords = new osg::Vec2Array();

            unsigned i;
            for (i = 0; i < vrml_tex_coord.size(); i++)
            {
                openvrml::vec2f vec = vrml_tex_coord[i];
                osg_texcoords->push_back(osg::Vec2(vec[0], vec[1]));
            }

            osg_geom->setTexCoordArray(0, osg_texcoords.get());

            // get array of texture indices
            std::auto_ptr<openvrml::field_value> fv2 = vrml_ifs->field("texCoordIndex");
            const openvrml::mfint32 *vrml_tex_coord_index = dynamic_cast<const openvrml::mfint32 *>(fv2.get());

            osg::ref_ptr<osg::IntArray> osg_tex_coord_index = new osg::IntArray();

            if(vrml_tex_coord_index->value().size() > 0)
            {
                for (i = 0; i < vrml_tex_coord_index->value().size(); i++)
                {
                    int index = vrml_tex_coord_index->value()[i];
                    if (index != -1) {
                        osg_tex_coord_index->push_back(index);
                    }
                }
                osg_geom->setTexCoordIndices(0, osg_tex_coord_index.get());
            } else
                // no indices defined, use coordIndex
                osg_geom->setTexCoordIndices(0, osg_geom->getVertexIndices());
        }
    }

    // get array of normals per vertex (if specified)
    {
        std::auto_ptr<openvrml::field_value> fv = vrml_ifs->field("normal");
        const openvrml::sfnode *sfn = dynamic_cast<const openvrml::sfnode *>(fv.get());
        openvrml::normal_node *vrml_normal_node = dynamic_cast<openvrml::normal_node *>(sfn->value().get());

        if (vrml_normal_node != 0) // if no normals, node is NULL pointer
        {
            const std::vector<openvrml::vec3f>& vrml_normal_coord = vrml_normal_node->vector();

            osg::ref_ptr<osg::Vec3Array> osg_normalcoords = new osg::Vec3Array();

            unsigned i;
            for (i = 0; i < vrml_normal_coord.size(); i++)
            {
                const openvrml::vec3f vec = vrml_normal_coord[i];
                osg_normalcoords->push_back(osg::Vec3(vec[0], vec[1], vec[2]));
            }
            osg_geom->setNormalArray(osg_normalcoords.get());

            // get array of normal indices
            std::auto_ptr<openvrml::field_value> fv2 = vrml_ifs->field("normalIndex");
            const openvrml::mfint32 *vrml_normal_index = dynamic_cast<const openvrml::mfint32 *>(fv2.get());

            osg::ref_ptr<osg::IntArray> osg_normal_index = new osg::IntArray();

            if (vrml_normal_index->value().size() > 0)
            {
                for (i = 0; i < vrml_normal_index->value().size(); i++)
                {
                    int index = vrml_normal_index->value()[i];
                    if (index != -1)
                    {
                        osg_normal_index->push_back(index);
                    }
                }
                osg_geom->setNormalIndices(osg_normal_index.get());
            }
            else
                // unspecified, use the coordIndex field
                osg_geom->setNormalIndices(osg_geom->getVertexIndices());

            // get normal binding
            std::auto_ptr<openvrml::field_value> fv3 = vrml_ifs->field("normalPerVertex");
            const openvrml::sfbool *vrml_norm_per_vertex = dynamic_cast<const openvrml::sfbool *>(fv3.get());

            if (vrml_norm_per_vertex->value())
            {
                osg_geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
            } else
            {
                osg_geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
            }
        }
    }

    // get array of colours per vertex (if specified)
    {
        std::auto_ptr<openvrml::field_value> fv = vrml_ifs->field("color");
        const openvrml::sfnode *sfn = dynamic_cast<const openvrml::sfnode *>(fv.get());
        openvrml::color_node *vrml_color_node = dynamic_cast<openvrml::color_node *>(sfn->value().get());

        if (vrml_color_node != 0) // if no colors, node is NULL pointer
        {
            const std::vector<openvrml::color> &vrml_colors = vrml_color_node->color();

            osg::ref_ptr<osg::Vec3Array> osg_colors = new osg::Vec3Array();

            unsigned i;
            for (i = 0; i < vrml_colors.size(); i++)
            {
                const openvrml::color color = vrml_colors[i];
                osg_colors->push_back(osg::Vec3(color.r(), color.g(), color.b()));
            }
            osg_geom->setColorArray(osg_colors.get());

            // get array of color indices
            std::auto_ptr<openvrml::field_value> fv2 = vrml_ifs->field("colorIndex");
            const openvrml::mfint32 *vrml_color_index = dynamic_cast<const openvrml::mfint32 *>(fv2.get());

            osg::ref_ptr<osg::IntArray> osg_color_index = new osg::IntArray();

            if(vrml_color_index->value().size() > 0)
            {
                for (i = 0; i < vrml_color_index->value().size(); i++)
                {
                    int index = vrml_color_index->value()[i];
                    if (index != -1) {
                        osg_color_index->push_back(index);
                    }
                }
                osg_geom->setColorIndices(osg_color_index.get());
            } else
                // unspecified, use coordIndices field
                osg_geom->setColorIndices(osg_geom->getVertexIndices());

            // get color binding
            std::auto_ptr<openvrml::field_value> fv3 = vrml_ifs->field("colorPerVertex");
            const openvrml::sfbool *vrml_color_per_vertex = dynamic_cast<const openvrml::sfbool *>(fv3.get());

            if (vrml_color_per_vertex->value())
            {
                osg_geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
            } else
            {
                osg_geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE);
            }
        }
    }


    // normal smoothing
    std::auto_ptr<openvrml::field_value> fv_solid = vrml_ifs->field("solid");
    const openvrml::sfbool *solid = dynamic_cast<const openvrml::sfbool *>(fv_solid.get());
    if (solid->value())
    {
        osg_geom->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));
    }

    if (!osg_geom->getNormalArray())
    {
#if 0
        // GvdB: This is what I wanted to do, but got zero normals since the triangles were considered temporaries (?)
        osgUtil::SmoothingVisitor().smooth(*osg_geom);
#else
        // GvdB: So I ended up computing the smoothing normals myself. Also, I might add support for "creaseAngle" if a big need for it rises.
        //       However, for now I can perfectly live with the fact that all edges are smoothed despite the use of a crease angle.
        osg::Vec3Array& coords = *static_cast<osg::Vec3Array*>(osg_geom->getVertexArray());
        assert(coords.size());

        osg::Vec3Array* normals = new osg::Vec3Array(coords.size());

        for (osg::Vec3Array::iterator it = normals->begin(); it != normals->end(); ++it)
        {
            (*it).set(0.0f, 0.0f, 0.0f);
        }


        osg::IntArray& indices = *static_cast<osg::IntArray*>(osg_geom->getVertexIndices());
        osg::DrawArrayLengths& lengths = *static_cast<osg::DrawArrayLengths*>(osg_geom->getPrimitiveSet(0));
        unsigned index = 0;

        for (osg::DrawArrayLengths::iterator it = lengths.begin(); it != lengths.end(); ++it)
        {
            assert(*it >= 3);
            const osg::Vec3& v0 = coords[indices[index]];
            const osg::Vec3& v1 = coords[indices[index + 1]];
            const osg::Vec3& v2 = coords[indices[index + 2]];

            osg::Vec3 normal = (v1 - v0) ^ (v2 - v0);
            normal.normalize();

            for (int i = 0; i != *it; ++i)
            {
                (*normals)[indices[index + i]] += normal;
            }

            index += *it;
        }

        assert(index == indices.size());

        for(osg::Vec3Array::iterator it = normals->begin(); it != normals->end(); ++it)
        {
            (*it).normalize();
        }

        osg_geom->setNormalArray(normals);
        osg_geom->setNormalIndices(osg_geom->getVertexIndices());
        osg_geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

#endif
    }

    osg::DrawArrayLengths& lengths = *static_cast<osg::DrawArrayLengths*>(osg_geom->getPrimitiveSet(0));

    osg::DrawArrayLengths::iterator it = lengths.begin();
    if (it != lengths.end())
    {
        switch (*it)
        {
        case 3:
            while (++it != lengths.end() && *it == 3)
                ;

            if (it == lengths.end())
            {
                // All polys are triangles
                osg::ref_ptr<osg::DrawArrays> mesh = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES);
                mesh->setCount(lengths.size() * 3);
                osg_geom->removePrimitiveSet(0);
                osg_geom->addPrimitiveSet(mesh.get());
            }
            break;

        case 4:
            while (++it != lengths.end() && *it == 4)
                ;

            if (it == lengths.end())
            {
                // All polys are quads
                osg::ref_ptr<osg::DrawArrays> mesh = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
                mesh->setCount(lengths.size() * 4);
                osg_geom->removePrimitiveSet(0);
                osg_geom->addPrimitiveSet(mesh.get());
            }

            break;
        }
    }

    return osg_geom.get();
}
