/**
 * @file llcubemap.cpp
 * @brief LLCubeMap class implementation
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */
#include "linden_common.h"

#include "llworkerthread.h"

#include "llcubemap.h"

#include "v4coloru.h"
#include "v3math.h"
#include "v3dmath.h"
#include "m3math.h"
#include "m4math.h"

#include "llrender.h"
#include "llglslshader.h"

#include "llglheaders.h"

namespace {
    const U16 RESOLUTION = 64;
}

bool LLCubeMap::sUseCubeMaps = true;

LLCubeMap::LLCubeMap(bool init_as_srgb)
    : mTextureStage(0),
      mMatrixStage(0),
      mIssRGB(init_as_srgb)
{
    mTargets[0] = GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
    mTargets[1] = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
    mTargets[2] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
    mTargets[3] = GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
    mTargets[4] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
    mTargets[5] = GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
}

LLCubeMap::~LLCubeMap()
{
}

void LLCubeMap::initGL()
{
    llassert(gGLManager.mInited);

    if (LLCubeMap::sUseCubeMaps)
    {
        // Not initialized, do stuff.
        if (mImages[0].isNull())
        {
            U32 texname = 0;

            LLImageGL::generateTextures(1, &texname);

            for (int i = 0; i < 6; i++)
            {
                mImages[i] = new LLImageGL(RESOLUTION, RESOLUTION, 4, false);
            #if USE_SRGB_DECODE
                if (mIssRGB) {
                    mImages[i]->setExplicitFormat(GL_SRGB8_ALPHA8, GL_RGBA);
                }
            #endif
                mImages[i]->setTarget(mTargets[i], LLTexUnit::TT_CUBE_MAP);
                mRawImages[i] = new LLImageRaw(RESOLUTION, RESOLUTION, 4);
                mImages[i]->createGLTexture(0, mRawImages[i], texname);

                gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_CUBE_MAP, texname);
                mImages[i]->setAddressMode(LLTexUnit::TAM_CLAMP);
                stop_glerror();
            }
            gGL.getTexUnit(0)->disable();
        }
        disable();
    }
    else
    {
        LL_WARNS() << "Using cube map without extension!" << LL_ENDL;
    }
}

void LLCubeMap::initRawData(const std::vector<LLPointer<LLImageRaw> >& rawimages)
{
    bool flip_x[6] =    { false, true,  false, false, true,  false };
    bool flip_y[6] =    { true,  true,  true,  false, true,  true  };
    bool transpose[6] = { false, false, false, false, true,  true  };

    // Yes, I know that this is inefficient! - djs 08/08/02
    for (int i = 0; i < 6; i++)
    {
        LLImageDataSharedLock lockIn(rawimages[i]);
        LLImageDataLock lockOut(mRawImages[i]);

        const U8 *sd = rawimages[i]->getData();
        U8 *td = mRawImages[i]->getData();

        S32 offset = 0;
        S32 sx, sy, so;
        for (int y = 0; y < 64; y++)
        {
            for (int x = 0; x < 64; x++)
            {
                sx = x;
                sy = y;
                if (flip_y[i])
                {
                    sy = 63 - y;
                }
                if (flip_x[i])
                {
                    sx = 63 - x;
                }
                if (transpose[i])
                {
                    S32 temp = sx;
                    sx = sy;
                    sy = temp;
                }

                so = 64*sy + sx;
                so *= 4;
                *(td + offset++) = *(sd + so++);
                *(td + offset++) = *(sd + so++);
                *(td + offset++) = *(sd + so++);
                *(td + offset++) = *(sd + so++);
            }
        }
    }
}

void LLCubeMap::initGLData()
{
    LL_PROFILE_ZONE_SCOPED;
    for (int i = 0; i < 6; i++)
    {
        mImages[i]->setSubImage(mRawImages[i], 0, 0, RESOLUTION, RESOLUTION);
    }
}

void LLCubeMap::init(const std::vector<LLPointer<LLImageRaw> >& rawimages)
{
    if (!gGLManager.mIsDisabled)
    {
        initGL();
        initRawData(rawimages);
        initGLData();
    }
}

void LLCubeMap::initReflectionMap(U32 resolution, U32 components)
{
    U32 texname = 0;

    LLImageGL::generateTextures(1, &texname);

    mImages[0] = new LLImageGL(resolution, resolution, components, true);
    mImages[0]->setTexName(texname);
    mImages[0]->setTarget(mTargets[0], LLTexUnit::TT_CUBE_MAP);
    gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_CUBE_MAP, texname);
    mImages[0]->setAddressMode(LLTexUnit::TAM_CLAMP);
}

void LLCubeMap::initEnvironmentMap(const std::vector<LLPointer<LLImageRaw> >& rawimages)
{
    llassert(rawimages.size() == 6);

    U32 texname = 0;

    LLImageGL::generateTextures(1, &texname);

    U32 resolution = rawimages[0]->getWidth();
    U32 components = rawimages[0]->getComponents();

    for (int i = 0; i < 6; i++)
    {
        llassert(rawimages[i]->getWidth() == resolution);
        llassert(rawimages[i]->getHeight() == resolution);
        llassert(rawimages[i]->getComponents() == components);

        mImages[i] = new LLImageGL(resolution, resolution, components, true);
        mImages[i]->setTarget(mTargets[i], LLTexUnit::TT_CUBE_MAP);
        mRawImages[i] = rawimages[i];
        mImages[i]->createGLTexture(0, mRawImages[i], texname);

        gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_CUBE_MAP, texname);
        mImages[i]->setAddressMode(LLTexUnit::TAM_CLAMP);
        stop_glerror();

        mImages[i]->setSubImage(mRawImages[i], 0, 0, resolution, resolution);
    }
    enableTexture(0);
    bind();
    mImages[0]->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC);
    glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
    gGL.getTexUnit(0)->disable();
    disable();
}

void LLCubeMap::generateMipMaps()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;

    mImages[0]->setUseMipMaps(true);
    mImages[0]->setHasMipMaps(true);
    enableTexture(0);
    bind();
    mImages[0]->setFilteringOption(LLTexUnit::TFO_BILINEAR);
    {
        LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("cmgmm - glGenerateMipmap");
        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
    }
    gGL.getTexUnit(0)->disable();
    disable();
}

GLuint LLCubeMap::getGLName()
{
    return mImages[0]->getTexName();
}

void LLCubeMap::bind()
{
    gGL.getTexUnit(mTextureStage)->bind(this);
}

void LLCubeMap::enable(S32 stage)
{
    enableTexture(stage);
}

void LLCubeMap::enableTexture(S32 stage)
{
    mTextureStage = stage;
    if (stage >= 0 && LLCubeMap::sUseCubeMaps)
    {
        gGL.getTexUnit(stage)->enable(LLTexUnit::TT_CUBE_MAP);
    }
}

void LLCubeMap::disable(void)
{
    disableTexture();
}

void LLCubeMap::disableTexture(void)
{
    if (mTextureStage >= 0 && LLCubeMap::sUseCubeMaps)
    {
        gGL.getTexUnit(mTextureStage)->disable();
        if (mTextureStage == 0)
        {
            gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE);
        }
    }
}

void LLCubeMap::setMatrix(S32 stage)
{
    mMatrixStage = stage;

    if (mMatrixStage < 0) return;

    //if (stage > 0)
    {
        gGL.getTexUnit(stage)->activate();
    }

    LLVector3 x(gGLModelView+0);
    LLVector3 y(gGLModelView+4);
    LLVector3 z(gGLModelView+8);

    LLMatrix3 mat3;
    mat3.setRows(x,y,z);
    LLMatrix4 trans(mat3);
    trans.transpose();

    gGL.matrixMode(LLRender::MM_TEXTURE);
    gGL.pushMatrix();
    gGL.loadMatrix((F32 *)trans.mMatrix);
    gGL.matrixMode(LLRender::MM_MODELVIEW);

    /*if (stage > 0)
    {
        gGL.getTexUnit(0)->activate();
    }*/
}

void LLCubeMap::restoreMatrix()
{
    if (mMatrixStage < 0) return;

    //if (mMatrixStage > 0)
    {
        gGL.getTexUnit(mMatrixStage)->activate();
    }
    gGL.matrixMode(LLRender::MM_TEXTURE);
    gGL.popMatrix();
    gGL.matrixMode(LLRender::MM_MODELVIEW);

    /*if (mMatrixStage > 0)
    {
        gGL.getTexUnit(0)->activate();
    }*/
}


void LLCubeMap::destroyGL()
{
    for (S32 i = 0; i < 6; i++)
    {
        mImages[i] = NULL;
    }
}