/*
	Rogue Trader.
	Copyright (c) 2007, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	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.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''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 DARRAGH COY 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.
*/

//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################
//
//
// Class Name:   GLTexture
// Purpose:      Simple class that uses the SDL and SDL_Image image loading
//               libarary to help load a texture with openGL. Also handles
//               texture binding transparently.
//
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "GLTexture.h"

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GLTexture::GLTexture()
Purpose:  Constructs the texture object. Trys to load the specified image file.
Params:   Texture file name to load
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

GLTexture::GLTexture( char * fileName )
{
    //--------------------------------------------------------------------------
    // Initialise all class variables to zero
    //--------------------------------------------------------------------------

    memset(this,0,sizeof(GLTexture));

    //--------------------------------------------------------------------------
    // Attempt to load the image with SDL and save SDL surface pointer
    //--------------------------------------------------------------------------

    SDL_Surface * sdlSurf = IMG_Load(fileName);

    //--------------------------------------------------------------------------
    // Load the texture with OpenGL if we have a valid surface: otherwise generate
    // a defualt chequered black and white texture
    //--------------------------------------------------------------------------

    if ( sdlSurf )
    {
        //----------------------------------------------------------------------
        // We have succedeeded in loading the image
        //----------------------------------------------------------------------

        // Get the width and height of the image: save also as the original width

        unsigned imageSizeX = this->sizeX = sdlSurf->w;
        unsigned imageSizeY = this->sizeY = sdlSurf->h;

        // Create a temporary array of RGBA pixels

        unsigned * imagePixels = new unsigned[imageSizeX * imageSizeY];

        //----------------------------------------------------------------------
        // Now see what format the SDL image is in and get its pixels.
        // This is where things get a little messy. GL appears to expect
        // a texture in big endian format so we must change RGBA to ABGR such
        // that when GL reads the data it will interpreted as RGBA.. We also
        // have to dabble with the various formats SDL gives us. I tried using
        // auto conversion, but unfortunately it doesn't seem to work with
        // transparency - so here we go..
        //----------------------------------------------------------------------

        if ( sdlSurf->format->BitsPerPixel == 8 )
        {
            //##################################################################
            // Palletised 8-bit image: may be transparent
            //##################################################################

            //------------------------------------------------------------------
            // See whether the image uses transparency
            //------------------------------------------------------------------

            if ( sdlSurf->flags & SDL_SRCCOLORKEY ) this->usesAlphaChannel = true;

            //------------------------------------------------------------------
            // Get all the info we need to convert to ABGR
            //------------------------------------------------------------------

            unsigned        * dst    = imagePixels;                      // pointer to destination array
            unsigned char   * src    = (unsigned char*)sdlSurf->pixels;  // pointer to source array of 8bit pixels
            unsigned transparentCol  = sdlSurf->format->colorkey;        // color index that represents transparent

            //------------------------------------------------------------------
            // SDL pads out the end of scanlines of 8-bit images such that they
            // have a size which is a multiple of 32. This is done for performance
            // reasons (aligned memory reads are faster) but we must add the amount
            // of padding when reaching the end of a scanline in order to get
            // properly onto the next scanline. Figure out the padding used
            // from image width..
            //------------------------------------------------------------------

            unsigned scanlinePadding = ( 4 - ( imageSizeX & 3 ) ) & 3;

            //------------------------------------------------------------------
            // Copy each scanline from the source to the destination
            //------------------------------------------------------------------

            for ( unsigned y = imageSizeY ; y ; y-- )
            {
                //--------------------------------------------------------------
                // Convert this scanline to ABGR
                //--------------------------------------------------------------

                for ( unsigned x = imageSizeX ; x ; x-- )
                {
                    //----------------------------------------------------------
                    // Get base color from palette table
                    //----------------------------------------------------------

                    unsigned col;

                    col  = 0xff000000;                                      // set a component of color: presume opaque at first
                    col |= sdlSurf->format->palette->colors[*src].r << 0;   // set r component of color
                    col |= sdlSurf->format->palette->colors[*src].g << 8;   // set g component of color
                    col |= sdlSurf->format->palette->colors[*src].b << 16;  // set b component of color

                    //----------------------------------------------------------
                    // If the image uses a transparent color key and the color
                    // matches the transparent color then disble the alpha bits
                    // of this image.
                    //----------------------------------------------------------

                    if  ( this->usesAlphaChannel  && * src == transparentCol ) col &= 0x00ffffff;

                    //----------------------------------------------------------
                    // Save color in destination and move on source and dest
                    // pointers to next pixel in both images
                    //----------------------------------------------------------

                    * dst = col;

                    dst++;
                    src++;
                }

                //--------------------------------------------------------------
                // Move onto next scanline: add move on the amount of bytes
                // required to get to the next dword aligned scanline.
                //--------------------------------------------------------------

                src += scanlinePadding;
            }

        }
        else if ( sdlSurf->format->BitsPerPixel == 16 )
        {
            //##################################################################
            // 16 bit true color non transparent image
            //##################################################################

            //------------------------------------------------------------------
            // Get pointers to tempoarary array and SDL surface pixels
            //------------------------------------------------------------------

            unsigned int   * dst = imagePixels;
            unsigned short * src = (unsigned short*)sdlSurf->pixels;

            //------------------------------------------------------------------
            // Figure out the amount of padding needed at the end of each scanline
            // to bring each scanline to a multiple of 4 bytes. See the 8-bit
            // case for an explanation of this.
            //------------------------------------------------------------------

            unsigned scanlinePadding = imageSizeX & 1;

            //------------------------------------------------------------------
            // 16 bit formats are a bit odd and could be encoded in many different
            // ways. use the information in the pixel format descriptor of the
            // sdl surface to ease conversion troubles.. I certainly don't know
            // before hand what format a 16 bit image will be in!!
            //------------------------------------------------------------------

            unsigned int rmask  = sdlSurf->format->Rmask;   // mask to isolate r component
            unsigned int gmask  = sdlSurf->format->Gmask;   // mask to isolate g component
            unsigned int bmask  = sdlSurf->format->Bmask;   // mask to isolate b component

            unsigned int rshift = sdlSurf->format->Rshift;  // amount we have to shift r right to get bits for r
            unsigned int gshift = sdlSurf->format->Gshift;  // amount we have to shift g right to get bits for g
            unsigned int bshift = sdlSurf->format->Bshift;  // amount we have to shift b right to get bits for b

            unsigned int rloss  = sdlSurf->format->Rloss;   // amount of precision loss for r: we must shift up by this amount to recover true color
            unsigned int gloss  = sdlSurf->format->Gloss;   // amount of precision loss for g: we must shift up by this amount to recover true color
            unsigned int bloss  = sdlSurf->format->Bloss;   // amount of precision loss for b: we must shift up by this amount to recover true color

            //------------------------------------------------------------------
            // Copy each pixel from the source to the destination
            //------------------------------------------------------------------

            for ( unsigned y = imageSizeY ; y ; y-- )
            {

                for ( unsigned x = imageSizeX ; x ; x-- )
                {
                    * dst  = 0xff000000;                                            // 16 bit images are always opaque.. I think ?
                    * dst |= ( ( ( * src & rmask ) >> rshift ) << rloss );          // recover r component from source
                    * dst |= ( ( ( * src & gmask ) >> gshift ) << gloss ) << 8;     // recover r component from source
                    * dst |= ( ( ( * src & bmask ) >> bshift ) << bloss ) << 16;    // recover r component from source

                    src++;      // move onto next pixel in input  array
                    dst++;      // move onto next pixel in output array
                }

                //--------------------------------------------------------------
                // Before moving onto the next scanline, add the amount of bytes
                // used in padding at the end of each scanline in order to get
                // properly onto the next line
                //--------------------------------------------------------------

                src += scanlinePadding;
            }
        }
        else if ( sdlSurf->format->BitsPerPixel == 24 )
        {
            //##################################################################
            // 24 bit true color non transparent image
            //##################################################################

            //------------------------------------------------------------------
            // Get pointers to tempoarary array and SDL surface pixels
            //------------------------------------------------------------------

            unsigned int  * dst = imagePixels;
            unsigned char * src = (unsigned char*) sdlSurf->pixels;

            //------------------------------------------------------------------
            // Figure out the amount of padding needed at the end of each scanline
            // to bring each scanline to a multiple of 4 bytes. See the 8-bit
            // case for an explanation of this.
            //------------------------------------------------------------------

            unsigned scanlinePadding = ( 4 - ( ( imageSizeX * 3) & 3 ) ) & 3;

            //------------------------------------------------------------------
            // Get the modifiers we need to recover correct rgb information from
            // the image data. Again like 16-bit images 24-bit images may be stored
            // in various ways..
            //------------------------------------------------------------------

            unsigned int rmask  = sdlSurf->format->Rmask;   // mask to isolate r component
            unsigned int gmask  = sdlSurf->format->Gmask;   // mask to isolate g component
            unsigned int bmask  = sdlSurf->format->Bmask;   // mask to isolate b component

            unsigned int rshift = sdlSurf->format->Rshift;  // amount we have to shift r right to get bits for r
            unsigned int gshift = sdlSurf->format->Gshift;  // amount we have to shift g right to get bits for g
            unsigned int bshift = sdlSurf->format->Bshift;  // amount we have to shift b right to get bits for b

            unsigned int rloss  = sdlSurf->format->Rloss;   // amount of precision loss for r: we must shift up by this amount to recover true color
            unsigned int gloss  = sdlSurf->format->Gloss;   // amount of precision loss for g: we must shift up by this amount to recover true color
            unsigned int bloss  = sdlSurf->format->Bloss;   // amount of precision loss for b: we must shift up by this amount to recover true color

            //------------------------------------------------------------------
            // Copy each pixel from the source to the destination
            //------------------------------------------------------------------

            for ( unsigned y = imageSizeY ; y ; y-- )
            {

                for ( unsigned x = imageSizeX ; x ; x-- )
                {
                    //----------------------------------------------------------
                    // Get the pixels of the input color: interpret as a 32 bit
                    // pixel for simplicitys sake
                    //----------------------------------------------------------

                    unsigned int src_col = *((unsigned int*)(src));

                    //----------------------------------------------------------
                    // Make up the ABGR components of the output color
                    //----------------------------------------------------------

                    * dst  = 0xff000000;                                            // since image is 24 alpha is always 255
                    * dst |= ( ( ( src_col & rmask ) >> rshift ) << rloss );        // recover the r component of the original image
                    * dst |= ( ( ( src_col & gmask ) >> gshift ) << gloss ) << 8;   // recover the r component of the original image
                    * dst |= ( ( ( src_col & bmask ) >> bshift ) << bloss ) << 16;  // recover the r component of the original image

                    src+=3;     // move onto next pixel in input  array
                    dst++;      // move onto next pixel in output array
                }

                //--------------------------------------------------------------
                // Before moving onto the next scanline, add the amount of bytes
                // used in padding at the end of each scanline in order to get
                // properly onto the next line
                //--------------------------------------------------------------

                src += scanlinePadding;
            }
        }
        else if ( sdlSurf->format->BitsPerPixel == 32 )
        {

            //##################################################################
            // 32 bit true color image with alpha channel
            //##################################################################

            //------------------------------------------------------------------
            // Set the flag saying that this texture uses the alpha channel
            //------------------------------------------------------------------

            this->usesAlphaChannel = true;

            //------------------------------------------------------------------
            // Get pointers to tempoarary array and SDL surface pixels
            //------------------------------------------------------------------

            unsigned * dst = imagePixels;
            unsigned * src = (unsigned*)sdlSurf->pixels;

            //------------------------------------------------------------------
            // Get the modifiers we need to recover correct rgb information from
            // the image data. Not one to repeat myself.. but again like the 16 and 24
            // cases we cannot rely on the data being formated in standard ARGB
            // type format, so we must employ these modifiers to get the components
            // correctly.
            //------------------------------------------------------------------

            unsigned int rmask  = sdlSurf->format->Rmask;   // mask to isolate r component
            unsigned int gmask  = sdlSurf->format->Gmask;   // mask to isolate g component
            unsigned int bmask  = sdlSurf->format->Bmask;   // mask to isolate b component
            unsigned int amask  = sdlSurf->format->Amask;   // mask to isolate a component

            unsigned int rshift = sdlSurf->format->Rshift;  // amount we have to shift r right to get bits for r
            unsigned int gshift = sdlSurf->format->Gshift;  // amount we have to shift g right to get bits for g
            unsigned int bshift = sdlSurf->format->Bshift;  // amount we have to shift b right to get bits for b
            unsigned int ashift = sdlSurf->format->Ashift;  // amount we have to shift a right to get bits for a

            unsigned int rloss  = sdlSurf->format->Rloss;   // amount of precision loss for r: we must shift up by this amount to recover true color
            unsigned int gloss  = sdlSurf->format->Gloss;   // amount of precision loss for g: we must shift up by this amount to recover true color
            unsigned int bloss  = sdlSurf->format->Bloss;   // amount of precision loss for b: we must shift up by this amount to recover true color
            unsigned int aloss  = sdlSurf->format->Aloss;   // amount of precision loss for a: we must shift up by this amount to recover true color

            //------------------------------------------------------------------
            // Do the copying from the source to the destination
            //------------------------------------------------------------------

            for ( unsigned i = imageSizeX * imageSizeY ; i ; i-- )
            {
                //--------------------------------------------------------------
                // Copy all components, but lay out in ABGR format for OpenGL
                //--------------------------------------------------------------

                * dst  = ( ( ( * src & rmask ) >> rshift ) << rloss );          // get the r component of the texture and add to color
                * dst |= ( ( ( * src & gmask ) >> gshift ) << gloss ) << 8;     // get the g component of the texture and add to color
                * dst |= ( ( ( * src & bmask ) >> bshift ) << bloss ) << 16;    // get the b component of the texture and add to color
                * dst |= ( ( ( * src & amask ) >> ashift ) << aloss ) << 24;    // get the a component of the texture and add to color

                //--------------------------------------------------------------
                // Move onto next pixels in source and dest arrays
                //--------------------------------------------------------------

                dst++;
                src++;
            }
        }

        //----------------------------------------------------------------------
        // Generate an OpenGL texture alias and select the texture
        //----------------------------------------------------------------------

        glGenTextures(1,&glTextureNum);                 // create the texture number
        glBindTexture(GL_TEXTURE_2D,glTextureNum);      // select the texture

        //----------------------------------------------------------------------
        // Load the image with OpenGL and generate mipmaps automatically
        //----------------------------------------------------------------------

        gluBuild2DMipmaps
        (
            GL_TEXTURE_2D,      // target texture mode: 2D of course! - how useful are 1D textures? : )
            GL_RGBA,            // internal format to store in: RGBA is most natural for the GPU
            imageSizeX,         // width of image
            imageSizeY,         // height of image
            GL_RGBA,            // format image being loaded is in
            GL_UNSIGNED_BYTE,   // data type per color component
            imagePixels         // actual image data
        );

        //----------------------------------------------------------------------
        // Cleanup
        //----------------------------------------------------------------------

        SDL_FreeSurface(sdlSurf);   // free up the SDL image used
        delete[] imagePixels;       // delete our temporary array of ABGR pixels
    }
    else
    {
        // Image cannot be loaded, generate and load a default image instead

        defaultImage();
    }
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GLTexture::GLTexture()
Purpose:  Constructor. Loads texture with OpenGL from the given array of data.
          Data must be in 32-bit ABGR format however.
Params:   data array, width of image, height of image
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

GLTexture::GLTexture( unsigned * data , unsigned width , unsigned height )
{
    //--------------------------------------------------------------------------
    // If paramters are invalid then generate a default chequered image
    //--------------------------------------------------------------------------

    if ( data == 0 || width == 0 || height == 0 )
    {
        // Invalid image, generate a default chequered image

        defaultImage();
    }
    else
    {
        //----------------------------------------------------------------------
        // Save the width and height of the original image
        //----------------------------------------------------------------------

        this->sizeX = width;
        this->sizeY = height;

        //----------------------------------------------------------------------
        // Generate an OpenGL texture alias and select the texture
        //----------------------------------------------------------------------

        glGenTextures(1,&glTextureNum);                 // create the texture number
        glBindTexture(GL_TEXTURE_2D,glTextureNum);      // select the texture

        //----------------------------------------------------------------------
        // Load the image with OpenGL and generate mipmaps automatically
        //----------------------------------------------------------------------

        gluBuild2DMipmaps
        (
            GL_TEXTURE_2D,      // target texture mode: 2D of course! - how useful are 1D textures? : )
            GL_RGBA,            // internal format to store in: RGBA is most natural for the GPU
            width,              // width of image
            height,             // height of image
            GL_RGBA,            // format image being loaded is in
            GL_UNSIGNED_BYTE,   // data type per color component
            data                // actual image data
        );
    }

}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GLTexture::defaultImage()
Purpose:  Generates a default chequered image when a particular resource cannot
          be loaded, and loads it with openGL
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void GLTexture::defaultImage()
{
    //--------------------------------------------------------------------------
    // Aallocate memory for a chequered texture of 16x16 pixels.
    //--------------------------------------------------------------------------

    unsigned * textureData = new unsigned[16*16];

    //--------------------------------------------------------------------------
    // Create the chequered pattern in the image
    //--------------------------------------------------------------------------

    for ( int y = 0 ; y < 16 ; y++ )
    for ( int x = 0 ; x < 16 ; x++ )
    {
        if ( ( x + y ) & 1 )
        {
            textureData[ y * 16 + x ] = 0xff000000;
        }
        else
        {
            textureData[ y * 16 + x ] = 0xffffffff;
        }
    }

    //--------------------------------------------------------------------------
    // Generate an OpenGL texture alias and select the texture
    //--------------------------------------------------------------------------

    glGenTextures(1,&glTextureNum);             // create the texture number
    glBindTexture(GL_TEXTURE_2D,glTextureNum);  // select the texture

    //--------------------------------------------------------------------------
    // Load the image with OpenGL and generate mipmaps automatically
    //--------------------------------------------------------------------------

    gluBuild2DMipmaps
    (
        GL_TEXTURE_2D,          // target texture mode: 2D of course! - how useful are 1D textures? : )
        GL_RGBA,                // internal format to store in: RGBA is most natural for the GPU
        16,                     // width of image
        16,                     // height of image
        GL_RGBA,                // format image being loaded is in
        GL_UNSIGNED_BYTE,       // data type per color component
        textureData             // actual image data
    );

    assert( glGetError() == GL_NO_ERROR );

    //--------------------------------------------------------------------------
    // delete the temporary array of pixels representing the default texture
    //--------------------------------------------------------------------------

    delete[] textureData;

    //--------------------------------------------------------------------------
    // Set original width and height of texture to 16 units
    //--------------------------------------------------------------------------

    this->sizeX = 16;
    this->sizeY = 16;
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GLTexture::~GLTexture()
Purpose:  Destructor. Frees the texture resource with opengl
Params:   n/a
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

GLTexture::~GLTexture()
{
    //--------------------------------------------------------------------------
    // Delete the texture with openGL if it exists
    //--------------------------------------------------------------------------

    if ( glTextureNum != 0 )
    {
        glDeleteTextures(1,&glTextureNum);
    }
    
    // Debug only:
    
    #ifdef DEBUG
    
		assert( glTextureNum != 0xffffffff ); glTextureNum = 0xffffffff;
    
    #endif
}
