/** 
 * @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++)
	{
		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;
	}
}