/** 
 * @file lltextureatlas.cpp
 * @brief LLTextureAtlas 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 "llviewerprecompiledheaders.h"
#include "linden_common.h"
#include "llerror.h"
#include "llimage.h"
#include "llmath.h"
#include "llgl.h"
#include "llrender.h"
#include "lltextureatlas.h"

//-------------------
S16 LLTextureAtlas::sMaxSubTextureSize = 64 ;
S16 LLTextureAtlas::sSlotSize = 32 ;

#ifndef DEBUG_ATLAS
#define DEBUG_ATLAS 0
#endif

#ifndef DEBUG_USAGE_BITS
#define DEBUG_USAGE_BITS 0
#endif
//**************************************************************************************************************
LLTextureAtlas::LLTextureAtlas(U8 ncomponents, S16 atlas_dim) : 
    LLViewerTexture(atlas_dim * sSlotSize, atlas_dim * sSlotSize, ncomponents, TRUE),
	mAtlasDim(atlas_dim),
	mNumSlotsReserved(0),
	mMaxSlotsInAtlas(atlas_dim * atlas_dim)
{
	generateEmptyUsageBits() ;

	//generate an empty texture
	generateGLTexture() ;
	LLPointer<LLImageRaw> image_raw = new LLImageRaw(mFullWidth, mFullHeight, mComponents);
	createGLTexture(0, image_raw, 0);
	image_raw = NULL;
}

LLTextureAtlas::~LLTextureAtlas() 
{
	if(mSpatialGroupList.size() > 0)
	{
		llerrs << "Not clean up the spatial groups!" << llendl ;
	}
	releaseUsageBits() ;
}

//virtual 
S8 LLTextureAtlas::getType() const
{
	return LLViewerTexture::ATLAS_TEXTURE ;
}

void LLTextureAtlas::getTexCoordOffset(S16 col, S16 row, F32& xoffset, F32& yoffset)
{
	xoffset = (F32)col / mAtlasDim ;
	yoffset = (F32)row / mAtlasDim ;	
}

void LLTextureAtlas::getTexCoordScale(S32 w, S32 h, F32& xscale, F32& yscale)
{
	xscale = (F32)w / (mAtlasDim * sSlotSize) ;
	yscale = (F32)h / (mAtlasDim * sSlotSize) ;	
}

//insert a texture piece into the atlas
LLGLuint LLTextureAtlas::insertSubTexture(LLImageGL* source_gl_tex, S32 discard_level, const LLImageRaw* raw_image, S16 slot_col, S16 slot_row)
{
	if(!getTexName())
	{
		return 0 ;
	}

	S32 w = raw_image->getWidth() ;
	S32 h = raw_image->getHeight() ;
	if(w < 8 || w > sMaxSubTextureSize || h < 8 || h > sMaxSubTextureSize)
	{
		//size overflow
		return 0 ;
	}

	BOOL res = gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getTexName());
	if (!res) 
	{
		llerrs << "bindTexture failed" << llendl;
	}
	
	GLint xoffset = sSlotSize * slot_col ;
	GLint yoffset = sSlotSize * slot_row ;

	if(!source_gl_tex->preAddToAtlas(discard_level, raw_image))
	{
		return 0 ;
	}

	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, TRUE);
	glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, w, h,
						mGLTexturep->getPrimaryFormat(), mGLTexturep->getFormatType(), raw_image->getData());
	
	source_gl_tex->postAddToAtlas() ;
	return getTexName();
}
	
//release a sub-texture slot from the atlas
void LLTextureAtlas::releaseSlot(S16 slot_col, S16 slot_row, S8 slot_width)
{
	unmarkUsageBits(slot_width, slot_col, slot_row) ;
	mNumSlotsReserved -= slot_width * slot_width ;
}

BOOL LLTextureAtlas::isEmpty() const 
{
	return !mNumSlotsReserved ;
}
	
BOOL LLTextureAtlas::isFull(S8 to_be_reserved) const 
{
	return mNumSlotsReserved + to_be_reserved > mMaxSlotsInAtlas ;
}
F32  LLTextureAtlas::getFullness() const 
{
	return (F32)mNumSlotsReserved / mMaxSlotsInAtlas ;
}

void LLTextureAtlas::addSpatialGroup(LLSpatialGroup* groupp) 
{
	if(groupp && !hasSpatialGroup(groupp))
	{
		mSpatialGroupList.push_back(groupp);
	}
}

void LLTextureAtlas::removeSpatialGroup(LLSpatialGroup* groupp) 
{
	if(groupp)
	{
		mSpatialGroupList.remove(groupp);
	}
}

void LLTextureAtlas::clearSpatialGroup() 
{
	mSpatialGroupList.clear();
}
void LLTextureAtlas::removeLastSpatialGroup() 
{
	mSpatialGroupList.pop_back() ;
}

LLSpatialGroup* LLTextureAtlas::getLastSpatialGroup() 
{
	if(mSpatialGroupList.size() > 0)
	{
		return mSpatialGroupList.back() ;
	}
	return NULL ;
}

BOOL LLTextureAtlas::hasSpatialGroup(LLSpatialGroup* groupp) 
{
	for(std::list<LLSpatialGroup*>::iterator iter = mSpatialGroupList.begin(); iter != mSpatialGroupList.end() ; ++iter)
	{
		if(*iter == groupp)
		{
			return TRUE ;
		}
	}
	return FALSE ;
}

//--------------------------------------------------------------------------------------
//private
void LLTextureAtlas::generateEmptyUsageBits()
{
	S32 col_len = (mAtlasDim + 7) >> 3 ;
	mUsageBits = new U8*[mAtlasDim] ;
	*mUsageBits = new U8[mAtlasDim * col_len] ;

	mUsageBits[0] = *mUsageBits ;
	for(S32 i = 1 ; i < mAtlasDim ; i++)
	{
	   mUsageBits[i] = mUsageBits[i-1] + col_len ;

	   for(S32 j = 0 ; j < col_len ; j++)
	   {
		   //init by 0 for all bits.
		   mUsageBits[i][j] = 0 ;
	   }
	}

	//do not forget mUsageBits[0]!
	for(S32 j = 0 ; j < col_len ; j++)
	{
		//init by 0 for all bits.
		mUsageBits[0][j] = 0 ;
	}

	mTestBits = NULL ;
#if DEBUG_USAGE_BITS
	//------------
	//test
	mTestBits = new U8*[mAtlasDim] ;
	*mTestBits = new U8[mAtlasDim * mAtlasDim] ;
	mTestBits[0] = *mTestBits ;
	for(S32 i = 1 ; i < mAtlasDim ; i++)
	{
	   mTestBits[i] = mTestBits[i-1] + mAtlasDim ;

	   for(S32 j = 0 ; j < mAtlasDim ; j++)
	   {
		   //init by 0 for all bits.
		   mTestBits[i][j] = 0 ;
	   }
	}

	for(S32 j = 0 ; j < mAtlasDim ; j++)
	{
		//init by 0 for all bits.
		mTestBits[0][j] = 0 ;
	}
#endif
}

void LLTextureAtlas::releaseUsageBits()
{
   if(mUsageBits)
   {
       delete[] *mUsageBits ;
       delete[] mUsageBits ;
   }
   mUsageBits = NULL ;

   //test
   if( mTestBits)
   {
	   delete[] *mTestBits;
	   delete[]  mTestBits;
   }
    mTestBits = NULL ;
}

void LLTextureAtlas::markUsageBits(S8 bits_len, U8 mask, S16 col, S16 row)
{
	S16 x = col >> 3 ;
	
	for(S8 i = 0 ; i < bits_len ; i++)
	{
		mUsageBits[row + i][x] |= mask ;
	}

#if DEBUG_USAGE_BITS
	//test
	for(S8 i = row ; i < row + bits_len ; i++)
	{
		for(S8 j = col ; j < col + bits_len ; j++)
		{
			mTestBits[i][j] = 1 ;
		}
	}
#endif
}

void LLTextureAtlas::unmarkUsageBits(S8 bits_len, S16 col, S16 row)
{
	S16 x = col >> 3 ;
	U8  mask = 1 ;
	for(S8 i = 1 ; i < bits_len ; i++)
	{
		mask |= (1 << i) ;
	}
	mask <<= (col & 7) ;
	mask = ~mask ;
	
	for(S8 i = 0 ; i < bits_len ; i++)
	{
		mUsageBits[row + i][x] &= mask ;
	}

#if DEBUG_USAGE_BITS
	//test
	for(S8 i = row ; i < row + bits_len ; i++)
	{
		for(S8 j = col ; j < col + bits_len ; j++)
		{
			mTestBits[i][j] = 0 ;
		}
	}
#endif
}

//return true if any of bits in the range marked.
BOOL LLTextureAtlas::areUsageBitsMarked(S8 bits_len, U8 mask, S16 col, S16 row)
{
	BOOL ret = FALSE ;	
	S16 x = col >> 3 ;
	
	for(S8 i = 0 ; i < bits_len ; i++)
	{
		if(mUsageBits[row + i][x] & mask)
		{
			ret = TRUE ;
			break ;
			//return TRUE ;
		}
	}

#if DEBUG_USAGE_BITS
	//test
	BOOL ret2 = FALSE ;
	for(S8 i = row ; i < row + bits_len ; i++)
	{
		for(S8 j = col ; j < col + bits_len ; j++)
		{
			if(mTestBits[i][j])
			{
				ret2 = TRUE ;
			}
		}
	}

	if(ret != ret2)
	{
		llerrs << "bits map corrupted." << llendl ;
	}
#endif
	return ret ;//FALSE ;
}

//----------------------------------------------------------------------
//
//index order: Z order, i.e.: 
// |-----|-----|-----|-----|
// |  10 |  11 | 14  | 15  |
// |-----|-----|-----|-----|
// |   8 |   9 | 12  | 13  |
// |-----|-----|-----|-----|
// |   2 |   3 |   6 |   7 |
// |-----|-----|-----|-----|
// |   0 |   1 |   4 |   5 |
// |-----|-----|-----|-----|
void LLTextureAtlas::getPositionFromIndex(S16 index, S16& col, S16& row)
{
	col = 0 ;
	row = 0 ;

	S16 index_copy = index ;
	for(S16 i = 0 ; index_copy && i < 16 ; i += 2)
	{
		col |= ((index & (1 << i)) >> i) << (i >> 1) ;
		row |= ((index & (1 << (i + 1))) >> (i + 1)) << (i >> 1) ;
		index_copy >>= 2 ;
	}
}
void LLTextureAtlas::getIndexFromPosition(S16 col, S16 row, S16& index)
{
	index = 0 ;
	S16 col_copy = col ;
	S16 row_copy = row ;
	for(S16 i = 0 ; (col_copy || row_copy) && i < 16 ; i++)
	{
		index |= ((col & 1 << i) << i) | ((row & 1 << i) << ( i + 1)) ;
		col_copy >>= 1 ;
		row_copy >>= 1 ;
	}
}
//----------------------------------------------------------------------
//return TRUE if succeeds.
BOOL LLTextureAtlas::getNextAvailableSlot(S8 bits_len, S16& col, S16& row)
{
    S16 index_step = bits_len * bits_len ;

    U8 mask = 1 ;
	for(S8 i = 1 ; i < bits_len ; i++)
	{
		mask |= (1 << i) ;
	}	
   
	U8 cur_mask ;
	for(S16 index = 0 ; index < mMaxSlotsInAtlas ; index += index_step)
    {
		getPositionFromIndex(index, col, row) ;
		
		cur_mask = mask << (col & 7) ;
		if(!areUsageBitsMarked(bits_len, cur_mask, col, row))
		{
			markUsageBits(bits_len, cur_mask, col, row) ;
			mNumSlotsReserved += bits_len * bits_len ;
			
			return TRUE ;
		}
    }

   return FALSE ;
}