/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WebGLContext.h"
#include "WebGLTexture.h"
#include "WebGLRenderbuffer.h"
#include "WebGLFramebuffer.h"
#include "GLContext.h"
#include "GLScreenBuffer.h"

namespace mozilla {

void
WebGLContext::Clear(GLbitfield mask)
{
    const char funcName[] = "clear";

    if (IsContextLost())
        return;

    MakeContextCurrent();

    uint32_t m = mask & (LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT);
    if (mask != m)
        return ErrorInvalidValue("%s: invalid mask bits", funcName);

    if (mask == 0) {
        GenerateWarning("Calling gl.clear(0) has no effect.");
    } else if (mRasterizerDiscardEnabled) {
        GenerateWarning("Calling gl.clear() with RASTERIZER_DISCARD enabled has no effects.");
    }

    if (mBoundDrawFramebuffer) {
        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName))
            return;

        if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
            for (const auto& cur : mBoundDrawFramebuffer->ColorDrawBuffers()) {
                if (!cur->IsDefined())
                    continue;

                switch (cur->Format()->format->componentType) {
                case webgl::ComponentType::Float:
                case webgl::ComponentType::NormInt:
                case webgl::ComponentType::NormUInt:
                    break;

                default:
                    ErrorInvalidOperation("%s: Color draw buffers must be floating-point"
                                          " or fixed-point. (normalized (u)ints)",
                                          funcName);
                    return;
                }
            }
        }
    }

    ScopedDrawCallWrapper wrapper(*this);
    gl->fClear(mask);
}

static GLfloat
GLClampFloat(GLfloat val)
{
    if (val < 0.0)
        return 0.0;

    if (val > 1.0)
        return 1.0;

    return val;
}

void
WebGLContext::ClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();

    const bool supportsFloatColorBuffers = (IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_float) ||
                                            IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float) ||
                                            IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float));
    if (!supportsFloatColorBuffers) {
        r = GLClampFloat(r);
        g = GLClampFloat(g);
        b = GLClampFloat(b);
        a = GLClampFloat(a);
    }

    gl->fClearColor(r, g, b, a);

    mColorClearValue[0] = r;
    mColorClearValue[1] = g;
    mColorClearValue[2] = b;
    mColorClearValue[3] = a;
}

void
WebGLContext::ClearDepth(GLclampf v)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();
    mDepthClearValue = GLClampFloat(v);
    gl->fClearDepth(mDepthClearValue);
}

void
WebGLContext::ClearStencil(GLint v)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();
    mStencilClearValue = v;
    gl->fClearStencil(v);
}

void
WebGLContext::ColorMask(WebGLboolean r, WebGLboolean g, WebGLboolean b, WebGLboolean a)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();
    mColorWriteMask[0] = r;
    mColorWriteMask[1] = g;
    mColorWriteMask[2] = b;
    mColorWriteMask[3] = a;
    gl->fColorMask(r, g, b, a);
}

void
WebGLContext::DepthMask(WebGLboolean b)
{
    if (IsContextLost())
        return;

    MakeContextCurrent();
    mDepthWriteMask = b;
    gl->fDepthMask(b);
}

void
WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
{
    const char funcName[] = "drawBuffers";
    if (IsContextLost())
        return;

    if (mBoundDrawFramebuffer) {
        mBoundDrawFramebuffer->DrawBuffers(funcName, buffers);
        return;
    }

    // GLES 3.0.4 p186:
    // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
    //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
    //  constant other than BACK and NONE, or with a value of `n` other than 1, the
    //  error INVALID_OPERATION is generated."
    if (buffers.Length() != 1) {
        ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
                              " length of 1.",
                              funcName);
        return;
    }

    switch (buffers[0]) {
    case LOCAL_GL_NONE:
    case LOCAL_GL_BACK:
        break;

    default:
        ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
                              " BACK or NONE.",
                              funcName);
        return;
    }

    mDefaultFB_DrawBuffer0 = buffers[0];
    gl->Screen()->SetDrawBuffer(buffers[0]);
}

void
WebGLContext::StencilMask(GLuint mask)
{
    if (IsContextLost())
        return;

    mStencilWriteMaskFront = mask;
    mStencilWriteMaskBack = mask;

    MakeContextCurrent();
    gl->fStencilMask(mask);
}

void
WebGLContext::StencilMaskSeparate(GLenum face, GLuint mask)
{
    if (IsContextLost())
        return;

    if (!ValidateFaceEnum(face, "stencilMaskSeparate: face"))
        return;

    switch (face) {
        case LOCAL_GL_FRONT_AND_BACK:
            mStencilWriteMaskFront = mask;
            mStencilWriteMaskBack = mask;
            break;
        case LOCAL_GL_FRONT:
            mStencilWriteMaskFront = mask;
            break;
        case LOCAL_GL_BACK:
            mStencilWriteMaskBack = mask;
            break;
    }

    MakeContextCurrent();
    gl->fStencilMaskSeparate(face, mask);
}

} // namespace mozilla
