/*
 * 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 "GrDebugGL.h"
#include "GrTextureObj.h"
#include "GrBufferObj.h"
#include "GrRenderBufferObj.h"
#include "GrFrameBufferObj.h"
#include "GrShaderObj.h"
#include "GrProgramObj.h"
#include "GrTextureUnitObj.h"
#include "GrVertexArrayObj.h"

GrDebugGL* GrDebugGL::gObj = nullptr;
int GrDebugGL::gStaticRefCount = 0;
GrDebugGL::Create GrDebugGL::gFactoryFunc[kObjTypeCount] = {
    GrTextureObj::createGrTextureObj,
    GrBufferObj::createGrBufferObj,
    GrRenderBufferObj::createGrRenderBufferObj,
    GrFrameBufferObj::createGrFrameBufferObj,
    GrShaderObj::createGrShaderObj,
    GrProgramObj::createGrProgramObj,
    GrTextureUnitObj::createGrTextureUnitObj,
    GrVertexArrayObj::createGrVertexArrayObj,
};


GrDebugGL::GrDebugGL()
    : fPackRowLength(0)
    , fUnPackRowLength(0)
    , fCurTextureUnit(0)
    , fArrayBuffer(nullptr)
    , fElementArrayBuffer(nullptr)
    , fFrameBuffer(nullptr)
    , fRenderBuffer(nullptr)
    , fProgram(nullptr)
    , fTexture(nullptr)
    , fVertexArray(nullptr)
    , fAbandoned(false) {

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

        fTextureUnits[i] = reinterpret_cast<GrTextureUnitObj *>(
                            createObj(GrDebugGL::kTextureUnit_ObjTypes));
        fTextureUnits[i]->ref();

        fTextureUnits[i]->setNumber(i);
    }
}

GrDebugGL::~GrDebugGL() {
    // unref & delete the texture units first so they don't show up on the leak report
    for (int i = 0; i < kDefaultMaxTextureUnits; ++i) {
        fTextureUnits[i]->unref();
        fTextureUnits[i]->deleteAction();
    }

    this->report();

    for (int i = 0; i < fObjects.count(); ++i) {
        delete fObjects[i];
    }
    fObjects.reset();

    fArrayBuffer = nullptr;
    fElementArrayBuffer = nullptr;
    fFrameBuffer = nullptr;
    fRenderBuffer = nullptr;
    fProgram = nullptr;
    fTexture = nullptr;
    fVertexArray = nullptr;
}

GrFakeRefObj *GrDebugGL::findObject(GrGLuint ID, GrObjTypes type) {
    for (int i = 0; i < fObjects.count(); ++i) {
        if (fObjects[i]->getID() == ID) { // && fObjects[i]->getType() == type) {
            // The application shouldn't be accessing objects
            // that (as far as OpenGL knows) were already deleted
            GrAlwaysAssert(!fObjects[i]->getDeleted());
            GrAlwaysAssert(!fObjects[i]->getMarkedForDeletion());
            return fObjects[i];
        }
    }

    return nullptr;
}

void GrDebugGL::setArrayBuffer(GrBufferObj *arrayBuffer) {
    if (fArrayBuffer) {
        // automatically break the binding of the old buffer
        GrAlwaysAssert(fArrayBuffer->getBound());
        fArrayBuffer->resetBound();

        GrAlwaysAssert(!fArrayBuffer->getDeleted());
        fArrayBuffer->unref();
    }

    fArrayBuffer = arrayBuffer;

    if (fArrayBuffer) {
        GrAlwaysAssert(!fArrayBuffer->getDeleted());
        fArrayBuffer->ref();

        GrAlwaysAssert(!fArrayBuffer->getBound());
        fArrayBuffer->setBound();
    }
}

void GrDebugGL::setVertexArray(GrVertexArrayObj* vertexArray) {
    if (vertexArray) {
        SkASSERT(!vertexArray->getDeleted());
    }
    SkRefCnt_SafeAssign(fVertexArray, vertexArray);
}

void GrDebugGL::setElementArrayBuffer(GrBufferObj *elementArrayBuffer) {
    if (fElementArrayBuffer) {
        // automatically break the binding of the old buffer
        GrAlwaysAssert(fElementArrayBuffer->getBound());
        fElementArrayBuffer->resetBound();

        GrAlwaysAssert(!fElementArrayBuffer->getDeleted());
        fElementArrayBuffer->unref();
    }

    fElementArrayBuffer = elementArrayBuffer;

    if (fElementArrayBuffer) {
        GrAlwaysAssert(!fElementArrayBuffer->getDeleted());
        fElementArrayBuffer->ref();

        GrAlwaysAssert(!fElementArrayBuffer->getBound());
        fElementArrayBuffer->setBound();
    }
}

void GrDebugGL::setTexture(GrTextureObj *texture)  {
    fTextureUnits[fCurTextureUnit]->setTexture(texture);
}

void GrDebugGL::setFrameBuffer(GrFrameBufferObj *frameBuffer)  {
    if (fFrameBuffer) {
        GrAlwaysAssert(fFrameBuffer->getBound());
        fFrameBuffer->resetBound();

        GrAlwaysAssert(!fFrameBuffer->getDeleted());
        fFrameBuffer->unref();
    }

    fFrameBuffer = frameBuffer;

    if (fFrameBuffer) {
        GrAlwaysAssert(!fFrameBuffer->getDeleted());
        fFrameBuffer->ref();

        GrAlwaysAssert(!fFrameBuffer->getBound());
        fFrameBuffer->setBound();
    }
}

void GrDebugGL::setRenderBuffer(GrRenderBufferObj *renderBuffer)  {
    if (fRenderBuffer) {
        GrAlwaysAssert(fRenderBuffer->getBound());
        fRenderBuffer->resetBound();

        GrAlwaysAssert(!fRenderBuffer->getDeleted());
        fRenderBuffer->unref();
    }

    fRenderBuffer = renderBuffer;

    if (fRenderBuffer) {
        GrAlwaysAssert(!fRenderBuffer->getDeleted());
        fRenderBuffer->ref();

        GrAlwaysAssert(!fRenderBuffer->getBound());
        fRenderBuffer->setBound();
    }
}

void GrDebugGL::useProgram(GrProgramObj *program) {
    if (fProgram) {
        GrAlwaysAssert(fProgram->getInUse());
        fProgram->resetInUse();

        GrAlwaysAssert(!fProgram->getDeleted());
        fProgram->unref();
    }

    fProgram = program;

    if (fProgram) {
        GrAlwaysAssert(!fProgram->getDeleted());
        fProgram->ref();

        GrAlwaysAssert(!fProgram->getInUse());
        fProgram->setInUse();
    }
}

void GrDebugGL::report() const {
    for (int i = 0; i < fObjects.count(); ++i) {
        if (!fAbandoned) {
            GrAlwaysAssert(0 == fObjects[i]->getRefCount());
            GrAlwaysAssert(fObjects[i]->getDeleted());
        }
    }
}