/*
 * Copyright (c) 2010, Google 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:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 THE COPYRIGHT
 * OWNER 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(ACCELERATED_2D_CANVAS)

#include "Texture.h"

#include "Extensions3D.h"
#include "FloatRect.h"
#include "GraphicsContext3D.h"
#include "IntRect.h"
#include <algorithm>
#include <wtf/StdLibExtras.h>
#include <wtf/UniqueArray.h>

namespace WebCore {

Texture::Texture(GraphicsContext3D* context, std::unique_ptr<Vector<unsigned>> tileTextureIds, Format format, int width, int height, int maxTextureSize)
    : m_context(context)
    , m_format(format)
    , m_tiles(IntSize(maxTextureSize, maxTextureSize), IntSize(width, height), true)
    , m_tileTextureIds(WTFMove(tileTextureIds))
{
}

Texture::~Texture()
{
    for (unsigned int i = 0; i < m_tileTextureIds->size(); i++)
        m_context->deleteTexture(m_tileTextureIds->at(i));
}

static void convertFormat(GraphicsContext3D* context, Texture::Format format, unsigned int* glFormat, unsigned int* glType, bool* swizzle)
{
    *swizzle = false;
    switch (format) {
    case Texture::RGBA8:
        *glFormat = GraphicsContext3D::RGBA;
        *glType = GraphicsContext3D::UNSIGNED_BYTE;
        break;
    case Texture::BGRA8:
        if (context->getExtensions().supports("GL_EXT_texture_format_BGRA8888")) {
            *glFormat = Extensions3D::BGRA_EXT;
            *glType = GraphicsContext3D::UNSIGNED_BYTE;
        } else {
            *glFormat = GraphicsContext3D::RGBA;
            *glType = GraphicsContext3D::UNSIGNED_BYTE;
            *swizzle = true;
        }
        break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }
}

RefPtr<Texture> Texture::create(GraphicsContext3D* context, Format format, int width, int height)
{
    int maxTextureSize = 0;
    context->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &maxTextureSize);
    TilingData tiling(IntSize(maxTextureSize, maxTextureSize), IntSize(width, height), true);

    // Check for overflow.
    int numTiles = tiling.numTilesX() * tiling.numTilesY();
    if (numTiles / tiling.numTilesX() != tiling.numTilesY()) {
        tiling.setTotalSize(IntSize());
        numTiles = 0;
    }

    auto textureIds = makeUnique<Vector<unsigned>>(numTiles);
    textureIds->fill(0, numTiles);

    for (int i = 0; i < numTiles; i++) {
        int textureId = context->createTexture();
        if (!textureId) {
            for (int i = 0; i < numTiles; i++)
                context->deleteTexture(textureIds->at(i));
            return nullptr;
        }
        textureIds->at(i) = textureId;

        int xIndex = i % tiling.numTilesX();
        int yIndex = i / tiling.numTilesX();
        IntRect tileBoundsWithBorder = tiling.tileBoundsWithBorder(xIndex, yIndex);

        unsigned int glFormat = 0;
        unsigned int glType = 0;
        bool swizzle;
        convertFormat(context, format, &glFormat, &glType, &swizzle);
        context->bindTexture(GraphicsContext3D::TEXTURE_2D, textureId);
        context->texImage2DResourceSafe(GraphicsContext3D::TEXTURE_2D, 0, glFormat,
                                        tileBoundsWithBorder.width(),
                                        tileBoundsWithBorder.height(),
                                        0, glFormat, glType);
    }
    return adoptRef(new Texture(context, WTFMove(textureIds), format, width, height, maxTextureSize));
}

template <bool swizzle>
static uint32_t* copySubRect(uint32_t* src, int srcX, int srcY, uint32_t* dst, int width, int height, int srcStride)
{
    uint32_t* srcOffset = src + srcX + srcY * srcStride;

    if (!swizzle && width == srcStride)
        return srcOffset;

    if (swizzle) {
        uint32_t* dstPixel = dst;
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width ; ++x) {
                uint32_t pixel = srcOffset[x + y * srcStride];
                *dstPixel = (pixel & 0xFF00FF00) | ((pixel & 0x00FF0000) >> 16) | ((pixel & 0x000000FF) << 16);
                dstPixel++;
            }
        }
    } else {
        for (int y = 0; y < height; ++y) {
            memcpy(dst + y * width, srcOffset + y * srcStride, 4 * width);
        }
    }
    return dst;
}

void Texture::load(void* pixels)
{
    updateSubRect(pixels, IntRect(0, 0, m_tiles.totalSize().width(), m_tiles.totalSize().height()));
}

void Texture::updateSubRect(void* pixels, const IntRect& updateRect)
{
    IntRect updateRectSanitized(updateRect);
    updateRectSanitized.intersect(IntRect(0, 0, m_tiles.totalSize().width(), m_tiles.totalSize().height()));

    uint32_t* pixels32 = static_cast<uint32_t*>(pixels);
    unsigned int glFormat = 0;
    unsigned int glType = 0;
    bool swizzle;
    convertFormat(m_context, m_format, &glFormat, &glType, &swizzle);
    if (swizzle) {
        ASSERT(glFormat == GraphicsContext3D::RGBA && glType == GraphicsContext3D::UNSIGNED_BYTE);
        // FIXME:  This could use PBO's to save doing an extra copy here.
    }
    int tempBuffSize = // Temporary buffer size is the smaller of the max texture size or the updateRectSanitized
        std::min(m_tiles.maxTextureSize().width(), m_tiles.borderTexels() + updateRectSanitized.width()) *
        std::min(m_tiles.maxTextureSize().height(), m_tiles.borderTexels() + updateRectSanitized.height());
    auto tempBuff = makeUniqueArray<uint32_t>(tempBuffSize);

    for (int tile = 0; tile < m_tiles.numTilesX() * m_tiles.numTilesY(); tile++) {
        int xIndex = tile % m_tiles.numTilesX();
        int yIndex = tile / m_tiles.numTilesX();

        // Intersect with tile
        IntRect tileBoundsWithBorder = m_tiles.tileBoundsWithBorder(xIndex, yIndex);

        IntRect updateRectIntersected = updateRectSanitized;
        updateRectIntersected.intersect(tileBoundsWithBorder);

        IntRect dstRect = updateRectIntersected;
        dstRect.moveBy(-tileBoundsWithBorder.location());

        if (updateRectIntersected.isEmpty())
            continue;

        // Copy sub rectangle out of larger pixel data
        uint32_t* uploadBuff = 0;
        if (swizzle) {
            uploadBuff = copySubRect<true>(
            pixels32, updateRectIntersected.x(), updateRectIntersected.y(),
            tempBuff.get(), updateRectIntersected.width(), updateRectIntersected.height(), m_tiles.totalSize().width());
        } else {
            uploadBuff = copySubRect<false>(
            pixels32, updateRectIntersected.x(), updateRectIntersected.y(),
            tempBuff.get(), updateRectIntersected.width(), updateRectIntersected.height(), m_tiles.totalSize().width());
        }

        m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_tileTextureIds->at(tile));
        m_context->texSubImage2D(GraphicsContext3D::TEXTURE_2D, 0 /* level */,
            dstRect.x(),
            dstRect.y(),
            updateRectIntersected.width(),
            updateRectIntersected.height(), glFormat, glType, uploadBuff);
    }
}

void Texture::bindTile(int tile)
{
    m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_tileTextureIds->at(tile));
    m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR);
    m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR);
    m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE);
    m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE);
}

}

#endif
