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

Copyright (c) 2000-2005 The OGRE Team
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.
-----------------------------------------------------------------------------
*/
#include "OgreD3D7Texture.h"
#include "OgreException.h"
#include "OgreImage.h"
#include "OgreLogManager.h"
#include "OgreHardwarePixelBuffer.h"
#include "OgreRoot.h"
#include "OgreD3D7HardwarePixelBuffer.h"

namespace Ogre {
//-----------------------------------------------------------------------------  

D3D7HardwarePixelBuffer::D3D7HardwarePixelBuffer(HardwareBuffer::Usage usage, IDirect3DDevice7 *device):
	HardwarePixelBuffer(0, 0, 0, PF_UNKNOWN, usage, false, false),
	mD3DDevice(device), mSurface(0),
	mDoMipmapGen(0)
{
}
D3D7HardwarePixelBuffer::~D3D7HardwarePixelBuffer()
{
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::bind(LPDIRECTDRAWSURFACE7 surface, PixelFormat format)
{
	mSurface = surface;
	DDSURFACEDESC2 desc;
	desc.dwSize = sizeof(DDSURFACEDESC2);
	if(surface->GetSurfaceDesc(&desc) != D3D_OK)
		OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Could not get surface information",
		 "D3D7HardwarePixelBuffer::D3D7HardwarePixelBuffer");
	mWidth = desc.dwWidth;
	mHeight = desc.dwHeight;
	assert((desc.lPitch%PixelUtil::getNumElemBytes(format))==0);
	mRowPitch = desc.lPitch/PixelUtil::getNumElemBytes(format);
	mDepth = 1;
	mFormat = format;

	// Calculate pitch and size
	mSlicePitch = mRowPitch*mHeight;
	mSizeInBytes = mRowPitch*mHeight*PixelUtil::getNumElemBytes(format);

	//std::stringstream str;
	//str << "bind" << " " << surface << " " << mWidth << " " << mHeight << " " << mFormat << " " << mRowPitch;
    //LogManager::getSingleton().logMessage(str.str());
}
//-----------------------------------------------------------------------------  
PixelBox D3D7HardwarePixelBuffer::lockImpl(const Image::Box lockBox,  LockOptions options)
{
	assert(lockBox.getDepth() == 1);
	// Set extents and format
	PixelBox rval(lockBox, mFormat);

	// Set locking flags according to options
	DWORD flags = DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT;
	switch(options)
	{
	case HBL_DISCARD:
		flags |= DDLOCK_WRITEONLY;
		break;
	case HBL_READ_ONLY:
		flags |= DDLOCK_READONLY;
		break;
	default: 
		break;
	};
	
	DDSURFACEDESC2 lrect; // Filled in by D3D
	HRESULT hr;

	if (lockBox.left == 0 && lockBox.top == 0 
		&& lockBox.right == mWidth && lockBox.bottom == mHeight)
	
	{
		// Lock whole surface
		mpRect = NULL;
	}
	else
	{
		mLockRect.left = lockBox.left;
		mLockRect.right = lockBox.right;
		mLockRect.top = lockBox.top;
		mLockRect.bottom = lockBox.bottom;
		mpRect = &mLockRect;
	}
	hr = mSurface->Lock(mpRect, &lrect, flags, NULL);
	if (FAILED(hr))		
		OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Surface locking failed",
		 	"D3D7HardwarePixelBuffer::lockImpl");
	
	rval.rowPitch = mRowPitch;
	rval.slicePitch = mSlicePitch;
	rval.data = lrect.lpSurface;
	return rval;
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::unlockImpl(void)
{
	HRESULT hr;	
	// Crazy DX7 convention: Unlock needs the same rect pointer that Lock got
	hr = mSurface->Unlock(mpRect);	
	if( FAILED( hr ) )
        OGRE_EXCEPT( hr, "Error during Unlock operation.", "D3D7HardwarePixelBuffer::unlockImpl" );

	if(mDoMipmapGen)
	{
		// Update mipmaps
		_genMipmaps(mLockRect);
	}
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::blit(HardwarePixelBuffer *src, const Image::Box &srcBox, const Image::Box &dstBox)
{
	// TODO
	// D3DXLoadTextureFromSurface
	HardwarePixelBuffer::blit(src, srcBox, dstBox);
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::blitFromMemory(const PixelBox &src, const Image::Box &dstBox)
{
	/* No 3D textures support in D3D7 */
	assert(src.getDepth() == 1);
	assert(dstBox.getDepth() == 1);

	/* We need a temporary surface in which to load the image data. */
    LPDIRECTDRAWSURFACE7 pddsTempSurface;
    HRESULT hr;
    D3DX_SURFACEFORMAT surfFmt, texFmt;
	MemoryDataStreamPtr buf;
	PixelBox converted;

    /* Compute the pixel format for the image. */
	if(src.format != mFormat)
	{
		/* Format is not directly supported, or the pixelbox is not consecutive; conversion is needed */
		PixelFormat tempFormat = mFormat;
		buf.bind(new MemoryDataStream(
			PixelUtil::getMemorySize(src.getWidth(), src.getHeight(), src.getDepth(),
										tempFormat)));
		converted = PixelBox(src.getWidth(), src.getHeight(), src.getDepth(), tempFormat, buf->getPtr());
		PixelUtil::bulkPixelConversion(src, converted);
	} 
	else
	{
		converted = src;
	}
	surfFmt = D3DTexture::OgreFormat_to_D3DXFormat(mFormat);
	
    /* Compute the current pixel format of the texture. */
	texFmt = D3DTexture::OgreFormat_to_D3DXFormat(mFormat);
	
    /* We generate the mip-maps by hand. */
    DWORD mipFlag, numMips;
    mipFlag = D3DX_TEXTURE_NOMIPMAP;

    /* Set the width and height. */
    DWORD dwWidth = src.getWidth(), dwHeight = src.getHeight();

    /* Create the temporary surface. */
    if( FAILED( hr = D3DXCreateTexture(
        mD3DDevice,
        NULL,
        &dwWidth,
        &dwHeight,
        &texFmt,
        NULL,
        &pddsTempSurface,
        &numMips ) ) )
    {
        OGRE_EXCEPT( hr, "Error during blit operation.", "D3D7HardwarePixelBuffer::blitFromMemory" );
    }

    /* Load the image into the temporary surface. */
    if( FAILED( hr = D3DXLoadTextureFromMemory(
        mD3DDevice,
        pddsTempSurface,
        D3DX_DEFAULT,
        converted.data,
        NULL,
        surfFmt,
		converted.rowPitch * PixelUtil::getNumElemBytes(converted.format),
        NULL,
        D3DX_FT_LINEAR ) ) )
    {
        pddsTempSurface->Release();
        OGRE_EXCEPT( hr, "Error during D3DXLoadTextureFromMemory operation.", "D3D7HardwarePixelBuffer::blitFromMemory" );
    }
	
	RECT texRect;
	texRect.top = dstBox.top;
	texRect.bottom = dstBox.bottom;
	texRect.left = dstBox.left;
	texRect.right = dstBox.right;

    if( FAILED( hr = mSurface->Blt(
        &texRect,
        pddsTempSurface, 
        NULL,
        DDBLT_WAIT,
        NULL ) ) )
    {
        pddsTempSurface->Release();
        OGRE_EXCEPT( hr, "Error during blit operation.", "D3DTexture::blitImage" );
    }

    /* Release the temporary surface. */
    pddsTempSurface->Release();

	if(mDoMipmapGen)
		_genMipmaps(texRect);
	OgreUnguard();
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::blitToMemory(const Image::Box &srcBox, const PixelBox &dst)
{
	OGRE_EXCEPT(Exception::UNIMPLEMENTED_FEATURE,
				"Function not yet implemented",
				"D3D7HardwarePixelBuffer::blitToMemory");
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::_setMipmapping(bool doMipmapGen)
{
	mDoMipmapGen = doMipmapGen;
}
//-----------------------------------------------------------------------------  
void D3D7HardwarePixelBuffer::_genMipmaps(const RECT &texRect)
{
	/* Load the image in all the mip-maps (if there are any, that is). */
	LPDIRECTDRAWSURFACE7 ddsMipLevel, ddsNextLevel;
	DDSCAPS2 ddsCaps;
	HRESULT mipRes = DD_OK;
	uint mipLevel = 1;

	ZeroMemory(&ddsCaps, sizeof(DDSCAPS2));
	ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP;

	/* Get the base level and increae the reference count. */
	ddsMipLevel = mSurface;
	ddsMipLevel->AddRef();
	
	/* While we can get a next level in the mip-map chain. */
	while( ddsMipLevel->GetAttachedSurface( &ddsCaps, &ddsNextLevel ) == DD_OK )
	{
		/* Calculate the destination rect. */
		RECT mipRect = { 
			texRect.left >> mipLevel,
			texRect.top  >> mipLevel,
			texRect.right >> mipLevel,
			texRect.bottom >> mipLevel
		};

		/* Blit using D3DX in order to use bilinear filtering. */
		D3DXLoadTextureFromSurface(
			mD3DDevice,
			ddsNextLevel,
			0,
			mSurface,
			(RECT*)&texRect,
			(RECT*)&mipRect,
			D3DX_FT_LINEAR );
		
		/* Release the current level and get the next one, incrementing the mip depth. */
		ddsMipLevel->Release();
		ddsMipLevel = ddsNextLevel;
		mipLevel++;
	}

	/* Release the last mip-map level surface. */
	ddsMipLevel->Release();
}


};
