#include "Bildschirm.h"
#include "Bild.h"
#include "Fenster.h"
#include "Text.h"
#include "Zeichnung.h"
#include "Globals.h"
#include "Zeit.h"
#include "ToolTip.h"
#include "MausEreignis.h"
#include <iostream>
#include "Datei.h"
#include "Mat3.h"
#include "Model3D.h"
#include "GraphicsApi.h"
#ifdef WIN32
#include "Kam3D.h"
#include <DirectXMath.h>
#include <D3Dcompiler.h>
#include <d3d11.h>
#include <d3d9.h>
#include "comdef.h"
#endif

using namespace Framework;

// Inhalt der Bildschirmklass aus Bildschirm.h
// Konstruktor 
Bildschirm::Bildschirm( WFenster *f )
    : ReferenceCounter(),
    api( 0 ),
    fenster( f ),
    members( new RCArray<Zeichnung>() ),
    fillColor( 0xFF000000 ),
    deckFarbe( 0 ),
    onTop( 0 ),
    renderOnTop( 0 ),
    renderZeichnungen( 1 ),
    rendering( 0 ),
    renderZeit( new ZeitMesser() ),
    tips( new RCArray< ToolTip >() ),
    tipAnzahl( 0 ),
    testRend( 1 ),
    fill( 1 ),
    rend( 0 )
{}

// Destruktor 
Bildschirm::~Bildschirm()
{
    lock();
#ifdef WIN32
    if( fenster )
        fenster->release();
#endif
    members->release();
    tipAnzahl = 0;
    tips->release();
    renderZeit->release();
    if( onTop )
        onTop->release();
    if( api )
        api->release();
    unlock();
}

// nicht konstant 
void Bildschirm::lock()
{
    cs.lock();
}

void Bildschirm::unlock()
{
    cs.unlock();
}

void Bildschirm::setFill( bool f )
{
    fill = f;
}

// Aktualisiert die Objekte, mit der die Grafikkarte verwaltet wird
void Bildschirm::update()
{
    lock();
    api->update();
    unlock();
}

void Bildschirm::setTestRend( bool tr ) // legt fest, ob vo rendern auf updates geprüft werden soll
{
    testRend = tr;
}

void Bildschirm::setRenderZeichnungen( bool rO ) // legt fest, ob die Zeichnunge gerendert werden
{
    lock();
    renderZeichnungen = rO;
    rend = 1;
    unlock();
}

void Bildschirm::setOnTop( bool onTop ) // legt fest, ob das onTop Zeichnung gerendert wid
{
    renderOnTop = onTop;
    rend = 1;
}

void Bildschirm::setOnTopZeichnung( Zeichnung *obj ) // setzt das OnTop Zeichnung
{
    lock();
    if( onTop )
        onTop->release();
    onTop = obj;
    rend = 1;
    unlock();
}

void Bildschirm::setdeckFarbe( int f ) // setzt die deckFarbe
{
    deckFarbe = f;
    rend = 1;
}

void Bildschirm::addMember( Zeichnung *obj ) // Fügt ein Zeichnung hinzu
{
    lock();
    members->add( obj );
    rend = 1;
    unlock();
}

void Bildschirm::removeMember( Zeichnung *zObj ) // Entfernt ein Zeichnung
{
    lock();
    for( int i = 0; i < members->getEintragAnzahl(); i++ )
    {
        if( members->z( i ) == zObj )
            members->remove( i );
    }
    rend = 1;
    unlock();
}

// Zeichnet ein Bild und präsentiert es auf dem Bildschirm
void Bildschirm::render()
{
    lock();
    api->update();
    rend = 1;
    unlock();
}

void Bildschirm::setFillFarbe( int f ) // setzt die Fill Farbe
{
    fillColor = f;
    rend = 1;
}

void Bildschirm::setVollbild( bool vollbild ) // setzt vollbild
{
    lock();
    api->setFullScreen( vollbild );
    unlock();
}

void Bildschirm::tick( double tickval )
{
    lock();
    if( !renderOnTop )
    {
        for( int i = 0; i < tipAnzahl; ++i )
            rend |= tips->z( i )->tick( tickval );
        for( Zeichnung *i : *members )
            rend |= i->tick( tickval );
    }
    else if( onTop )
    {
        rend |= onTop->tick( tickval );
        for( int i = 0; i < tipAnzahl; ++i )
            rend |= tips->z( i )->tick( tickval );
    }
    unlock();
}

void Bildschirm::setBackBufferSize( int breite, int height ) // setzt die Größe des Backbuffers
{
    lock();
    api->setBackBufferSize( Vec2< int >( breite, height ) );
    unlock();
}

void Bildschirm::setBackBufferSize( Punkt &size )
{
    lock();
    api->setBackBufferSize( size );
    unlock();
}

void Bildschirm::doMausEreignis( MausEreignis &me ) // sendet maus Ereignis
{
    int fBr = api->getBackBufferSize().x;
    int fHi = api->getBackBufferSize().y;
#ifdef WIN32
    if( fenster )
    {
        fBr = fenster->getKörperBreite();
        fHi = fenster->getKörperHöhe();
    }
#endif
    me.mx = (int)( me.mx * api->getBackBufferSize().x / (double)fBr + 0.5 );
    me.my = (int)( me.my * api->getBackBufferSize().y / (double)fHi + 0.5 );
    lock();
    if( !renderOnTop )
    {
        for( int i = tipAnzahl - 1; i >= 0; i-- )
            tips->z( i )->doPublicMausEreignis( me );
        for( int i = members->getEintragAnzahl() - 1; i >= 0; i-- )
            members->z( i )->doPublicMausEreignis( me );
    }
    else if( onTop )
    {
        onTop->doPublicMausEreignis( me );
        for( int i = tipAnzahl - 1; i >= 0; i-- )
            tips->z( i )->doPublicMausEreignis( me );
    }
    unlock();
}

void Bildschirm::doTastaturEreignis( TastaturEreignis &te ) // sendet tastatur Ereignis
{
    lock();
    if( !renderOnTop )
    {
        for( int i = members->getEintragAnzahl() - 1; i >= 0; i-- )
            members->z( i )->doTastaturEreignis( te );
    }
    for( int i = tipAnzahl - 1; i >= 0; i-- )
        tips->z( i )->doTastaturEreignis( te );
    unlock();
}

void Bildschirm::addToolTip( ToolTip *tip ) // fügt ToolTip hinzu
{
    lock();
    tips->add( tip, tipAnzahl );
    ++tipAnzahl;
    rend = 1;
    unlock();
}

bool Bildschirm::removeToolTip( ToolTip *zTip ) // entfernt ToolTip
{
    lock();
    bool gefunden = 0;
    for( int i = 0; i < tipAnzahl; ++i )
    {
        ToolTip *tmp = tips->z( i );
        if( tmp == zTip )
        {
            tips->remove( i );
            --tipAnzahl;
            gefunden = 1;
            rend = 1;
            break;
        }
    }
    unlock();
    return gefunden;
}

// constant 
Bild *Bildschirm::getRenderBild() const
{
    return dynamic_cast<Bild *>( api->zUIRenderBild()->getThis() );
}

Bild *Bildschirm::zRenderBild() const
{
    return api->zUIRenderBild();
}

Iterator<Zeichnung *> Bildschirm::getMembers() const // gibt die Zeichnunge zurück
{
    return members->begin();
}

int Bildschirm::getFillFarbe() const // gibt die Füll Farbe zurück
{
    return fillColor;
}

bool Bildschirm::istVolbild() const // gibt zurück, ob vollbild an ist
{
    return api->isFullScreen();
}

const Punkt Bildschirm::getBackBufferSize() const // gibt die Größe des Backbuffers zurück
{
    return api->getBackBufferSize();
}

void Bildschirm::warteAufRendern() const // wartet auf die render Funktion
{
    while( rendering )
    {
        if( !rendering )
            return;
    }
}

double Bildschirm::getRenderZeit() const // gibt zurück wie viele Sekunden das Rendern dauert
{
    return renderZeit->getSekunden();
}

// Gibt die Grafik API zurück (ohne erhöhten Reference Counter)
GraphicsApi *Bildschirm::zGraphicsApi() const
{
    return api;
}

// Gibt die Grafik API zurück
GraphicsApi *Bildschirm::getGraphicsApi() const
{
    return api ? dynamic_cast<GraphicsApi *>( api->getThis() ) : 0;
}

#ifdef WIN32
int MonitorEnum( HMONITOR m, HDC dc, LPRECT r, LPARAM p )
{
    Monitor *mon = new Monitor();
    mon->existiert = 1;
    mon->x = r->left;
    mon->y = r->top;
    mon->breite = r->right - r->left;
    mon->height = r->bottom - r->top;
    ( ( Array< Monitor * > * )p )->add( mon );
    return 1;
}

Monitor Framework::getMonitor( int id )
{
    if( id < 0 )
    {
        Monitor m;
        m.existiert = 0;
        return m;
    }
    Array< Monitor * > *monitore = new Array< Monitor * >();
    EnumDisplayMonitors( 0, 0, (MONITORENUMPROC)MonitorEnum, (LPARAM)monitore );
    int anz = monitore->getEintragAnzahl();
    if( id >= monitore->getEintragAnzahl() )
    {
        for( int i = 0; i < anz; ++i )
            delete monitore->get( i );
        delete monitore;
        Monitor m;
        m.existiert = 0;
        return m;
    }
    Monitor m = *monitore->get( id );
    for( int i = 0; i < anz; ++i )
        delete monitore->get( i );
    delete monitore;
    return m;
}
// Bildschirm2D
// Konstruktor 
Bildschirm2D::Bildschirm2D( WFenster *fenster )
    : Bildschirm( fenster )
{
    api = new DirectX9();
    api->initialize( dynamic_cast<WFenster *>( fenster->getThis() ), fenster->getKörperGröße(), 0 );
}

// Destruktor 
Bildschirm2D::~Bildschirm2D()
{}

void Bildschirm2D::render() // Zeichnet das Bild
{
    if( !rend && testRend )
        return;
    rendering = 1;
    Bild *ui = api->zUIRenderBild();
    if( ui )
    {
        lock();
        renderZeit->messungStart();
        api->beginFrame( fill && ( rend || !testRend ), 0, fillColor );
        if( renderZeichnungen )
        {
            if( renderOnTop && deckFarbe && ( deckFarbe & ( fillColor | 0xFF000000 ) ) == deckFarbe )
            {
                ui->setAlpha( 255 - (unsigned char)( deckFarbe >> 24 ) );
                for( Zeichnung *z : *members )
                    z->render( *ui ); // zeichnen nach zwischenbuffer
                ui->releaseAlpha();
            }
            else
            {
                for( Zeichnung *z : *members )
                    z->render( *ui ); // zeichnen nach zwischenbuffer
                if( renderOnTop && deckFarbe )
                    ui->alphaRegion( 0, 0, ui->getBreite(), ui->getHeight(), deckFarbe );
            }
            for( int i = 0; i < tipAnzahl; ++i )
                tips->z( i )->render( *ui );
        }
        if( renderOnTop && onTop )
            onTop->render( *ui );
        api->presentFrame();
        renderZeit->messungEnde();
        unlock();
    }
    rendering = 0;
    rend = 0;
}

// Bildschirm3D
// Konstruktor 
Bildschirm3D::Bildschirm3D( WFenster *fenster )
    : Bildschirm( fenster ),
    kameras( new RCArray< Kam3D >() ),
    rend3D( 0 )
{
    if( DirectX12::isAvailable() )
        api = new DirectX12();
    else if( DirectX11::isAvailable() )
        api = new DirectX11();
    else
        api = new DirectX9();
    api->initialize( dynamic_cast<WFenster *>( fenster->getThis() ), fenster->getKörperGröße(), 0 );
}

Bildschirm3D::Bildschirm3D( WFenster *fenster, GraphicApiType apiTyp )
    : Bildschirm( fenster ),
    kameras( new RCArray< Kam3D >() ),
    rend3D( 0 )
{
    if( apiTyp == DIRECTX9 )
        api = new DirectX9();
    if( apiTyp == DIRECTX11 )
        api = new DirectX11();
    if( apiTyp == DIRECTX12 )
        api = new DirectX12();
    api->initialize( dynamic_cast<WFenster *>( fenster->getThis() ), fenster->getKörperGröße(), 0 );
}

// Destruktor 
Bildschirm3D::~Bildschirm3D()
{
    kameras->release();
}

// nicht constant 
void Bildschirm3D::addKamera( Kam3D *obj ) // Fügt ein Zeichnung hinzu
{
    lock();
    kameras->add( obj );
    rend3D = 1;
    unlock();
}

void Bildschirm3D::removeKamera( Kam3D *zObj ) // Entfernt ein Zeichnung
{
    lock();
    for( int i = 0; kameras->z( i ); i++ )
    {
        if( kameras->z( i ) == zObj )
        {
            kameras->remove( i );
            break;
        }
    }
    rend3D = 1;
    unlock();
}

void Bildschirm3D::tick( double tickval )
{
    lock();
    __super::tick( tickval );
    for( Kam3D *k : *kameras )
        rend3D |= k->tick( tickval );
    unlock();
}

void Bildschirm3D::doMausEreignis( MausEreignis &me ) // sendet maus Ereignis
{
    lock();
    __super::doMausEreignis( me );
    for( int i = kameras->getEintragAnzahl() - 1; i >= 0; i-- )
        kameras->z( i )->doMausEreignis( me );
    unlock();
}

void Bildschirm3D::doTastaturEreignis( TastaturEreignis &te ) // sendet tastatur Ereignis
{
    lock();
    __super::doTastaturEreignis( te );
    for( int i = kameras->getEintragAnzahl() - 1; i >= 0; i-- )
        kameras->z( i )->doTastaturEreignis( te );
    unlock();
}

void Bildschirm3D::render() // Zeichnet das Bild
{
    if( !rend && !rend3D && testRend )
        return;
    rendering = 1;
    lock();
    renderZeit->messungStart();
    // Clear the back buffer.
    api->beginFrame( fill && ( rend || !testRend ), fill && ( rend3D || !testRend || rend ), fillColor );
    if( rend3D || !testRend || rend )
    {
        // Render 3d Objects
        for( Kam3D *k : *kameras )
            api->renderKamera( k );
        rend3D = 0;
    }
    // render User Interface
    Bild *ui = api->zUIRenderBild();
    if( ( rend || !testRend ) && ui )
    {
        if( renderZeichnungen )
        {
            if( renderOnTop && deckFarbe && ( deckFarbe & ( fillColor | 0xFF000000 ) ) == deckFarbe )
            {
                ui->setAlpha( 255 - (unsigned char)( deckFarbe >> 24 ) );
                for( Zeichnung *z : *members )
                    z->render( *ui ); // zeichnen nach zwischenbuffer
                ui->releaseAlpha();
            }
            else
            {
                for( Zeichnung *z : *members )
                    z->render( *ui ); // zeichnen nach zwischenbuffer
                if( renderOnTop && deckFarbe )
                    ui->alphaRegion( 0, 0, ui->getBreite(), ui->getHeight(), deckFarbe );
            }
            for( int i = 0; i < tipAnzahl; ++i )
                tips->z( i )->render( *ui );
        }
        if( renderOnTop && onTop )
            onTop->render( *ui );
    }
    api->presentFrame();
    unlock();
    renderZeit->messungEnde();
#ifdef _DEBUG
    //std::cout << renderZeit->getSekunden() << "\n";
#endif
    rendering = 0;
    rend = 0;
}
#endif