#include "GraphicsApi.h"
#include "Bild.h"
#include "Globals.h"
#include "DLLRegister.h"
#include "Fenster.h"
#include "Kam3D.h"
#include "Welt3D.h"
#include "Textur.h"
#include "Zeit.h"

#include <d3d9.h>

using namespace Framework;


DirectX9::DirectX9()
    : GraphicsApi( DIRECTX9 ),
    pDirect3D( 0 ),
    pDevice( 0 ),
    pBackBuffer( 0 ),
    backRect( new D3DLOCKED_RECT() )
{
    uiBild = new Bild( 1 );
}

DirectX9::~DirectX9()
{
    backRect->pBits = NULL;
    delete backRect;
    if( pBackBuffer )
    {
        pBackBuffer->Release();
        pBackBuffer = NULL;
    }
    if( pDevice )
    {
        pDevice->Release();
        pDevice = NULL;
    }
    if( pDirect3D )
    {
        pDirect3D->Release();
        pDirect3D = NULL;
        getDLLRegister()->releaseDLL( "d3d9.dll" );
    }
    uiBild->release();
}

typedef IDirect3D9* (__stdcall* D3D9CreateFunction)(UINT);

void DirectX9::initialize( WFenster* fenster, Vec2<int> backBufferSize, bool fullScreen )
{
    if( pDirect3D )
        return GraphicsApi::initialize( fenster, backBufferSize, fullScreen );
    GraphicsApi::initialize( fenster, backBufferSize, fullScreen );

    HINSTANCE dll = getDLLRegister()->ladeDLL( "d3d9.dll", "d3d9.dll" );
    if( !dll )
    {
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "DirectX 9 konnte nicht gefunden werden." ), MB_ICONERROR );
        return;
    }
    D3D9CreateFunction direct3DCreate9 = (D3D9CreateFunction)GetProcAddress( dll, "Direct3DCreate9" );
    if( !direct3DCreate9 )
    {
        getDLLRegister()->releaseDLL( "d3d9.dll" );
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "Der Einstiegspunkt Direct3DCreate9 fon DirectX 9 konnte nicht gefunden werden." ), MB_ICONERROR );
        return;
    }
    pDirect3D = direct3DCreate9( D3D_SDK_VERSION );

    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof( d3dpp ) );
    d3dpp.Windowed = !fullScreen;
    d3dpp.hDeviceWindow = fenster ? fenster->getFensterHandle() : 0;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
    d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    d3dpp.BackBufferHeight = this->backBufferSize.y;
    d3dpp.BackBufferWidth = this->backBufferSize.x;

    uiBild->neuBild( this->backBufferSize.x, this->backBufferSize.y, 0xFF000000 );

    HRESULT result = pDirect3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, fenster->getFensterHandle(),
                                              D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE, &d3dpp, &pDevice );
    if( result != S_OK )
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "DirectX 9 konnte nicht initialisiert werden." ), MB_ICONERROR );
    if( pDevice )
        result = pDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer );
    if( result != S_OK )
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "DirectX 9 konnte nicht initialisiert werden." ), MB_ICONERROR );
}

void DirectX9::update()
{
    if( !pDirect3D )
        return;
    backRect->pBits = NULL;
    if( pBackBuffer )
    {
        pBackBuffer->Release();
        pBackBuffer = NULL;
    }
    if( pDevice )
    {
        pDevice->Release();
        pDevice = NULL;
    }
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof( d3dpp ) );
    d3dpp.Windowed = !fullScreen;
    d3dpp.hDeviceWindow = fenster->getFensterHandle();
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
    d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    if( !backBufferSize.x || !backBufferSize.y )
        backBufferSize = fenster->getKörperGröße();
    d3dpp.BackBufferHeight = backBufferSize.y;
    d3dpp.BackBufferWidth = backBufferSize.x;

    uiBild->neuBild( backBufferSize.x, backBufferSize.y, 0xFF000000 );

    HRESULT result = pDirect3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, fenster->getFensterHandle(),
                                              D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE, &d3dpp, &pDevice );
    if( result != S_OK )
        WMessageBox( fenster->getFensterHandle(), new Text( "Fehler" ), new Text( "DirectX 9 konnte nicht initialisiert werden." ), MB_ICONERROR );
    if( pDevice )
        result = pDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer );
    if( result != S_OK )
        WMessageBox( fenster->getFensterHandle(), new Text( "Fehler" ), new Text( "DirectX 9 konnte nicht initialisiert werden." ), MB_ICONERROR );
}

void DirectX9::beginFrame( bool fill2D, bool fill3D, int fillColor )
{
    if( fill2D )
        uiBild->setFarbe( fillColor );
}

void DirectX9::renderKamera( Kam3D* zKamera )
{
    Mat4< float > mat = zKamera->getProjectionMatrix();
    Mat4< float > inv = zKamera->getViewMatrix().getInverse();
    Welt3D* welt = zKamera->zWelt();
    ZeitMesser zm = ZeitMesser();
    zm.messungStart();
    for( int x = 0; x < zKamera->zViewPort()->width; x++ )
    {
        for( int y = 0; y < zKamera->zViewPort()->height; y++ )
        {
            Vec3< float > wPoint = Vec3< float >( x / (0.5f * zKamera->zViewPort()->width) - 1, y / (0.5f * zKamera->zViewPort()->height) - 1, 0.1f );
            Vec3< float > wPoint2 = Vec3< float >( wPoint.x, wPoint.y, 1.f );
            wPoint.x = (wPoint.x * 0.1f) / mat.elements[ 0 ][ 0 ];
            wPoint.y = (wPoint.y * 0.1f) / mat.elements[ 1 ][ 1 ];
            wPoint2.x = wPoint2.x / mat.elements[ 0 ][ 0 ];
            wPoint2.y = wPoint2.y / mat.elements[ 1 ][ 1 ];
            wPoint = inv * wPoint;
            wPoint2 = inv * wPoint2;
            Vec3< float > wDir = wPoint2 - wPoint;
            uiBild->setPixelDP( x + (int)zKamera->zViewPort()->x, y + (int)zKamera->zViewPort()->y, welt->traceRay( wPoint, wDir ) );
        }
    }
    zm.messungEnde();
    std::cout << zm.getSekunden() << "\n";
}

void DirectX9::presentFrame()
{
    if( !uiBild->getBuffer() )
        return;
    HRESULT result;
    result = pBackBuffer->LockRect( backRect, 0, 0 );
    if( result != S_OK )
    {
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "Es ist ein Fehler beim rendern aufgetreten." ), MB_ICONERROR );
        update();
    }
    // kopieren zum Bildschrirm 
    int* bgBuff = uiBild->getBuffer();
    int tmpBr = sizeof( D3DCOLOR ) * uiBild->getBreite();
    for( int y = 0, pitch = 0, bry = 0; y < uiBild->getHeight(); ++y, pitch += backRect->Pitch, bry += uiBild->getBreite() )
        memcpy( &((BYTE*)backRect->pBits)[ pitch ], (void*)&(bgBuff[ bry ]), tmpBr );
    // Beende Bild 
    result = pBackBuffer->UnlockRect();
    if( result != S_OK )
    {
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "Es ist ein Fehler beim rendern aufgetreten." ), MB_ICONERROR );
        update();
    }
    if( result != S_OK )
    {
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "Es ist ein Fehler beim rendern aufgetreten." ), MB_ICONERROR );
        update();
    }
    result = pDevice->Present( 0, 0, 0, 0 );
    if( result != S_OK )
    {
        WMessageBox( fenster ? fenster->getFensterHandle() : 0, new Text( "Fehler" ), new Text( "Es ist ein Fehler beim rendern aufgetreten." ), MB_ICONERROR );
        update();
    }
}

Textur* DirectX9::createOrGetTextur( const char* name, Bild* b )
{
    Textur* ret = new DX9Textur();
    ret->setBildZ( b );
    return ret;
}

Bild* DirectX9::zUIRenderBild() const
{
    return uiBild;
}

DXBuffer* DirectX9::createIndexBuffer()
{
    throw "Unsupported Operation for DirectX9 Graphics API. Use DirectX11 or 12";
}

DXBuffer* DirectX9::createVertexBuffer()
{
    throw "Unsupported Operation for DirectX9 Graphics API. Use DirectX11 or 12";
}