/*
-----------------------------------------------------------------------------
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 "OgreD3D7RenderSystem.h"
#include "OgreDDDriver.h"
#include "OgreDDVideoModeList.h"
#include "OgreDDVideoMode.h"
#include "OgreD3D7DeviceList.h"
#include "OgreD3D7Device.h"
#include "OgreLogManager.h"
#include "OgreException.h"

namespace Ogre {
    DDDriver DDDriver::operator=(const DDDriver &orig)
    {


        mGuid = orig.mGuid;
        //mDriverDesc = new char(strlen(orig.mDriverDesc) + 1);
        //strcpy(mDriverDesc, orig.mDriverDesc);
        mDriverDesc = orig.mDriverDesc;
        //mDriverName = new char(strlen(orig.mDriverName)+1);
        //strcpy(mDriverName, orig.mDriverName);
        mDriverName = orig.mDriverName;
        mPrimaryDisplay = orig.mPrimaryDisplay;
        active3DDevice = orig.active3DDevice;
        activeVideoMode = orig.activeVideoMode;
        lpD3D = orig.lpD3D;
        lpDD7 = orig.lpDD7;
        lpDDClipper = orig.lpDDClipper;
        lpDDSBack = orig.lpDDSBack;
        lpDDSPrimary = orig.lpDDSPrimary;
        mSWCaps = orig.mSWCaps;
        mHWCaps = orig.mHWCaps;
        mDeviceList = orig.mDeviceList;
        mVideoModeList = orig.mVideoModeList;


        return *this;

    }

    // Default constructor
    DDDriver::DDDriver()
    {
        // Init pointers
        active3DDevice = NULL;
        activeHWnd = 0;
        activeVideoMode = NULL;
        lpD3D = NULL;
        lpDD7 = NULL;
        lpDDClipper = NULL;
        lpDDSBack = NULL;
        lpDDSPrimary = NULL;
        mDeviceList = NULL;
        mVideoModeList = NULL;


    }

    DDDriver::~DDDriver()
    {
        // Delete related system objects
        if (mDeviceList)
            delete mDeviceList;
        if (mVideoModeList)
            delete mVideoModeList;

    }


    // Copy Constructor
    DDDriver::DDDriver(const DDDriver &ob)
    {


        mGuid = ob.mGuid;
        //mDriverDesc = new char(strlen(ob.mDriverDesc) + 1);
        //strcpy(mDriverDesc, ob.mDriverDesc);
        mDriverDesc = ob.mDriverDesc;
        //mDriverName = new char(strlen(ob.mDriverName)+1);
        //strcpy(mDriverName, ob.mDriverName);
        mDriverName = ob.mDriverName;
        mPrimaryDisplay = ob.mPrimaryDisplay;
        active3DDevice = ob.active3DDevice;
        activeVideoMode = ob.activeVideoMode;
        lpD3D = ob.lpD3D;
        lpDD7 = ob.lpDD7;
        lpDDClipper = ob.lpDDClipper;
        lpDDSBack = ob.lpDDSBack;
        lpDDSPrimary = ob.lpDDSPrimary;
        mSWCaps = ob.mSWCaps;
        mHWCaps = ob.mHWCaps;
        mDeviceList = ob.mDeviceList;
        mVideoModeList = ob.mVideoModeList;

    }

    // Constructor with enumeration details
    DDDriver::DDDriver(GUID FAR *lpGuid,
                                LPSTR lpDriverDescription,
                                LPSTR lpDriverName)
    {
        HRESULT hr;

        // Init pointers
        active3DDevice = NULL;
        activeHWnd = 0;
        activeVideoMode = NULL;
        lpD3D = NULL;
        lpDD7 = NULL;
        lpDDClipper = NULL;
        lpDDSBack = NULL;
        lpDDSPrimary = NULL;
        mDeviceList = NULL;
        mVideoModeList = NULL;

        // Copy GUID, Description and Name
        // Deal with NULL (default display driver)
        if (lpGuid)
        {
            memcpy(&mGuid, lpGuid, sizeof(GUID));
            mPrimaryDisplay = false;
        }
        else
            mPrimaryDisplay = true;


        mDriverDesc = String(lpDriverDescription);
        //mDriverDesc = new char(strlen(lpDriverDescription)+1);
        //strcpy(mDriverDesc, lpDriverDescription);

        mDriverName = String(lpDriverName);
        //mDriverName = new char(strlen(lpDriverName)+1);
        //strcpy(mDriverName, lpDriverName);


        // Get capabilities
        mHWCaps.dwSize = sizeof(DDCAPS);
        mSWCaps.dwSize = sizeof(DDCAPS);

        hr = directDraw()->GetCaps(&mHWCaps, &mSWCaps);
        if (FAILED(hr))
            throw Exception(hr, "Cannot get direct draw driver capabilities.",
                "DIM_DDDriver - directDraw()");


    }

    void DDDriver::createWindowSurfaces(HWND hWnd, unsigned int width, unsigned int height, unsigned int colourDepth, bool fullScreen,
            LPDIRECTDRAWSURFACE7 *front, LPDIRECTDRAWSURFACE7 *back)
    {

        char msg[150];
        DWORD dwFlags;
        DDSURFACEDESC2 ddsd;
        HRESULT hr;
        DDSCAPS2 ddscaps;
        LPDIRECTDRAWCLIPPER clip;

        LogManager::getSingleton().logMessage("Creating DirectDraw surfaces for window with dimensions:");

        if (fullScreen)
            sprintf(msg, "  FULLSCREEN w:%i h:%i bpp:%i", width, height, colourDepth);
        else
            sprintf(msg, "  WINDOWED w:%i h:%i", width, height);

        LogManager::getSingleton().logMessage(msg);

        // Set co-op level
        if (fullScreen)
        {
            dwFlags = DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE;
        }
        else
        {
            dwFlags = DDSCL_NORMAL;
        }

        // Also set FPU cooperative mode (optimisation)
        dwFlags = dwFlags | DDSCL_FPUSETUP;

        hr = directDraw()->SetCooperativeLevel(hWnd, dwFlags);
        if (FAILED(hr))
            throw Exception(hr, "Error setting cooperative mode",
                "DDDriver - createWindowSurfaces");


        // Create surfaces
        // If we're running fullscreen, create a complex flipping
        // chain surface (implicit back buffer)
        // Otherwise we need separate back buffers

        if (fullScreen)
        {
            // Set Video Mode
            hr = lpDD7->SetDisplayMode(width, height, colourDepth, 0, 0);
            if (FAILED(hr))
                throw Exception(hr, "Unable to set fullScreen display mode.", "DDDriver - createWindowSurfaces");

            // Set up surfaces
            ZeroMemory( &ddsd, sizeof(DDSURFACEDESC2) );
            ddsd.dwSize = sizeof(DDSURFACEDESC2);
            ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
            ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX |
                DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE;

            // Create a single back buffer
            ddsd.dwBackBufferCount = 1;
            hr = directDraw()->CreateSurface(&ddsd,front,NULL);
            if (FAILED(hr))
                throw Exception(hr, "Error creating linked surface buffers",
                    "DDDriver - createWindowSurfaces");

            // Get back buffer
            ZeroMemory(&ddscaps, sizeof(DDSCAPS2));
            ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
            hr = (*front)->GetAttachedSurface(&ddscaps, back);

            if (FAILED(hr))
                throw Exception(hr, "Error retrieving linked back buffer",
                    "DDDriver - createWindowSurfaces");

            LogManager::getSingleton().logMessage("Successfully created full screen rendering surface / flipping chain.");
        }
        else
        {
            // Explicitly create front and back buffers, and create
            // a clipper object

            // Standard non-auto-flip primary
            ZeroMemory( &ddsd, sizeof(DDSURFACEDESC2) );
            ddsd.dwSize  = sizeof(DDSURFACEDESC2);
            ddsd.dwFlags = DDSD_CAPS;
            ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
            hr = directDraw()->CreateSurface(&ddsd,front,NULL);
            if (FAILED(hr))
                throw Exception(hr, "Error creating primary surface buffer",
                    "DDDriver - createWindowSurfaces");


            // Create offscreen buffer
            ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
            ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |
                DDSCAPS_3DDEVICE;
            // Set the dimensions of the back buffer. Note that if our window changes
            // size, we need to destroy this surface and create a new one.
            ddsd.dwWidth  = width;
            ddsd.dwHeight = height;
            // Create the back buffer. The most likely reason for failure is running
            // out of video memory. (A more sophisticated app should handle this.)
            hr = directDraw()->CreateSurface( &ddsd, back, NULL );
            if( FAILED( hr ) )
                throw Exception(hr, "Error creating back surface buffer",
                    "DDDriver - createWindowSurfaces");

            LogManager::getSingleton().logMessage("Windowed mode rendering & display surfaces created.");

            // Create clipper
            hr = directDraw()->CreateClipper(0, &clip, NULL);
            if (FAILED(hr))
                throw Exception(hr, "Error creating clipper",
                    "DDDriver - createWindowSurfaces");

            // Assign it to the window
            clip->SetHWnd(0,hWnd);
            // Assign it to primary surface
            (*front)->SetClipper(clip);
            clip->Release();

            LogManager::getSingleton().logMessage("Window clipper created.");

        }



    }


    String DDDriver::DriverName(void) const
    {
        return mDriverName;
    }

    String DDDriver::DriverDescription(void) const
    {
        return mDriverDesc;
    }

    D3DDeviceList* DDDriver::get3DDeviceList(void)
    {
        if (!mDeviceList)
            mDeviceList = new D3DDeviceList(lpD3D);

        return mDeviceList;
    }

    DDVideoModeList* DDDriver::getVideoModeList(void)
    {
        if (!mVideoModeList)
            mVideoModeList = new DDVideoModeList(lpDD7);

        return mVideoModeList;
    }

    DDVideoMode* DDDriver::getActiveVideoMode(void)
    {
        return activeVideoMode;
    }


    D3DDevice* DDDriver::get3DDevice(void)
    {
        return active3DDevice;
    }


    LPDIRECTDRAW7 DDDriver::directDraw()
    {
        HRESULT rVal;

        if (!lpDD7)
        {
            // We need to create a direct draw object
            // Create with GUID for this driver
            if (mPrimaryDisplay)
                rVal = DirectDrawCreateEx( NULL, (VOID**)&lpDD7, IID_IDirectDraw7, NULL );
            else
                rVal = DirectDrawCreateEx( &mGuid, (VOID**)&lpDD7, IID_IDirectDraw7, NULL );
            if (rVal != DD_OK)
                throw Exception(rVal, "Cannot create direct draw interface.",
                    "DIM_DDDriver - directDraw()");

            // Get Direct3D interface too
            lpDD7->QueryInterface(IID_IDirect3D7, (VOID**)&lpD3D);


        }

        return lpDD7;
    }

    void DDDriver::Cleanup(void)
    {
        // Tell 3D Device to clean itself up
        if (active3DDevice)
        {
            active3DDevice->Cleanup();
        }

        if (activeHWnd && lpDD7)
            lpDD7->SetCooperativeLevel(activeHWnd, DDSCL_NORMAL);

        // Release DirectX system objects

        if (lpDDSBack)
        {
            lpDDSBack->Release();
            lpDDSBack = NULL;
        }

        if (lpDDSPrimary)
        {
            lpDDSPrimary->Release();
            lpDDSPrimary = NULL;
        }

        if (lpD3D)
        {
            lpD3D->Release();
            lpD3D = NULL;
        }

        if (lpDD7)
        {
            lpDD7->Release();
            lpDD7 = NULL;
        }




    }


    void DDDriver::CheckWindow(void)
    {
        // If windowed mode, check window size & position
        RECT rcCheck;

        if (RunningFullScreen())
            return;

        GetClientRect( activeHWnd, &rcCheck );
        ClientToScreen( activeHWnd, (POINT*)&rcCheck.left );
        ClientToScreen( activeHWnd, (POINT*)&rcCheck.right );

        // Has the window resized? If so, we need to recreate surfaces
        if ((rcCheck.right - rcCheck.left !=
                rcViewport.right - rcViewport.left) ||
            (rcCheck.bottom - rcCheck.top !=
                rcViewport.bottom - rcViewport.top))
        {
            // The window has changed size
            //g_DIMSystem->CleanupRenderer();
            //g_DIMSystem->Reinitialise();
        }
        else if (rcCheck.left != rcViewport.left ||
            rcCheck.top != rcViewport.top)
        {
            // Window has only moved
            // Just alter the blit location
            rcViewport = rcCheck;
        }


    }


    void DDDriver::FlipBuffers(void)
    {
        HRESULT hr;
        if (runningFullScreen)
        {
            // Use flipping chain
            hr = lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);


        }
        else
        {
            // Ordinary Blit
            RECT srcRect;
            srcRect.left = 0;
            srcRect.top = 0;
            srcRect.right = rcViewport.right - rcViewport.left;
            srcRect.bottom = rcViewport.bottom - rcViewport.top;
            hr = lpDDSPrimary->Blt(&rcViewport,lpDDSBack,&srcRect,DDBLT_WAIT,NULL);
        }

        if (hr == DDERR_SURFACELOST)
        {
            // Restore surfaces
            RestoreSurfaces();
        }
        else
        {
            if (FAILED(hr))
             throw Exception(hr,"Error flipping surfaces", "DDDriver::FlipBuffers");
        }

    }

    void DDDriver::RestoreSurfaces(void)
    {
        HRESULT hr;

        if (lpDDSPrimary->IsLost())
        {
            hr = lpDDSPrimary->Restore();
            if (FAILED(hr))
                throw Exception(hr, "Error restoring lost primary surface.", "DDDriver - RestoreSurfaces");
        }

        if (lpDDSBack->IsLost())
        {
            hr = lpDDSBack->Restore();
            if (FAILED(hr))
                throw Exception(hr, "Error restoring lost back buffer surface.", "DDDriver - RestoreSurfaces");
        }

    }

    void DDDriver::OutputText(int x, int y, char* text)
    {
        HDC hDC;

        // Get a DC for the surface. Then, write out the buffer
        if( lpDDSBack )
        {
            if( SUCCEEDED( lpDDSBack->GetDC(&hDC) ) )
            {
                SetTextColor( hDC, RGB(255,255,0) );
                SetBkMode( hDC, TRANSPARENT );
                ExtTextOut( hDC, x, y, 0, NULL, text, lstrlen(text), NULL );
                lpDDSBack->ReleaseDC(hDC);
            }
        }
    }

    bool DDDriver::RunningFullScreen(void) const
    {
        return runningFullScreen;
    }

    RECT DDDriver::ViewportRect(void) const
    {
        return rcViewport;
    }

    bool DDDriver::CanRenderWindowed(void) const
    {
        return (mHWCaps.dwCaps2 & DDCAPS2_CANRENDERWINDOWED) > 0;
    }


    bool DDDriver::Has3DAcceleration(void) const
    {
        return (mHWCaps.dwCaps & DDCAPS_3D);
    }



    void DDDriver::GetDisplayDetails(unsigned int& width, unsigned int& height, unsigned int& colourDepth)
    {
        // Get details from primary surface
        // This works for both windowed and fullscreen modes

        DDSURFACEDESC2 ddsd;


        if (lpDDSPrimary)
        {
            ZeroMemory(&ddsd, sizeof(DDSURFACEDESC2));
            ddsd.dwSize = sizeof(DDSURFACEDESC2);
            lpDDSPrimary->GetSurfaceDesc(&ddsd);

            width = ddsd.dwWidth;
            height = ddsd.dwHeight;
            colourDepth = ddsd.ddpfPixelFormat.dwRGBBitCount;
        }
    }

    void DDDriver::logCaps(void) const
    {
        // Sends capabilities of this driver to the log
        char msg[255];

        LogManager::getSingleton().logMessage("DirectDraw Driver Capabilities:");

        sprintf(msg, "  3D Acceleration: %i", Has3DAcceleration());
        LogManager::getSingleton().logMessage(msg);

        sprintf(msg, "  Render in a window: %i", CanRenderWindowed());
        LogManager::getSingleton().logMessage(msg);



    }

}


