/***************************************************************************
 *   Copyright (c) 2009 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"

#ifndef _PreComp_
# include <algorithm>
# ifdef FC_OS_MACOSX
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
# else
# include <GL/gl.h>
# include <GL/glu.h>
# endif
# include <Inventor/actions/SoGLRenderAction.h>
# include <Inventor/bundles/SoMaterialBundle.h>
# include <Inventor/elements/SoCoordinateElement.h>
# include <Inventor/elements/SoGLCoordinateElement.h>
# include <Inventor/elements/SoMaterialBindingElement.h>
# include <Inventor/elements/SoProjectionMatrixElement.h>
# include <Inventor/elements/SoViewingMatrixElement.h>
#endif

#include <Gui/SoFCInteractiveElement.h>
#include <Gui/SoFCSelectionAction.h>
#include "SoFCIndexedFaceSet.h"

using namespace MeshGui;


SO_NODE_SOURCE(SoFCIndexedFaceSet);

void SoFCIndexedFaceSet::initClass()
{
    SO_NODE_INIT_CLASS(SoFCIndexedFaceSet, SoIndexedFaceSet, "IndexedFaceSet");
}

SoFCIndexedFaceSet::SoFCIndexedFaceSet() : renderTriangleLimit(100000), selectBuf(0)
{
    SO_NODE_CONSTRUCTOR(SoFCIndexedFaceSet);
    setName(SoFCIndexedFaceSet::getClassTypeId().getName());
}

/**
 * Either renders the complete mesh or only a subset of the points.
 */
void SoFCIndexedFaceSet::GLRender(SoGLRenderAction *action)
{
    if (this->coordIndex.getNum() < 3)
        return;

    if (!this->shouldGLRender(action))
        return;

    SoState * state = action->getState();
    SbBool mode = Gui::SoFCInteractiveElement::get(state);

    unsigned int num = this->coordIndex.getNum()/4;
    if (mode == false || num <= this->renderTriangleLimit) {
        inherited::GLRender(action);
    }
    else {
        SoMaterialBindingElement::Binding matbind =
            SoMaterialBindingElement::get(state);
        int32_t binding = (int32_t)(matbind);

        const SoCoordinateElement * coords;
        const SbVec3f * normals;
        const int32_t * cindices;
        int numindices;
        const int32_t * nindices;
        const int32_t * tindices;
        const int32_t * mindices;
        SbBool normalCacheUsed;

        SoMaterialBundle mb(action);

        SoTextureCoordinateBundle tb(action, true, false);
        SbBool sendNormals = !mb.isColorOnly() || tb.isFunction();

        this->getVertexData(state, coords, normals, cindices,
                            nindices, tindices, mindices, numindices,
                            sendNormals, normalCacheUsed);

        mb.sendFirst(); // make sure we have the correct material

        drawCoords(static_cast<const SoGLCoordinateElement*>(coords), cindices, numindices,
                   normals, nindices, &mb, mindices, binding, &tb, tindices);
        // Disable caching for this node
        SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DONT_AUTO_CACHE);
    }
}

void SoFCIndexedFaceSet::drawCoords(const SoGLCoordinateElement * const vertexlist,
                                    const int32_t *vertexindices,
                                    int numindices,
                                    const SbVec3f *normals,
                                    const int32_t *normalindices,
                                    SoMaterialBundle *materials,
                                    const int32_t *matindices,
                                    const int32_t binding,
                                    const SoTextureCoordinateBundle * const texcoords,
                                    const int32_t *texindices)
{
    const SbVec3f * coords3d = 0;
    coords3d = vertexlist->getArrayPtr3();

    int mod = numindices/(4*this->renderTriangleLimit)+1;
    float size = std::min<float>((float)mod,3.0f);
    glPointSize(size);

    SbBool per_face = false;
    SbBool per_vert = false;
    switch (binding) {
        case SoMaterialBindingElement::PER_FACE:
            per_face = true;
            break;
        case SoMaterialBindingElement::PER_VERTEX:
            per_vert = true;
            break;
        default:
            break;
    }

    int ct=0;
    const int32_t *viptr = vertexindices;
    int32_t v1, v2, v3;
    SbVec3f dummynormal(0,0,1);
    const SbVec3f *currnormal = &dummynormal;
    if (normals) currnormal = normals;

    glBegin(GL_POINTS);
    for (int index=0; index<numindices; ct++) {
        if (ct%mod==0) {
            if (per_face)
                materials->send(ct, true);
            v1 = *viptr++; index++;
            if (per_vert)
                materials->send(v1, true);
            if (normals)
                currnormal = &normals[*normalindices++];
            glNormal3fv((const GLfloat*)currnormal);
            glVertex3fv((const GLfloat*)(coords3d + v1));

            v2 = *viptr++; index++;
            if (per_vert)
                materials->send(v2, true);
            if (normals)
                currnormal = &normals[*normalindices++];
            glNormal3fv((const GLfloat*)currnormal);
            glVertex3fv((const GLfloat*)(coords3d + v2));

            v3 = *viptr++; index++;
            if (per_vert)
                materials->send(v3, true);
            if (normals)
                currnormal = &normals[*normalindices++];
            glNormal3fv((const GLfloat*)currnormal);
            glVertex3fv((const GLfloat*)(coords3d + v3));
        }
        else {
            viptr++; index++; normalindices++;
            viptr++; index++; normalindices++;
            viptr++; index++; normalindices++;
        }

        viptr++; index++; normalindices++;
    }
    glEnd();
}

void SoFCIndexedFaceSet::doAction(SoAction * action)
{
    if (action->getTypeId() == Gui::SoGLSelectAction::getClassTypeId()) {
        SoNode* node = action->getNodeAppliedTo();
        if (!node) return; // on no node applied

        // The node we have is the parent of this node and the coordinate node
        // thus we search there for it.
        SoSearchAction sa;
        sa.setInterest(SoSearchAction::FIRST);
        sa.setSearchingAll(false);
        sa.setType(SoCoordinate3::getClassTypeId(), 1);
        sa.apply(node);
        SoPath * path = sa.getPath();
        if (!path) return;

        // make sure we got the node we wanted
        SoNode* coords = path->getNodeFromTail(0);
        if (!(coords && coords->getTypeId().isDerivedFrom(SoCoordinate3::getClassTypeId())))
            return;
        startSelection(action);
        renderSelectionGeometry(static_cast<SoCoordinate3*>(coords)->point.getValues(0));
        stopSelection(action);
    }
    else if (action->getTypeId() == Gui::SoVisibleFaceAction::getClassTypeId()) {
        SoNode* node = action->getNodeAppliedTo();
        if (!node) return; // on no node applied

        // The node we have is the parent of this node and the coordinate node
        // thus we search there for it.
        SoSearchAction sa;
        sa.setInterest(SoSearchAction::FIRST);
        sa.setSearchingAll(false);
        sa.setType(SoCoordinate3::getClassTypeId(), 1);
        sa.apply(node);
        SoPath * path = sa.getPath();
        if (!path) return;

        // make sure we got the node we wanted
        SoNode* coords = path->getNodeFromTail(0);
        if (!(coords && coords->getTypeId().isDerivedFrom(SoCoordinate3::getClassTypeId())))
            return;
        startVisibility(action);
        renderVisibleFaces(static_cast<SoCoordinate3*>(coords)->point.getValues(0));
        stopVisibility(action);
    }

    inherited::doAction(action);
}

void SoFCIndexedFaceSet::startSelection(SoAction * action)
{
    Gui::SoGLSelectAction *doaction = static_cast<Gui::SoGLSelectAction*>(action);
    const SbViewportRegion& vp = doaction->getViewportRegion();
    int x = vp.getViewportOriginPixels()[0];
    int y = vp.getViewportOriginPixels()[1];
    int w = vp.getViewportSizePixels()[0];
    int h = vp.getViewportSizePixels()[1];

    int bufSize = 5*(this->coordIndex.getNum()/4); // make the buffer big enough
    this->selectBuf = new GLuint[bufSize];

    SbMatrix view = SoViewingMatrixElement::get(action->getState());
    SbMatrix proj = SoProjectionMatrixElement::get(action->getState());

    glSelectBuffer(bufSize, selectBuf);
    glRenderMode(GL_SELECT);

    glInitNames();
    glPushName(-1);

    //double mp[16];
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT,viewport);
    glMatrixMode(GL_PROJECTION);
    //glGetDoublev(GL_PROJECTION_MATRIX ,mp);
    glPushMatrix();
    glLoadIdentity();
    gluPickMatrix(x, y, w, h, viewport);
    glMultMatrixf(/*mp*/(float*)proj);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf((float*)view);
}

void SoFCIndexedFaceSet::stopSelection(SoAction * action)
{
    // restoring the original projection matrix
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glFlush();

    // returning to normal rendering mode
    GLint hits = glRenderMode(GL_RENDER);

    int bufSize = 5*(this->coordIndex.getNum()/4);
    std::vector< std::pair<double,unsigned int> > hit;
    GLint index=0;
    for (GLint ii=0;ii<hits && index<bufSize;ii++) {
        GLint ct = (GLint)selectBuf[index];
        hit.push_back(std::pair<double,unsigned int>
            (selectBuf[index+1]/4294967295.0,selectBuf[index+3]));
        index = index+ct+3;
    }

    delete [] selectBuf;
    selectBuf = 0;
    bool sorted = true;
    if(sorted) std::sort(hit.begin(),hit.end());

    Gui::SoGLSelectAction *doaction = static_cast<Gui::SoGLSelectAction*>(action);
    doaction->indices.reserve(hit.size());
    for (GLint ii=0;ii<hits;ii++) {
        doaction->indices.push_back(hit[ii].second);
    }
}

void SoFCIndexedFaceSet::renderSelectionGeometry(const SbVec3f * coords3d)
{
    int numfaces = this->coordIndex.getNum()/4;
    const int32_t * cindices = this->coordIndex.getValues(0);

    int fcnt=0;
    int32_t v1, v2, v3;
    for (int index=0; index<numfaces;index++,cindices++) {
        glLoadName(fcnt);
        glBegin(GL_TRIANGLES);
            v1 = *cindices++;
            glVertex3fv((const GLfloat*)(coords3d + v1));
            v2 = *cindices++;
            glVertex3fv((const GLfloat*)(coords3d + v2));
            v3 = *cindices++;
            glVertex3fv((const GLfloat*)(coords3d + v3));
        glEnd();
        fcnt++;
    }
}

void SoFCIndexedFaceSet::startVisibility(SoAction * action)
{
    SbMatrix view = SoViewingMatrixElement::get(action->getState());
    SbMatrix proj = SoProjectionMatrixElement::get(action->getState());

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMultMatrixf((float*)proj);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf((float*)view);
}

void SoFCIndexedFaceSet::stopVisibility(SoAction * action)
{
    // restoring the original projection matrix
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glFlush();
}

void SoFCIndexedFaceSet::renderVisibleFaces(const SbVec3f * coords3d)
{
    //GLint redBits, greenBits, blueBits;

    //glGetIntegerv (GL_RED_BITS, &redBits);
    //glGetIntegerv (GL_GREEN_BITS, &greenBits);
    //glGetIntegerv (GL_BLUE_BITS, &blueBits);
    glDisable (GL_BLEND);
    glDisable (GL_DITHER);
    glDisable (GL_FOG);
    glDisable (GL_LIGHTING);
    glDisable (GL_TEXTURE_1D);
    glDisable (GL_TEXTURE_2D);
    glShadeModel (GL_FLAT);

    uint32_t numfaces = this->coordIndex.getNum()/4;
    const int32_t * cindices = this->coordIndex.getValues(0);

    int32_t v1, v2, v3;
    for (uint32_t index=0; index<numfaces;index++,cindices++) {
        glBegin(GL_TRIANGLES);
            float t;
            SbColor c;
            c.setPackedValue(index<<8,t);
            glColor3f(c[0],c[1],c[2]);
            v1 = *cindices++;
            glVertex3fv((const GLfloat*)(coords3d + v1));
            v2 = *cindices++;
            glVertex3fv((const GLfloat*)(coords3d + v2));
            v3 = *cindices++;
            glVertex3fv((const GLfloat*)(coords3d + v3));
        glEnd();
    }
}
