
/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */


#include "gl/GrGLInterface.h"
#include "GrDebugGL.h"
#include "GrShaderObj.h"
#include "GrProgramObj.h"
#include "GrBufferObj.h"
#include "GrTextureUnitObj.h"
#include "GrTextureObj.h"
#include "GrFrameBufferObj.h"
#include "GrRenderBufferObj.h"
#include "GrVertexArrayObj.h"
#include "SkFloatingPoint.h"
#include "../GrGLNoOpInterface.h"

namespace { // suppress no previous prototype warning

////////////////////////////////////////////////////////////////////////////////
GrGLvoid GR_GL_FUNCTION_TYPE debugGLActiveTexture(GrGLenum texture) {

    // Ganesh offsets the texture unit indices
    texture -= GR_GL_TEXTURE0;
    GrAlwaysAssert(texture < GrDebugGL::getInstance()->getMaxTextureUnits());

    GrDebugGL::getInstance()->setCurTextureUnit(texture);
}

////////////////////////////////////////////////////////////////////////////////
GrGLvoid GR_GL_FUNCTION_TYPE debugGLAttachShader(GrGLuint programID,
                                                 GrGLuint shaderID) {

    GrProgramObj *program = GR_FIND(programID, GrProgramObj,
                                    GrDebugGL::kProgram_ObjTypes);
    GrAlwaysAssert(program);

    GrShaderObj *shader = GR_FIND(shaderID,
                                  GrShaderObj,
                                  GrDebugGL::kShader_ObjTypes);
    GrAlwaysAssert(shader);

    program->AttachShader(shader);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLBeginQuery(GrGLenum target, GrGLuint id) {
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindAttribLocation(GrGLuint program,
                                                       GrGLuint index,
                                                       const char* name) {
}

////////////////////////////////////////////////////////////////////////////////
GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindTexture(GrGLenum target,
                                                GrGLuint textureID) {

    // we don't use cube maps
    GrAlwaysAssert(target == GR_GL_TEXTURE_2D);
                                    // || target == GR_GL_TEXTURE_CUBE_MAP);

    // a textureID of 0 is acceptable - it binds to the default texture target
    GrTextureObj *texture = GR_FIND(textureID, GrTextureObj,
                                    GrDebugGL::kTexture_ObjTypes);

    GrDebugGL::getInstance()->setTexture(texture);
}


////////////////////////////////////////////////////////////////////////////////
GrGLvoid GR_GL_FUNCTION_TYPE debugGLBufferData(GrGLenum target,
                                               GrGLsizeiptr size,
                                               const GrGLvoid* data,
                                               GrGLenum usage) {
    GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
                   GR_GL_ELEMENT_ARRAY_BUFFER == target);
    GrAlwaysAssert(size >= 0);
    GrAlwaysAssert(GR_GL_STREAM_DRAW == usage ||
                   GR_GL_STATIC_DRAW == usage ||
                   GR_GL_DYNAMIC_DRAW == usage);

    GrBufferObj *buffer = NULL;
    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getArrayBuffer();
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
            break;
        default:
            SkFAIL("Unexpected target to glBufferData");
            break;
    }

    GrAlwaysAssert(buffer);
    GrAlwaysAssert(buffer->getBound());

    buffer->allocate(size, reinterpret_cast<const GrGLchar *>(data));
    buffer->setUsage(usage);
}


GrGLvoid GR_GL_FUNCTION_TYPE debugGLPixelStorei(GrGLenum pname,
                                                GrGLint param) {

    switch (pname) {
        case GR_GL_UNPACK_ROW_LENGTH:
            GrDebugGL::getInstance()->setUnPackRowLength(param);
            break;
        case GR_GL_PACK_ROW_LENGTH:
            GrDebugGL::getInstance()->setPackRowLength(param);
            break;
        case GR_GL_UNPACK_ALIGNMENT:
            break;
        case GR_GL_PACK_ALIGNMENT:
            GrAlwaysAssert(false);
            break;
        default:
            GrAlwaysAssert(false);
            break;
    }
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLReadPixels(GrGLint x,
                                               GrGLint y,
                                               GrGLsizei width,
                                               GrGLsizei height,
                                               GrGLenum format,
                                               GrGLenum type,
                                               GrGLvoid* pixels) {

    GrGLint pixelsInRow = width;
    if (0 < GrDebugGL::getInstance()->getPackRowLength()) {
        pixelsInRow = GrDebugGL::getInstance()->getPackRowLength();
    }

    GrGLint componentsPerPixel = 0;

    switch (format) {
        case GR_GL_RGBA:
            // fallthrough
        case GR_GL_BGRA:
            componentsPerPixel = 4;
            break;
        case GR_GL_RGB:
            componentsPerPixel = 3;
            break;
        case GR_GL_RED:
            componentsPerPixel = 1;
            break;
        default:
            GrAlwaysAssert(false);
            break;
    }

    GrGLint alignment = 4;  // the pack alignment (one of 1, 2, 4 or 8)
    // Ganesh currently doesn't support setting GR_GL_PACK_ALIGNMENT

    GrGLint componentSize = 0;  // size (in bytes) of a single component

    switch (type) {
        case GR_GL_UNSIGNED_BYTE:
            componentSize = 1;
            break;
        default:
            GrAlwaysAssert(false);
            break;
    }

    GrGLint rowStride = 0;  // number of components (not bytes) to skip
    if (componentSize >= alignment) {
        rowStride = componentsPerPixel * pixelsInRow;
    } else {
        float fTemp =
            sk_float_ceil(componentSize * componentsPerPixel * pixelsInRow /
                          static_cast<float>(alignment));
        rowStride = static_cast<GrGLint>(alignment * fTemp / componentSize);
    }

    GrGLchar *scanline = static_cast<GrGLchar *>(pixels);
    for (int y = 0; y < height; ++y) {
        memset(scanline, 0, componentsPerPixel * componentSize * width);
        scanline += rowStride;
    }
}

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLUseProgram(GrGLuint programID) {

     // A programID of 0 is legal
     GrProgramObj *program = GR_FIND(programID,
                                     GrProgramObj,
                                     GrDebugGL::kProgram_ObjTypes);

     GrDebugGL::getInstance()->useProgram(program);
 }

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindFramebuffer(GrGLenum target,
                                                     GrGLuint frameBufferID) {

     GrAlwaysAssert(GR_GL_FRAMEBUFFER == target ||
                    GR_GL_READ_FRAMEBUFFER == target ||
                    GR_GL_DRAW_FRAMEBUFFER);

     // a frameBufferID of 0 is acceptable - it binds to the default
     // frame buffer
     GrFrameBufferObj *frameBuffer = GR_FIND(frameBufferID,
                                             GrFrameBufferObj,
                                             GrDebugGL::kFrameBuffer_ObjTypes);

     GrDebugGL::getInstance()->setFrameBuffer(frameBuffer);
 }

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindRenderbuffer(GrGLenum target, GrGLuint renderBufferID) {

     GrAlwaysAssert(GR_GL_RENDERBUFFER == target);

     // a renderBufferID of 0 is acceptable - it unbinds the bound render buffer
     GrRenderBufferObj *renderBuffer = GR_FIND(renderBufferID,
                                               GrRenderBufferObj,
                                               GrDebugGL::kRenderBuffer_ObjTypes);

     GrDebugGL::getInstance()->setRenderBuffer(renderBuffer);
 }

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteTextures(GrGLsizei n, const GrGLuint* textures) {

     // first potentially unbind the texture
     // TODO: move this into GrDebugGL as unBindTexture?
     for (unsigned int i = 0;
          i < GrDebugGL::getInstance()->getMaxTextureUnits();
          ++i) {
         GrTextureUnitObj *pTU = GrDebugGL::getInstance()->getTextureUnit(i);

         if (pTU->getTexture()) {
             for (int j = 0; j < n; ++j) {

                 if (textures[j] == pTU->getTexture()->getID()) {
                     // this ID is the current texture - revert the binding to 0
                     pTU->setTexture(NULL);
                 }
             }
         }
     }

     // TODO: fuse the following block with DeleteRenderBuffers?
     // Open GL will remove a deleted render buffer from the active
     // frame buffer but not from any other frame buffer
     if (GrDebugGL::getInstance()->getFrameBuffer()) {

         GrFrameBufferObj *frameBuffer = GrDebugGL::getInstance()->getFrameBuffer();

         for (int i = 0; i < n; ++i) {

             if (frameBuffer->getColor() &&
                 textures[i] == frameBuffer->getColor()->getID()) {
                 frameBuffer->setColor(NULL);
             }
             if (frameBuffer->getDepth() &&
                 textures[i] == frameBuffer->getDepth()->getID()) {
                 frameBuffer->setDepth(NULL);
             }
             if (frameBuffer->getStencil() &&
                 textures[i] == frameBuffer->getStencil()->getID()) {
                 frameBuffer->setStencil(NULL);
             }
         }
     }

     // then actually "delete" the buffers
     for (int i = 0; i < n; ++i) {
         GrTextureObj *buffer = GR_FIND(textures[i],
                                        GrTextureObj,
                                        GrDebugGL::kTexture_ObjTypes);
         GrAlwaysAssert(buffer);

         // OpenGL gives no guarantees if a texture is deleted while attached to
         // something other than the currently bound frame buffer
         GrAlwaysAssert(!buffer->getBound());

         GrAlwaysAssert(!buffer->getDeleted());
         buffer->deleteAction();
     }

 }

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteFramebuffers(GrGLsizei n,
                                                        const GrGLuint *frameBuffers) {

     // first potentially unbind the buffers
     if (GrDebugGL::getInstance()->getFrameBuffer()) {
         for (int i = 0; i < n; ++i) {

             if (frameBuffers[i] ==
                 GrDebugGL::getInstance()->getFrameBuffer()->getID()) {
                 // this ID is the current frame buffer - rebind to the default
                 GrDebugGL::getInstance()->setFrameBuffer(NULL);
             }
         }
     }

     // then actually "delete" the buffers
     for (int i = 0; i < n; ++i) {
         GrFrameBufferObj *buffer = GR_FIND(frameBuffers[i],
                                            GrFrameBufferObj,
                                            GrDebugGL::kFrameBuffer_ObjTypes);
         GrAlwaysAssert(buffer);

         GrAlwaysAssert(!buffer->getDeleted());
         buffer->deleteAction();
     }
 }

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteRenderbuffers(GrGLsizei n,
                                                         const GrGLuint *renderBuffers) {

     // first potentially unbind the buffers
     if (GrDebugGL::getInstance()->getRenderBuffer()) {
         for (int i = 0; i < n; ++i) {

             if (renderBuffers[i] ==
                 GrDebugGL::getInstance()->getRenderBuffer()->getID()) {
                 // this ID is the current render buffer - make no
                 // render buffer be bound
                 GrDebugGL::getInstance()->setRenderBuffer(NULL);
             }
         }
     }

     // TODO: fuse the following block with DeleteTextures?
     // Open GL will remove a deleted render buffer from the active frame
     // buffer but not from any other frame buffer
     if (GrDebugGL::getInstance()->getFrameBuffer()) {

         GrFrameBufferObj *frameBuffer =
                               GrDebugGL::getInstance()->getFrameBuffer();

         for (int i = 0; i < n; ++i) {

             if (frameBuffer->getColor() &&
                 renderBuffers[i] == frameBuffer->getColor()->getID()) {
                 frameBuffer->setColor(NULL);
             }
             if (frameBuffer->getDepth() &&
                 renderBuffers[i] == frameBuffer->getDepth()->getID()) {
                 frameBuffer->setDepth(NULL);
             }
             if (frameBuffer->getStencil() &&
                 renderBuffers[i] == frameBuffer->getStencil()->getID()) {
                 frameBuffer->setStencil(NULL);
             }
         }
     }

     // then actually "delete" the buffers
     for (int i = 0; i < n; ++i) {
         GrRenderBufferObj *buffer = GR_FIND(renderBuffers[i],
                                             GrRenderBufferObj,
                                             GrDebugGL::kRenderBuffer_ObjTypes);
         GrAlwaysAssert(buffer);

         // OpenGL gives no guarantees if a render buffer is deleted
         // while attached to something other than the currently
         // bound frame buffer
         GrAlwaysAssert(!buffer->getColorBound());
         GrAlwaysAssert(!buffer->getDepthBound());
         // However, at GrContext destroy time we release all GrRsources and so stencil buffers
         // may get deleted before FBOs that refer to them.
         //GrAlwaysAssert(!buffer->getStencilBound());

         GrAlwaysAssert(!buffer->getDeleted());
         buffer->deleteAction();
     }
 }

 GrGLvoid GR_GL_FUNCTION_TYPE debugGLFramebufferRenderbuffer(GrGLenum target,
                                                             GrGLenum attachment,
                                                             GrGLenum renderbuffertarget,
                                                             GrGLuint renderBufferID) {

     GrAlwaysAssert(GR_GL_FRAMEBUFFER == target);
     GrAlwaysAssert(GR_GL_COLOR_ATTACHMENT0 == attachment ||
                    GR_GL_DEPTH_ATTACHMENT == attachment ||
                    GR_GL_STENCIL_ATTACHMENT == attachment);
     GrAlwaysAssert(GR_GL_RENDERBUFFER == renderbuffertarget);

     GrFrameBufferObj *framebuffer = GrDebugGL::getInstance()->getFrameBuffer();
     // A render buffer cannot be attached to the default framebuffer
     GrAlwaysAssert(framebuffer);

     // a renderBufferID of 0 is acceptable - it unbinds the current
     // render buffer
     GrRenderBufferObj *renderbuffer = GR_FIND(renderBufferID,
                                               GrRenderBufferObj,
                                               GrDebugGL::kRenderBuffer_ObjTypes);

     switch (attachment) {
     case GR_GL_COLOR_ATTACHMENT0:
         framebuffer->setColor(renderbuffer);
         break;
     case GR_GL_DEPTH_ATTACHMENT:
         framebuffer->setDepth(renderbuffer);
         break;
     case GR_GL_STENCIL_ATTACHMENT:
         framebuffer->setStencil(renderbuffer);
         break;
     default:
         GrAlwaysAssert(false);
         break;
     };

 }

 ////////////////////////////////////////////////////////////////////////////////
 GrGLvoid GR_GL_FUNCTION_TYPE debugGLFramebufferTexture2D(GrGLenum target,
                                                          GrGLenum attachment,
                                                          GrGLenum textarget,
                                                          GrGLuint textureID,
                                                          GrGLint level) {

     GrAlwaysAssert(GR_GL_FRAMEBUFFER == target);
     GrAlwaysAssert(GR_GL_COLOR_ATTACHMENT0 == attachment ||
                    GR_GL_DEPTH_ATTACHMENT == attachment ||
                    GR_GL_STENCIL_ATTACHMENT == attachment);
     GrAlwaysAssert(GR_GL_TEXTURE_2D == textarget);

     GrFrameBufferObj *framebuffer = GrDebugGL::getInstance()->getFrameBuffer();
     // A texture cannot be attached to the default framebuffer
     GrAlwaysAssert(framebuffer);

     // A textureID of 0 is allowed - it unbinds the currently bound texture
     GrTextureObj *texture = GR_FIND(textureID, GrTextureObj,
                                     GrDebugGL::kTexture_ObjTypes);
     if (texture) {
         // The texture shouldn't be bound to a texture unit - this
         // could lead to a feedback loop
         GrAlwaysAssert(!texture->getBound());
     }

     GrAlwaysAssert(0 == level);

     switch (attachment) {
     case GR_GL_COLOR_ATTACHMENT0:
         framebuffer->setColor(texture);
         break;
     case GR_GL_DEPTH_ATTACHMENT:
         framebuffer->setDepth(texture);
         break;
     case GR_GL_STENCIL_ATTACHMENT:
         framebuffer->setStencil(texture);
         break;
     default:
         GrAlwaysAssert(false);
         break;
     };
 }

GrGLuint GR_GL_FUNCTION_TYPE debugGLCreateProgram() {

    GrProgramObj *program = GR_CREATE(GrProgramObj,
                                      GrDebugGL::kProgram_ObjTypes);

    return program->getID();
}

GrGLuint GR_GL_FUNCTION_TYPE debugGLCreateShader(GrGLenum type) {

    GrAlwaysAssert(GR_GL_VERTEX_SHADER == type ||
                   GR_GL_FRAGMENT_SHADER == type);

    GrShaderObj *shader = GR_CREATE(GrShaderObj, GrDebugGL::kShader_ObjTypes);
    shader->setType(type);

    return shader->getID();
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteProgram(GrGLuint programID) {

    GrProgramObj *program = GR_FIND(programID,
                                    GrProgramObj,
                                    GrDebugGL::kProgram_ObjTypes);
    GrAlwaysAssert(program);

    if (program->getRefCount()) {
        // someone is still using this program so we can't delete it here
        program->setMarkedForDeletion();
    } else {
        program->deleteAction();
    }
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteShader(GrGLuint shaderID) {

    GrShaderObj *shader = GR_FIND(shaderID,
                                  GrShaderObj,
                                  GrDebugGL::kShader_ObjTypes);
    GrAlwaysAssert(shader);

    if (shader->getRefCount()) {
        // someone is still using this shader so we can't delete it here
        shader->setMarkedForDeletion();
    } else {
        shader->deleteAction();
    }
}

GrGLvoid debugGenObjs(GrDebugGL::GrObjTypes type,
                      GrGLsizei n,
                      GrGLuint* ids) {

   for (int i = 0; i < n; ++i) {
        GrFakeRefObj *obj = GrDebugGL::getInstance()->createObj(type);
        GrAlwaysAssert(obj);
        ids[i] = obj->getID();
    }
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenBuffers(GrGLsizei n, GrGLuint* ids) {
    debugGenObjs(GrDebugGL::kBuffer_ObjTypes, n, ids);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenerateMipmap(GrGLenum level) {
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenFramebuffers(GrGLsizei n,
                                                    GrGLuint* ids) {
    debugGenObjs(GrDebugGL::kFrameBuffer_ObjTypes, n, ids);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenRenderbuffers(GrGLsizei n,
                                                     GrGLuint* ids) {
    debugGenObjs(GrDebugGL::kRenderBuffer_ObjTypes, n, ids);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenTextures(GrGLsizei n, GrGLuint* ids) {
    debugGenObjs(GrDebugGL::kTexture_ObjTypes, n, ids);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenVertexArrays(GrGLsizei n, GrGLuint* ids) {
    debugGenObjs(GrDebugGL::kVertexArray_ObjTypes, n, ids);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteVertexArrays(GrGLsizei n, const GrGLuint* ids) {
    for (GrGLsizei i = 0; i < n; ++i) {
        GrVertexArrayObj* array =
            GR_FIND(ids[i], GrVertexArrayObj, GrDebugGL::kVertexArray_ObjTypes);
        GrAlwaysAssert(array);

        // Deleting the current vertex array binds object 0
        if (GrDebugGL::getInstance()->getVertexArray() == array) {
            GrDebugGL::getInstance()->setVertexArray(NULL);
        }

        if (array->getRefCount()) {
            // someone is still using this vertex array so we can't delete it here
            array->setMarkedForDeletion();
        } else {
            array->deleteAction();
        }
    }
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindVertexArray(GrGLuint id) {
    GrVertexArrayObj* array = GR_FIND(id, GrVertexArrayObj, GrDebugGL::kVertexArray_ObjTypes);
    GrAlwaysAssert((0 == id) || array);
    GrDebugGL::getInstance()->setVertexArray(array);
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindBuffer(GrGLenum target, GrGLuint bufferID) {
    GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target || GR_GL_ELEMENT_ARRAY_BUFFER == target);

    GrBufferObj *buffer = GR_FIND(bufferID,
                                  GrBufferObj,
                                  GrDebugGL::kBuffer_ObjTypes);
    // 0 is a permissible bufferID - it unbinds the current buffer

    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            GrDebugGL::getInstance()->setArrayBuffer(buffer);
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            GrDebugGL::getInstance()->setElementArrayBuffer(buffer);
            break;
        default:
            SkFAIL("Unexpected target to glBindBuffer");
            break;
    }
}

// deleting a bound buffer has the side effect of binding 0
GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteBuffers(GrGLsizei n, const GrGLuint* ids) {
    // first potentially unbind the buffers
    for (int i = 0; i < n; ++i) {

        if (GrDebugGL::getInstance()->getArrayBuffer() &&
            ids[i] == GrDebugGL::getInstance()->getArrayBuffer()->getID()) {
            // this ID is the current array buffer
            GrDebugGL::getInstance()->setArrayBuffer(NULL);
        }
        if (GrDebugGL::getInstance()->getElementArrayBuffer() &&
            ids[i] ==
                GrDebugGL::getInstance()->getElementArrayBuffer()->getID()) {
            // this ID is the current element array buffer
            GrDebugGL::getInstance()->setElementArrayBuffer(NULL);
        }
    }

    // then actually "delete" the buffers
    for (int i = 0; i < n; ++i) {
        GrBufferObj *buffer = GR_FIND(ids[i],
                                      GrBufferObj,
                                      GrDebugGL::kBuffer_ObjTypes);
        GrAlwaysAssert(buffer);

        GrAlwaysAssert(!buffer->getDeleted());
        buffer->deleteAction();
    }
}

// map a buffer to the caller's address space
GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBufferRange(GrGLenum target, GrGLintptr offset,
                                                    GrGLsizeiptr length, GrGLbitfield access) {
    GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
                   GR_GL_ELEMENT_ARRAY_BUFFER == target);

    // We only expect read access and we expect that the buffer or range is always invalidated.
    GrAlwaysAssert(!SkToBool(GR_GL_MAP_READ_BIT & access));
    GrAlwaysAssert((GR_GL_MAP_INVALIDATE_BUFFER_BIT | GR_GL_MAP_INVALIDATE_RANGE_BIT) & access);

    GrBufferObj *buffer = NULL;
    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getArrayBuffer();
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
            break;
        default:
            SkFAIL("Unexpected target to glMapBufferRange");
            break;
    }

    if (buffer) {
        GrAlwaysAssert(offset >= 0 && offset + length <= buffer->getSize());
        GrAlwaysAssert(!buffer->getMapped());
        buffer->setMapped(offset, length);
        return buffer->getDataPtr() + offset;
    }

    GrAlwaysAssert(false);
    return NULL;        // no buffer bound to the target
}

GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBuffer(GrGLenum target, GrGLenum access) {
    GrAlwaysAssert(GR_GL_WRITE_ONLY == access);

    GrBufferObj *buffer = NULL;
    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getArrayBuffer();
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
            break;
        default:
            SkFAIL("Unexpected target to glMapBuffer");
            break;
    }

    return debugGLMapBufferRange(target, 0, buffer->getSize(),
                                 GR_GL_MAP_WRITE_BIT | GR_GL_MAP_INVALIDATE_BUFFER_BIT);
}

// remove a buffer from the caller's address space
// TODO: check if the "access" method from "glMapBuffer" was honored
GrGLboolean GR_GL_FUNCTION_TYPE debugGLUnmapBuffer(GrGLenum target) {

    GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
                   GR_GL_ELEMENT_ARRAY_BUFFER == target);

    GrBufferObj *buffer = NULL;
    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getArrayBuffer();
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
            break;
        default:
            SkFAIL("Unexpected target to glUnmapBuffer");
            break;
    }

    if (buffer) {
        GrAlwaysAssert(buffer->getMapped());
        buffer->resetMapped();
        return GR_GL_TRUE;
    }

    GrAlwaysAssert(false);
    return GR_GL_FALSE; // GR_GL_INVALID_OPERATION;
}

GrGLvoid GR_GL_FUNCTION_TYPE debugGLFlushMappedBufferRange(GrGLenum target,
                                                           GrGLintptr offset,
                                                           GrGLsizeiptr length) {
    GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
                   GR_GL_ELEMENT_ARRAY_BUFFER == target);

    GrBufferObj *buffer = NULL;
    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getArrayBuffer();
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
            break;
        default:
            SkFAIL("Unexpected target to glUnmapBuffer");
            break;
    }

    if (buffer) {
        GrAlwaysAssert(buffer->getMapped());
        GrAlwaysAssert(offset >= 0 && (offset + length) <= buffer->getMappedLength());
    } else {
        GrAlwaysAssert(false);
    }
}


GrGLvoid GR_GL_FUNCTION_TYPE debugGLGetBufferParameteriv(GrGLenum target,
                                                         GrGLenum value,
                                                         GrGLint* params) {

    GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
                   GR_GL_ELEMENT_ARRAY_BUFFER == target);
    GrAlwaysAssert(GR_GL_BUFFER_SIZE == value ||
                   GR_GL_BUFFER_USAGE == value);

    GrBufferObj *buffer = NULL;
    switch (target) {
        case GR_GL_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getArrayBuffer();
            break;
        case GR_GL_ELEMENT_ARRAY_BUFFER:
            buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
            break;
    }

    GrAlwaysAssert(buffer);

    switch (value) {
        case GR_GL_BUFFER_MAPPED:
            *params = GR_GL_FALSE;
            if (buffer)
                *params = buffer->getMapped() ? GR_GL_TRUE : GR_GL_FALSE;
            break;
        case GR_GL_BUFFER_SIZE:
            *params = 0;
            if (buffer)
                *params = SkToInt(buffer->getSize());
            break;
        case GR_GL_BUFFER_USAGE:
            *params = GR_GL_STATIC_DRAW;
            if (buffer)
                *params = buffer->getUsage();
            break;
        default:
            SkFAIL("Unexpected value to glGetBufferParamateriv");
            break;
    }
};
} // end of namespace

////////////////////////////////////////////////////////////////////////////////
struct GrDebugGLInterface : public GrGLInterface {

public:
    SK_DECLARE_INST_COUNT(GrDebugGLInterface)

    GrDebugGLInterface()
        : fWrapped(NULL) {
        GrDebugGL::staticRef();
    }

    virtual ~GrDebugGLInterface() {
        GrDebugGL::staticUnRef();
    }

    void setWrapped(GrGLInterface *interface) {
        fWrapped.reset(interface);
    }

    virtual void abandon() const SK_OVERRIDE {
        GrDebugGL::abandon();
    }

    // TODO: there are some issues w/ wrapping another GL interface inside the
    // debug interface:
    //      Since none of the "gl" methods are member functions they don't get
    //      a "this" pointer through which to access "fWrapped"
    //      This could be worked around by having all of them access the
    //      "glInterface" pointer - i.e., treating the debug interface as a
    //      true singleton
    //
    //      The problem with this is that we also want to handle OpenGL
    //      contexts. The natural way to do this is to have multiple debug
    //      interfaces. Each of which represents a separate context. The
    //      static ID count would still uniquify IDs across all of them.
    //      The problem then is that we couldn't treat the debug GL
    //      interface as a singleton (since there would be one for each
    //      context).
    //
    //      The solution to this is probably to alter SkDebugGlContext's
    //      "makeCurrent" method to make a call like "makeCurrent(this)" to
    //      the debug GL interface (assuming that the application will create
    //      multiple SkGLContext's) to let it switch between the active
    //      context. Everything in the GrDebugGL object would then need to be
    //      moved to a GrContextObj and the GrDebugGL object would just switch
    //      between them. Note that this approach would also require that
    //      SkDebugGLContext wrap an arbitrary other context
    //      and then pass the wrapped interface to the debug GL interface.

protected:
private:

    SkAutoTUnref<GrGLInterface> fWrapped;

    typedef GrGLInterface INHERITED;
};

////////////////////////////////////////////////////////////////////////////////
const GrGLInterface* GrGLCreateDebugInterface() {
    GrGLInterface* interface = SkNEW(GrDebugGLInterface);

    interface->fStandard = kGL_GrGLStandard;

    GrGLInterface::Functions* functions = &interface->fFunctions;
    functions->fActiveTexture = debugGLActiveTexture;
    functions->fAttachShader = debugGLAttachShader;
    functions->fBeginQuery = debugGLBeginQuery;
    functions->fBindAttribLocation = debugGLBindAttribLocation;
    functions->fBindBuffer = debugGLBindBuffer;
    functions->fBindFragDataLocation = noOpGLBindFragDataLocation;
    functions->fBindTexture = debugGLBindTexture;
    functions->fBindVertexArray = debugGLBindVertexArray;
    functions->fBlendColor = noOpGLBlendColor;
    functions->fBlendFunc = noOpGLBlendFunc;
    functions->fBufferData = debugGLBufferData;
    functions->fBufferSubData = noOpGLBufferSubData;
    functions->fClear = noOpGLClear;
    functions->fClearColor = noOpGLClearColor;
    functions->fClearStencil = noOpGLClearStencil;
    functions->fColorMask = noOpGLColorMask;
    functions->fCompileShader = noOpGLCompileShader;
    functions->fCompressedTexImage2D = noOpGLCompressedTexImage2D;
    functions->fCompressedTexSubImage2D = noOpGLCompressedTexSubImage2D;
    functions->fCopyTexSubImage2D = noOpGLCopyTexSubImage2D;
    functions->fCreateProgram = debugGLCreateProgram;
    functions->fCreateShader = debugGLCreateShader;
    functions->fCullFace = noOpGLCullFace;
    functions->fDeleteBuffers = debugGLDeleteBuffers;
    functions->fDeleteProgram = debugGLDeleteProgram;
    functions->fDeleteQueries = noOpGLDeleteIds;
    functions->fDeleteShader = debugGLDeleteShader;
    functions->fDeleteTextures = debugGLDeleteTextures;
    functions->fDeleteVertexArrays = debugGLDeleteVertexArrays;
    functions->fDepthMask = noOpGLDepthMask;
    functions->fDisable = noOpGLDisable;
    functions->fDisableVertexAttribArray = noOpGLDisableVertexAttribArray;
    functions->fDrawArrays = noOpGLDrawArrays;
    functions->fDrawBuffer = noOpGLDrawBuffer;
    functions->fDrawBuffers = noOpGLDrawBuffers;
    functions->fDrawElements = noOpGLDrawElements;
    functions->fEnable = noOpGLEnable;
    functions->fEnableVertexAttribArray = noOpGLEnableVertexAttribArray;
    functions->fEndQuery = noOpGLEndQuery;
    functions->fFinish = noOpGLFinish;
    functions->fFlush = noOpGLFlush;
    functions->fFlushMappedBufferRange = debugGLFlushMappedBufferRange;
    functions->fFrontFace = noOpGLFrontFace;
    functions->fGenerateMipmap = debugGLGenerateMipmap;
    functions->fGenBuffers = debugGLGenBuffers;
    functions->fGenQueries = noOpGLGenIds;
    functions->fGenTextures = debugGLGenTextures;
    functions->fGetBufferParameteriv = debugGLGetBufferParameteriv;
    functions->fGetError = noOpGLGetError;
    functions->fGetIntegerv = noOpGLGetIntegerv;
    functions->fGetQueryObjecti64v = noOpGLGetQueryObjecti64v;
    functions->fGetQueryObjectiv = noOpGLGetQueryObjectiv;
    functions->fGetQueryObjectui64v = noOpGLGetQueryObjectui64v;
    functions->fGetQueryObjectuiv = noOpGLGetQueryObjectuiv;
    functions->fGetQueryiv = noOpGLGetQueryiv;
    functions->fGetProgramInfoLog = noOpGLGetInfoLog;
    functions->fGetProgramiv = noOpGLGetShaderOrProgramiv;
    functions->fGetShaderInfoLog = noOpGLGetInfoLog;
    functions->fGetShaderiv = noOpGLGetShaderOrProgramiv;
    functions->fGetString = noOpGLGetString;
    functions->fGetStringi = noOpGLGetStringi;
    functions->fGetTexLevelParameteriv = noOpGLGetTexLevelParameteriv;
    functions->fGetUniformLocation = noOpGLGetUniformLocation;
    functions->fGenVertexArrays = debugGLGenVertexArrays;
    functions->fLineWidth = noOpGLLineWidth;
    functions->fLinkProgram = noOpGLLinkProgram;
    functions->fMapBuffer = debugGLMapBuffer;
    functions->fMapBufferRange = debugGLMapBufferRange;
    functions->fPixelStorei = debugGLPixelStorei;
    functions->fQueryCounter = noOpGLQueryCounter;
    functions->fReadBuffer = noOpGLReadBuffer;
    functions->fReadPixels = debugGLReadPixels;
    functions->fScissor = noOpGLScissor;
    functions->fShaderSource = noOpGLShaderSource;
    functions->fStencilFunc = noOpGLStencilFunc;
    functions->fStencilFuncSeparate = noOpGLStencilFuncSeparate;
    functions->fStencilMask = noOpGLStencilMask;
    functions->fStencilMaskSeparate = noOpGLStencilMaskSeparate;
    functions->fStencilOp = noOpGLStencilOp;
    functions->fStencilOpSeparate = noOpGLStencilOpSeparate;
    functions->fTexImage2D = noOpGLTexImage2D;
    functions->fTexParameteri = noOpGLTexParameteri;
    functions->fTexParameteriv = noOpGLTexParameteriv;
    functions->fTexSubImage2D = noOpGLTexSubImage2D;
    functions->fTexStorage2D = noOpGLTexStorage2D;
    functions->fDiscardFramebuffer = noOpGLDiscardFramebuffer;
    functions->fUniform1f = noOpGLUniform1f;
    functions->fUniform1i = noOpGLUniform1i;
    functions->fUniform1fv = noOpGLUniform1fv;
    functions->fUniform1iv = noOpGLUniform1iv;
    functions->fUniform2f = noOpGLUniform2f;
    functions->fUniform2i = noOpGLUniform2i;
    functions->fUniform2fv = noOpGLUniform2fv;
    functions->fUniform2iv = noOpGLUniform2iv;
    functions->fUniform3f = noOpGLUniform3f;
    functions->fUniform3i = noOpGLUniform3i;
    functions->fUniform3fv = noOpGLUniform3fv;
    functions->fUniform3iv = noOpGLUniform3iv;
    functions->fUniform4f = noOpGLUniform4f;
    functions->fUniform4i = noOpGLUniform4i;
    functions->fUniform4fv = noOpGLUniform4fv;
    functions->fUniform4iv = noOpGLUniform4iv;
    functions->fUniformMatrix2fv = noOpGLUniformMatrix2fv;
    functions->fUniformMatrix3fv = noOpGLUniformMatrix3fv;
    functions->fUniformMatrix4fv = noOpGLUniformMatrix4fv;
    functions->fUnmapBuffer = debugGLUnmapBuffer;
    functions->fUseProgram = debugGLUseProgram;
    functions->fVertexAttrib1f = noOpGLVertexAttrib1f;
    functions->fVertexAttrib2fv = noOpGLVertexAttrib2fv;
    functions->fVertexAttrib3fv = noOpGLVertexAttrib3fv;
    functions->fVertexAttrib4fv = noOpGLVertexAttrib4fv;
    functions->fVertexAttribPointer = noOpGLVertexAttribPointer;
    functions->fViewport = noOpGLViewport;
    functions->fBindFramebuffer = debugGLBindFramebuffer;
    functions->fBindRenderbuffer = debugGLBindRenderbuffer;
    functions->fCheckFramebufferStatus = noOpGLCheckFramebufferStatus;
    functions->fDeleteFramebuffers = debugGLDeleteFramebuffers;
    functions->fDeleteRenderbuffers = debugGLDeleteRenderbuffers;
    functions->fFramebufferRenderbuffer = debugGLFramebufferRenderbuffer;
    functions->fFramebufferTexture2D = debugGLFramebufferTexture2D;
    functions->fGenFramebuffers = debugGLGenFramebuffers;
    functions->fGenRenderbuffers = debugGLGenRenderbuffers;
    functions->fGetFramebufferAttachmentParameteriv =
                                    noOpGLGetFramebufferAttachmentParameteriv;
    functions->fGetRenderbufferParameteriv = noOpGLGetRenderbufferParameteriv;
    functions->fRenderbufferStorage = noOpGLRenderbufferStorage;
    functions->fRenderbufferStorageMultisample =
                                    noOpGLRenderbufferStorageMultisample;
    functions->fBlitFramebuffer = noOpGLBlitFramebuffer;
    functions->fResolveMultisampleFramebuffer =
                                    noOpGLResolveMultisampleFramebuffer;
    functions->fMatrixLoadf = noOpGLMatrixLoadf;
    functions->fMatrixLoadIdentity = noOpGLMatrixLoadIdentity;

    functions->fBindFragDataLocationIndexed =
                                    noOpGLBindFragDataLocationIndexed;

    interface->fExtensions.init(kGL_GrGLStandard, functions->fGetString, functions->fGetStringi,
                                functions->fGetIntegerv);

    return interface;
}
