/*
 * Copyright (C) 2009 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"

#if ENABLE(3D_CANVAS)

#include "WebGLFramebuffer.h"
#include "WebGLRenderingContext.h"

namespace WebCore {
    
PassRefPtr<WebGLFramebuffer> WebGLFramebuffer::create(WebGLRenderingContext* ctx)
{
    return adoptRef(new WebGLFramebuffer(ctx));
}

WebGLFramebuffer::WebGLFramebuffer(WebGLRenderingContext* ctx)
    : CanvasObject(ctx)
    , m_colorAttachment(0)
    , m_depthAttachment(0)
    , m_stencilAttachment(0)
    , m_depthStencilAttachment(0)
{
    setObject(context()->graphicsContext3D()->createFramebuffer());
}

void WebGLFramebuffer::setAttachment(unsigned long attachment, CanvasObject* attachedObject)
{
    if (!object())
        return;
    if (attachedObject && !attachedObject->object())
        attachedObject = 0;
    switch (attachment) {
    case GraphicsContext3D::COLOR_ATTACHMENT0:
        m_colorAttachment = attachedObject;
        break;
    case GraphicsContext3D::DEPTH_ATTACHMENT:
        m_depthAttachment = attachedObject;
        break;
    case GraphicsContext3D::STENCIL_ATTACHMENT:
        m_stencilAttachment = attachedObject;
        break;
    case GraphicsContext3D::DEPTH_STENCIL_ATTACHMENT:
        m_depthStencilAttachment = attachedObject;
        break;
    default:
        return;
    }
    initializeRenderbuffers();
}

void WebGLFramebuffer::onBind()
{
    initializeRenderbuffers();
}

void WebGLFramebuffer::onAttachedObjectChange(CanvasObject* object)
{
    // Currently object == 0 is not considered, but this might change if the
    // lifespan of CanvasObject changes.
    if (object
        && (object == m_colorAttachment || object == m_depthAttachment
            || object == m_stencilAttachment || object == m_depthStencilAttachment))
        initializeRenderbuffers();
}

unsigned long WebGLFramebuffer::getColorBufferFormat()
{
    if (object() && m_colorAttachment && m_colorAttachment->object()) {
        if (m_colorAttachment->isRenderbuffer()) {
            unsigned long format = (reinterpret_cast<WebGLRenderbuffer*>(m_colorAttachment))->getInternalFormat();
            switch (format) {
            case GraphicsContext3D::RGBA4:
            case GraphicsContext3D::RGB5_A1:
                return GraphicsContext3D::RGBA;
            case GraphicsContext3D::RGB565:
                return GraphicsContext3D::RGB;
            }
        } else if (m_colorAttachment->isTexture())
            return (reinterpret_cast<WebGLTexture*>(m_colorAttachment))->getInternalFormat();
    }
    return 0;
}

void WebGLFramebuffer::_deleteObject(Platform3DObject object)
{
    context()->graphicsContext3D()->deleteFramebuffer(object);
}

bool WebGLFramebuffer::isUninitialized(CanvasObject* attachedObject)
{
    if (attachedObject && attachedObject->object() && attachedObject->isRenderbuffer()
        && !(reinterpret_cast<WebGLRenderbuffer*>(attachedObject))->isInitialized())
        return true;
    return false;
}

void WebGLFramebuffer::setInitialized(CanvasObject* attachedObject)
{
    if (attachedObject && attachedObject->object() && attachedObject->isRenderbuffer())
        (reinterpret_cast<WebGLRenderbuffer*>(attachedObject))->setInitialized();
}

void WebGLFramebuffer::initializeRenderbuffers()
{
    if (!object())
        return;
    bool initColor = false, initDepth = false, initStencil = false;
    unsigned long mask = 0;
    if (isUninitialized(m_colorAttachment)) {
        initColor = true;
        mask |= GraphicsContext3D::COLOR_BUFFER_BIT;
    }
    if (isUninitialized(m_depthAttachment)) {
        initDepth = true;
        mask |= GraphicsContext3D::DEPTH_BUFFER_BIT;
    }
    if (isUninitialized(m_stencilAttachment)) {
        initStencil = true;
        mask |= GraphicsContext3D::STENCIL_BUFFER_BIT;
    }
    if (isUninitialized(m_depthStencilAttachment)) {
        initDepth = true;
        initStencil = true;
        mask |= (GraphicsContext3D::DEPTH_BUFFER_BIT | GraphicsContext3D::STENCIL_BUFFER_BIT);
    }
    if (!initColor && !initDepth && !initStencil)
        return;

    // We only clear un-initialized renderbuffers when they are ready to be
    // read, i.e., when the framebuffer is complete.
    GraphicsContext3D* g3d = context()->graphicsContext3D();
    if (g3d->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE)
        return;

    float colorClearValue[] = {0, 0, 0, 0}, depthClearValue = 0;
    int stencilClearValue = 0;
    unsigned char colorMask[] = {1, 1, 1, 1}, depthMask = 1, stencilMask = 1;
    bool isScissorEnabled = false;
    bool isDitherEnabled = false;
    if (initColor) {
        g3d->getFloatv(GraphicsContext3D::COLOR_CLEAR_VALUE, colorClearValue);
        g3d->getBooleanv(GraphicsContext3D::COLOR_WRITEMASK, colorMask);
        g3d->clearColor(0, 0, 0, 0);
        g3d->colorMask(true, true, true, true);
    }
    if (initDepth) {
        g3d->getFloatv(GraphicsContext3D::DEPTH_CLEAR_VALUE, &depthClearValue);
        g3d->getBooleanv(GraphicsContext3D::DEPTH_WRITEMASK, &depthMask);
        g3d->clearDepth(0);
        g3d->depthMask(true);
    }
    if (initStencil) {
        g3d->getIntegerv(GraphicsContext3D::STENCIL_CLEAR_VALUE, &stencilClearValue);
        g3d->getBooleanv(GraphicsContext3D::STENCIL_WRITEMASK, &stencilMask);
        g3d->clearStencil(0);
        g3d->stencilMask(true);
    }
    isScissorEnabled = g3d->isEnabled(GraphicsContext3D::SCISSOR_TEST);
    g3d->disable(GraphicsContext3D::SCISSOR_TEST);
    isDitherEnabled = g3d->isEnabled(GraphicsContext3D::DITHER);
    g3d->disable(GraphicsContext3D::DITHER);

    g3d->clear(mask);

    if (initColor) {
        g3d->clearColor(colorClearValue[0], colorClearValue[1], colorClearValue[2], colorClearValue[3]);
        g3d->colorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
    }
    if (initDepth) {
        g3d->clearDepth(depthClearValue);
        g3d->depthMask(depthMask);
    }
    if (initStencil) {
        g3d->clearStencil(stencilClearValue);
        g3d->stencilMask(stencilMask);
    }
    if (isScissorEnabled)
        g3d->enable(GraphicsContext3D::SCISSOR_TEST);
    else
        g3d->disable(GraphicsContext3D::SCISSOR_TEST);
    if (isDitherEnabled)
        g3d->enable(GraphicsContext3D::DITHER);
    else
        g3d->disable(GraphicsContext3D::DITHER);

    if (initColor)
        setInitialized(m_colorAttachment);
    if (initDepth && initStencil && m_depthStencilAttachment)
        setInitialized(m_depthStencilAttachment);
    else {
        if (initDepth)
            setInitialized(m_depthAttachment);
        if (initStencil)
            setInitialized(m_stencilAttachment);
    }
}

}

#endif // ENABLE(3D_CANVAS)
