/*
	Wheels Of Steel
	Copyright (c) 2006, 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:   Mipmap
// Purpose:      Mipmap creation and drawing. A mipmap is a multiple level of detail bitmap
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "Mipmap.h"

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     Mimap::Mimap
Purpose:  Loads the bitmap file and creates the levels of detail
Params:   Bitmap file to load, if bitmap is transparent, program window
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

Mipmap::Mipmap(char * bitmapFileName, bool transparent, Window * window)
{
    //--------------------------------------------------------------------------
    // Check params for validity
    //--------------------------------------------------------------------------

    if ( bitmapFileName == 0 || window == 0 )
    {
        // No file name or window given: invalidate the mipmap

        valid_mipmap = false;

        // Abort creation of mipmap

        return;
    }
    else
    {
        // Everything ok.. so far..

        valid_mipmap = true;
    }

    //--------------------------------------------------------------------------
    // Attempt to load the base image
    //--------------------------------------------------------------------------

    // Load the base image and save its pointer

    Bitmap * bmpBase = window->loadBitmap(bitmapFileName,transparent);

    // If the base image wasn't loaded successfully then delete and abort

    if ( ! bmpBase->loadedSuccessfully() )
    {
        valid_mipmap = false;   // invalidate mipmap
        delete bmpBase;         // delete bitmap
        return;                 // abort further mipmap generation
    }

    base_width  = bmpBase->getWidth();      // save base image width
    base_height = bmpBase->getHeight();     // save base image height

    //--------------------------------------------------------------------------
    // Determine how many mipmap levels there will be from size of base image
    //--------------------------------------------------------------------------

    bitmap_count = 0;   // set the bitmap count of the mimap to zero initially

    {
        // Get width and height of base image

        int width  = bmpBase->getWidth();
        int height = bmpBase->getHeight();

        // See which is larger

        if ( width >= height )
        {
            // Determine the number of mipmaps by continously dividing width by two

            do { width  /= 2; bitmap_count++; }  while( width > 0 );

        }
        else
        {
            // Determine the number of mipmaps by continously dividing height by two

            do { height /= 2; bitmap_count++; }  while( height > 0 );
        }
    }

    //--------------------------------------------------------------------------
    // Create an array to hold pointers to bitmaps. Save first element as base image
    //--------------------------------------------------------------------------

    bitmap_array    = new Bitmap * [bitmap_count];  // Create bitmap array
    bitmap_array[0] = bmpBase;                      // save first element

    //--------------------------------------------------------------------------
    // Create each succesive mipmap level
    //--------------------------------------------------------------------------

    for ( unsigned int mipLevel = 1; mipLevel < bitmap_count; mipLevel++ )
    {
        // Decide on width and height of this mipmap level

        unsigned int width   = bmpBase->getWidth()  >> mipLevel;
        unsigned int height  = bmpBase->getHeight() >> mipLevel;

        // Make sure width and height are 1 or greater

        width  = width  > 0 ? width  : 1;
        height = height > 0 ? height : 1;

        // Create a temporary array of 32bit rgb pixels to store the mipmap level

        unsigned int * mipData  = new unsigned int[width*height];

        // Get the base images pixel array and width + height

        unsigned int base_width         =  bmpBase->getWidth();
        unsigned int base_height        =  bmpBase->getHeight();
        unsigned int const * baseData   =  bmpBase->getBits();

        // Figure out how much bigger the base image is than the mipmap level in terms of width and height

        float baseScale_width  = (float)bmpBase->getWidth()  / (float) width;
        float baseScale_height = (float)bmpBase->getHeight() / (float) height;

        // Now perform a nearest neighbour match for the pixels in the mipmap level to the base image

        for ( unsigned int y = 0 ; y < height ; y++ )
        for ( unsigned int x = 0 ; x < width  ; x++ )
        {
            // Figure out which pixel to map to from the base image for this mipmap level pixel

            unsigned int base_x = (int)(x * baseScale_width );
            unsigned int base_y = (int)(y * baseScale_height);

            if ( base_x - (int)(base_x) >= 0.5f ) base_x+=1;     // round the base position value to get the truest match
            if ( base_y - (int)(base_y) >= 0.5f ) base_y+=1;     // round the base position value to get the truest match

            // Make sure they don't go over array boundarys

            base_x = base_x >= base_width  ? base_width  - 1 : base_x;
            base_y = base_y >= base_height ? base_height - 1 : base_y;

            // Assign the chosen base image pixel to this mipmap level pixel

            mipData[ x + y * width] = baseData[ base_x + base_y * base_width] & 0x00ffffff;
        }

        // If the mipmap is transparent then make sure the bottom right pixel equals the transparent color

        if ( transparent )
        {
            mipData[ width * height - 1] =  baseData[ base_width * base_height - 1] & 0x00ffffff;
        }

        // Create a bitmap object and save its pointer in the bitmap array

        bitmap_array[mipLevel] = window->loadBitmap
        (
            mipData,        // data
            width,          // width of image
            height,         // height of image
            transparent     // is image transparent ?
        );

        // delete the tempoarary array of pixels

        delete mipData;

    } // end of generate mimap level loop

}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     Mimap::~Mimap
Purpose:  Mipmap destructor
Params:   Frees all mipmap resources
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

Mipmap::~Mipmap()
{
    // If the mipmap is invalid then abort

    if ( valid_mipmap == false ) return;

    // Delete every single bitmap in the mipmap

    for ( unsigned int mipLevel = 0 ; mipLevel < bitmap_count ; mipLevel++ )
    {
        delete bitmap_array[mipLevel];
    }

    // delete the array of pointers to bitmaps

    delete bitmap_array;
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     Mimap::draw
Purpose:  Draws the mipmap to the specified window. Selects a level of detail
          lower than or equal to specified size in pixels. Using mipmaps helps
          prevent the ugly artifacts caused by gdi downscaling and is much much faster.
Params:   Window to draw to, position, size
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

void Mipmap::draw
(
    Window * window,
    unsigned int x,
    unsigned int y,
    unsigned int width,
    unsigned int height

) const
{
    // If invalid window or zero size then abort. Optimisation: cull images of width 2 or less.

    if ( window == 0 || width <= 2 || height == 0 ) return;

    // Select level of detail to draw

    unsigned int level          = 0;

    unsigned int levelWidth     = base_width;
    unsigned int levelHeight    = base_height;

    while ( levelWidth > width || levelHeight > height )
    {
        // selected level greater than output dimensions. downsize to the next level down.

        level++;
        levelWidth  >>= 1;
        levelHeight >>= 1;
    }

    // Draw selected level of detail

    window->drawBitmap
    (
        bitmap_array[level],
        x,
        y,
        width,
        height
    );
}
