/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2006 Torus Knot Software Ltd
Also see acknowledgements in Readme.html

This program 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; either version 2 of the License, or (at your option) any later
version.

This program 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 program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.

You may alternatively use this source under the terms of a specific version of
the OGRE Unrestricted License provided you have obtained such a license from
Torus Knot Software Ltd.
-----------------------------------------------------------------------------
*/

#include "OgreHeightmapTerrainPageSource.h"
#include "OgreTerrainPage.h"
#include "OgreException.h"
#include "OgreStringConverter.h"
#include "OgreTerrainSceneManager.h"
#include "OgreResourceManager.h"
#include "OgreLogManager.h"

namespace Ogre {

    //-------------------------------------------------------------------------
    HeightmapTerrainPageSource::HeightmapTerrainPageSource()
        : mIsRaw(false), mFlipTerrain(false), mPage(0)
    {
    }
    //-------------------------------------------------------------------------
    HeightmapTerrainPageSource::~HeightmapTerrainPageSource()
    {
        shutdown();
    }
    //-------------------------------------------------------------------------
    void HeightmapTerrainPageSource::shutdown(void)
    {
        // Image will destroy itself
        delete mPage;
        mPage = 0;
    }
    //-------------------------------------------------------------------------
    void HeightmapTerrainPageSource::loadHeightmap(void)
    {
        size_t imgSize;
        // Special-case RAW format
        if (mIsRaw)
        {
            // Image size comes from setting (since RAW is not self-describing)
            imgSize = mRawSize;
            
            // Load data
            mRawData.setNull();
            DataStreamPtr stream = 
                ResourceGroupManager::getSingleton().openResource(
                    mSource, ResourceGroupManager::getSingleton().getWorldResourceGroupName());
            mRawData = MemoryDataStreamPtr(new MemoryDataStream(mSource, stream));

            // Validate size
            size_t numBytes = imgSize * imgSize * mRawBpp;
            if (mRawData->size() != numBytes)
            {
                shutdown();
                OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, 
                    "RAW size (" + StringConverter::toString(mRawData->size()) + 
                    ") does not agree with configuration settings.", 
                    "HeightmapTerrainPageSource::loadHeightmap");
            }
        }
        else
        {
            mImage.load(mSource, ResourceGroupManager::getSingleton().getWorldResourceGroupName());
            // Must be square (dimensions checked later)
            if ( mImage.getWidth() != mImage.getHeight())
            {
                shutdown();
                OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
                    "Heightmap must be square",
                    "HeightmapTerrainPageSource::loadHeightmap");
            }
            imgSize = mImage.getWidth();
        }
        //check to make sure it's the expected size
        if ( imgSize != mPageSize)
        {
            shutdown();
            String err = "Error: Invalid heightmap size : " +
                StringConverter::toString( imgSize ) +
                ". Should be " + StringConverter::toString(mPageSize);
            OGRE_EXCEPT( Exception::ERR_INVALIDPARAMS, err, 
                "HeightmapTerrainPageSource::loadHeightmap" );
        }

    }
    //-------------------------------------------------------------------------
    void HeightmapTerrainPageSource::initialise(TerrainSceneManager* tsm, 
        ushort tileSize, ushort pageSize, bool asyncLoading, 
        TerrainPageSourceOptionList& optionList)
    {
        // Shutdown to clear any previous data
        shutdown();

        TerrainPageSource::initialise(tsm, tileSize, pageSize, asyncLoading, optionList);

        // Get source image
        TerrainPageSourceOptionList::iterator ti, tiend;
        tiend = optionList.end();
        bool imageFound = false;
        mIsRaw = false;
        bool rawSizeFound = false;
        bool rawBppFound = false;
        for (ti = optionList.begin(); ti != tiend; ++ti)
        {
            String val = ti->first;
            StringUtil::trim(val);
            if (StringUtil::startsWith(val, "Heightmap.image", false))
            {
                mSource = ti->second;
                imageFound = true;
                // is it a raw?
                if (StringUtil::endsWith(mSource, "raw"))
                {
                    mIsRaw = true;
                }
            }
            else if (StringUtil::startsWith(val, "Heightmap.raw.size", false))
            {
                mRawSize = atoi(ti->second.c_str());
                rawSizeFound = true;
            }
            else if (StringUtil::startsWith(val, "Heightmap.raw.bpp", false))
            {
                mRawBpp = atoi(ti->second.c_str());
                if (mRawBpp < 1 || mRawBpp > 2)
                {
                    OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, 
                        "Invalid value for 'Heightmap.raw.bpp', must be 1 or 2",
                        "HeightmapTerrainPageSource::initialise");
                }
                rawBppFound = true;
            }
            else if (StringUtil::startsWith(val, "Heightmap.flip", false))
            {
                mFlipTerrain = StringConverter::parseBool(ti->second);
            }
            else
            {
                LogManager::getSingleton().logMessage("Warning: ignoring unknown Heightmap option '"
                    + val + "'");
            }
        }
        if (!imageFound)
        {
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, 
                "Missing option 'Heightmap.image'", 
                "HeightmapTerrainPageSource::initialise");
        }
        if (mIsRaw && 
            (!rawSizeFound || !rawBppFound))
        {
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, 
                "Options 'Heightmap.raw.size' and 'Heightmap.raw.bpp' must "
                "be specified for RAW heightmap sources", 
                "HeightmapTerrainPageSource::initialise");
        }
        // Load it!
        loadHeightmap();
    }
    //-------------------------------------------------------------------------
    void HeightmapTerrainPageSource::requestPage(ushort x, ushort y)
    {
        // Only 1 page provided
        if (x == 0 && y == 0 && !mPage)
        {
            // Convert the image data to unscaled floats
            ulong totalPageSize = mPageSize * mPageSize; 
            Real *heightData = new Real[totalPageSize];
            const uchar* pOrigSrc, *pSrc;
            Real* pDest = heightData;
            Real invScale;
            bool is16bit = false;
            
            if (mIsRaw)
            {
                pOrigSrc = mRawData->getPtr();
                is16bit = (mRawBpp == 2);
            }
            else
            {
                PixelFormat pf = mImage.getFormat();
                if (pf != PF_L8 && pf != PF_L16)
                {
                    OGRE_EXCEPT( Exception::ERR_INVALIDPARAMS, 
                        "Error: Image is not a grayscale image.",
                        "HeightmapTerrainPageSource::requestPage" );
                }

                pOrigSrc = mImage.getData();
                is16bit = (pf == PF_L16);
            }
            // Determine mapping from fixed to floating
            ulong rowSize;
            if ( is16bit )
            {
                invScale = 1.0f / 65535.0f; 
                rowSize =  mPageSize * 2;
            }
            else 
            {
                invScale = 1.0f / 255.0f; 
                rowSize =  mPageSize;
            }
            // Read the data
            pSrc = pOrigSrc;
            for (ulong j = 0; j < mPageSize; ++j)
            {
                if (mFlipTerrain)
                {
                    // Work backwards 
                    pSrc = pOrigSrc + (rowSize * (mPageSize - j - 1));
                }
                for (ulong i = 0; i < mPageSize; ++i)
                {
                    if (is16bit)
                    {
                        #if OGRE_ENDIAN == OGRE_ENDIAN_BIG
                            ushort val = *pSrc++ << 8;
                            val += *pSrc++;
                        #else
                            ushort val = *pSrc++;
                            val += *pSrc++ << 8;
                        #endif
                        *pDest++ = Real(val) * invScale;
                    }
                    else
                    {
                        *pDest++ = Real(*pSrc++) * invScale;
                    }
                }
            }

            // Call listeners
            firePageConstructed(0, 0, heightData);
            // Now turn into TerrainPage
            // Note that we're using a single material for now
            if (mSceneManager)
            {
                mPage = buildPage(heightData, 
                    mSceneManager->getOptions().terrainMaterial);
                mSceneManager->attachPage(0, 0, mPage);
            }

            // Free temp store
            delete [] heightData;
        }
    }
    //-------------------------------------------------------------------------
    void HeightmapTerrainPageSource::expirePage(ushort x, ushort y)
    {
        // Single page
        if (x == 0 && y == 0 && mPage)
        {
            delete mPage;
            mPage = 0;
        }

    }
    //-------------------------------------------------------------------------


}
