#include "gpuCacheRasterSelect.h"
#include "gpuCacheSample.h"
#include "gpuCacheVBOProxy.h"
#include "gpuCacheDrawTraversal.h"
#include "gpuCacheGLFT.h"
#include "CacheReader.h"
#include <algorithm>
namespace {
        
using namespace GPUCache;
    
    
    
    class DrawWireframeState : public DrawTraversalState
    {
    public:
        DrawWireframeState(
            const Frustum&    frustrum,
            const double      seconds,
            VBOProxy::VBOMode vboMode)
            : DrawTraversalState(frustrum, seconds, kPruneNone),
              fVBOMode(vboMode)
        {}
        VBOProxy::VBOMode vboMode() const
        { return fVBOMode; }
    private:
        VBOProxy::VBOMode fVBOMode;
    };
        
    class DrawWireframeTraversal
        : public DrawTraversal<DrawWireframeTraversal, DrawWireframeState>
    {
    public:
        typedef DrawTraversal<DrawWireframeTraversal, DrawWireframeState> BaseClass;
        DrawWireframeTraversal(
            DrawWireframeState&     state,
            bool                    isReflection,
            Frustum::ClippingResult parentClippingResult)
            : BaseClass(state, xform, isReflection, parentClippingResult)
        {}
        
        void draw(const boost::shared_ptr<const ShapeSample>& sample)
        {
            if (!sample->visibility()) return;
            gGLFT->glLoadMatrixd(xform().matrix[0]);
            if (sample->isBoundingBoxPlaceHolder()) {
                state().vboProxy().drawBoundingBox(sample);
                GlobalReaderCache::theCache().hintShapeReadOrder(subNode());
                return;
            }
            assert(sample->positions());
            assert(sample->normals());
            
            
            
            
            
            state().vboProxy().drawWireframe(sample, state().vboMode());
            state().vboProxy().drawVertices(sample, state().vboMode());
        }
    };
    
    
    
    class DrawShadedState : public DrawTraversalState
    {
    public:
        DrawShadedState(
            const Frustum&    frustrum,
            const double      seconds,
            VBOProxy::VBOMode vboMode)
            : DrawTraversalState(frustrum, seconds, kPruneNone),
              fVBOMode(vboMode)
        {}
        VBOProxy::VBOMode vboMode() const
        { return fVBOMode; }
    private:
        VBOProxy::VBOMode fVBOMode;
    };
        
    class DrawShadedTraversal
        : public DrawTraversal<DrawShadedTraversal, DrawShadedState>
    {
    public:
        typedef DrawTraversal<DrawShadedTraversal, DrawShadedState> BaseClass;
        DrawShadedTraversal(
            DrawShadedState&        state,
            bool                    isReflection,
            Frustum::ClippingResult parentClippingResult)
            : BaseClass(state, xform, isReflection, parentClippingResult)
        {}
        
        void draw(const boost::shared_ptr<const ShapeSample>& sample)
        {
            if (!sample->visibility()) return;
            gGLFT->glLoadMatrixd(xform().matrix[0]);
            
            if (sample->isBoundingBoxPlaceHolder()) {
                state().vboProxy().drawBoundingBox(sample, true);
                GlobalReaderCache::theCache().hintShapeReadOrder(subNode());
                return;
            }
            assert(sample->positions());
            assert(sample->normals());
            
            
            
            
            
            const size_t numGroups = sample->numIndexGroups();
            for (size_t groupId = 0; groupId < numGroups; groupId++) {
                state().vboProxy().drawTriangles(
                    sample, groupId, VBOProxy::kNoNormals, VBOProxy::kNoUVs,
                    state().vboMode());
                state().vboProxy().drawVertices(sample, state().vboMode());
            }
        }
    };
}
namespace GPUCache {
#define MAX_RASTER_SELECT_RENDER_SIZE 16
RasterSelect::RasterSelect(
) 
    : fSelectInfo(selectInfo),
      fMinZ(std::numeric_limits<float>::max())
{
    unsigned int sxl, syl, sw, sh;
    fSelectInfo.selectRect(sxl, syl, sw, sh);
    unsigned int vxl, vyl, vw, vh;
    
    
    
    const unsigned int width = (MAX_RASTER_SELECT_RENDER_SIZE < sw) ?
        MAX_RASTER_SELECT_RENDER_SIZE : sw;
    const unsigned int height = (MAX_RASTER_SELECT_RENDER_SIZE < sh) ?
        MAX_RASTER_SELECT_RENDER_SIZE : sh;
    const double sx = double(width) / double(sw);
    const double sy = double(height) / double(sh);
    const double fx = 2.0 / double(vw);
    const double fy = 2.0 / double(vh);
    
    selectMatrix.
matrix[0][0] = sx;
    selectMatrix.
matrix[1][1] = sy;
    selectMatrix.
matrix[3][0] = -1.0 - sx * (fx * (sxl - vxl) - 1.0);
    selectMatrix.matrix[3][1] = -1.0 - sy * (fy * (syl - vyl) - 1.0);
    
        
    ::glMatrixMode(GL_PROJECTION);
    ::glPushMatrix();
    ::glLoadMatrixd(selectMatrix[0]);
    ::glMultMatrixd(projMatrix[0]);
    ::glMatrixMode(GL_MODELVIEW);
    ::glScissor(vxl, vyl, width, height);
    ::glEnable(GL_SCISSOR_TEST);
    ::glClear(GL_DEPTH_BUFFER_BIT);
    fWasDepthTestEnabled = ::glIsEnabled(GL_DEPTH_TEST);
    if (!fWasDepthTestEnabled) {
        ::glEnable(GL_DEPTH_TEST);
    }
}
RasterSelect::~RasterSelect() 
{}
void RasterSelect::processEdges(
    const SubNode::Ptr rootNode,
    double seconds,
    size_t ,
    VBOProxy::VBOMode vboMode
)
{
    unsigned int x, y, w, h;
    double viewportX = static_cast<int>(x);   
    double viewportY = static_cast<int>(y);   
    double viewportW = w;
    double viewportH = h;
    fSelectInfo.selectRect(x, y, w, h);
    double selectX = static_cast<int>(x);  
    double selectY = static_cast<int>(y);  
    double selectW = w;
    double selectH = h;
    selectAdjustMatrix[0][0] = viewportW / selectW;
    selectAdjustMatrix[1][1] = viewportH / selectH;
    selectAdjustMatrix[3][0] = ((viewportX + viewportW/2.0) - (selectX + selectW/2.0)) / 
        viewportW * 2.0 * selectAdjustMatrix[0][0];
    selectAdjustMatrix[3][1] = ((viewportY + viewportH/2.0) - (selectY + selectH/2.0)) /
        viewportH * 2.0 * selectAdjustMatrix[1][1];
    MMatrix localToPort = modelViewMatrix * projMatrix * selectAdjustMatrix;
 
    {
        Frustum frustum(localToPort.
inverse());
        
        DrawWireframeState state(frustum, seconds, vboMode);
        DrawWireframeTraversal traveral(state, xform, false, Frustum::kUnknown);
        rootNode->accept(traveral);
    }
}
void RasterSelect::processTriangles(
    const SubNode::Ptr rootNode,
    double seconds,
    size_t ,
    VBOProxy::VBOMode vboMode
)
{
    unsigned int x, y, w, h;
    double viewportX = static_cast<int>(x);   
    double viewportY = static_cast<int>(y);   
    double viewportW = w;
    double viewportH = h;
    fSelectInfo.selectRect(x, y, w, h);
    double selectX = static_cast<int>(x);  
    double selectY = static_cast<int>(y);  
    double selectW = w;
    double selectH = h;
    selectAdjustMatrix[0][0] = viewportW / selectW;
    selectAdjustMatrix[1][1] = viewportH / selectH;
    selectAdjustMatrix[3][0] = ((viewportX + viewportW/2.0) - (selectX + selectW/2.0)) / 
        viewportW * 2.0 * selectAdjustMatrix[0][0];
    selectAdjustMatrix[3][1] = ((viewportY + viewportH/2.0) - (selectY + selectH/2.0)) /
        viewportH * 2.0 * selectAdjustMatrix[1][1];
    MMatrix localToPort = modelViewMatrix * projMatrix * selectAdjustMatrix;
 
    {
        Frustum frustum(localToPort.
inverse());
        
        DrawShadedState state(frustum, seconds, vboMode);
        DrawShadedTraversal traveral(state, xform, false, Frustum::kUnknown);
        rootNode->accept(traveral);
    }
}
void RasterSelect::processBoundingBox(
    const SubNode::Ptr rootNode,
    double seconds
)
{
    
    
    assert(0);
}
void RasterSelect::end()
{
    
    unsigned int sxl, syl, sw, sh;
    fSelectInfo.selectRect(sxl, syl, sw, sh);
    unsigned int vxl, vyl, vw, vh;
    const unsigned int width = (MAX_RASTER_SELECT_RENDER_SIZE < sw) ?
        MAX_RASTER_SELECT_RENDER_SIZE : sw;
    const unsigned int height = (MAX_RASTER_SELECT_RENDER_SIZE < sh) ?
        MAX_RASTER_SELECT_RENDER_SIZE : sh;
    float* selDepth = new float[MAX_RASTER_SELECT_RENDER_SIZE*
                                MAX_RASTER_SELECT_RENDER_SIZE];
    
    GLint buffer;
    ::glGetIntegerv( GL_READ_BUFFER, &buffer );
    ::glReadBuffer( GL_BACK );
    ::glReadPixels(vxl, vyl, width, height,
                   GL_DEPTH_COMPONENT, GL_FLOAT, (void *)selDepth); 
    ::glReadBuffer( buffer );
    for (unsigned int j=0; j<height; ++j) {
        for (unsigned int i=0; i<width; ++i) {
            const GLfloat depth = selDepth[j*width + i];
            if (depth < 1.0f) {
                fMinZ = std::min(fMinZ, depth);
            }
        }   
    }
    ::glMatrixMode(GL_PROJECTION);
    ::glPopMatrix();
    ::glMatrixMode(GL_MODELVIEW);
    ::glDisable(GL_SCISSOR_TEST);
    if (!fWasDepthTestEnabled) {
        ::glDisable(GL_DEPTH_TEST);        
    }
}
bool RasterSelect::isSelected() const
{
    return fMinZ != std::numeric_limits<float>::max();
}
float RasterSelect::minZ() const
{
    return fMinZ;
}
}