/*
 Copyright (C) 2002 Andreas Thiede ( a.thiede@berlin.de )

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU 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 General Public License for more details.

 You should have received a copy of the GNU 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
*/

#include <IO/Output/OutputWindow.hpp>
#include <IO/Manager/IOManager.hpp>
#include <IO/Manager/IOBuffer.hpp>
#include <IO/Manager/IOStream.hpp>
#include <IO/Manager/IOModel.hpp>
#include <IO/Manager/IOView.hpp>
#include <toolkit/GLWindow.hpp>
#include <toolkit/WindowController.hpp>
#include <skins/SkinBase.hpp>

#import <lib/Tuner.h>
#import <lib/Preferences.h>

#import <unistd.h>
#import <time.h>
#import <sys/time.h>

extern "C" void ResetFrames();

static UInt64 millitime()
{
    struct timeval now;
        
    gettimeofday( &now, 0 );
    
    return (UInt64) now.tv_sec * 1000 + (UInt64) now.tv_usec / 1000;
}

class MyGLWindow : public GLWindow, public IOView {

    typedef GLWindow super;

 public:

    UInt64               mStartTime;
    int                  mMovieWidth, mMovieHeight;
    int                  mWindowWidth, mWindowHeight;
    float                mDesiredFPS;
    int                  mFrameCount;
    int                  mDivider;
    IOModel              mTVType;
    IOModel              mChannel;
    IOModel              mMovieFile;
    GLWindow::String   * mChannelInfo;
    GLWindow::String   * mFrameRate;
    GLWindow::String   * mFileInfo;
    
    OutputWindow       * mOwner;
    CWindowController  * mController;
    bool                 mFullScreen;
    int                  mX, mY;
    SkinBase           * mCurrentSkin;
    
    MyGLWindow( OutputWindow * owner, int x, int y, int movieWidth, int movieHeight, int windowWidth, int windowHeight )
        : mTVType     ( "Hardware.TVType"     )
        , mChannel    ( "Tuner.CurrentChannel")
        , mMovieFile  ( "Player.CurrentFile"  )
        , mFullScreen ( false )
        , mCurrentSkin( 0 )  {
        
        mMovieWidth   = movieWidth;
        mMovieHeight  = movieHeight;
        mWindowWidth  = windowWidth;
        mWindowHeight = windowHeight;
        mOwner        = owner;
        mFrameCount   = 0;
        mDivider      = 1;
        mX            = x;
        mY            = y;
        
        mController = (CWindowController*) IOManager::instance().openNode( "Window Controller");

        assert( mController != 0 );

        mTVType.addView( this );                
        mChannel.addView( this );                
        mMovieFile.addView( this );                

        mChannelInfo = new GLWindow::String( 10, 10, mChannel.getString() );
        
        getStringList().push_back( mChannelInfo );

        mFrameRate =  new GLWindow::String( 50, 10, "" );

        getStringList().push_back( mFrameRate );

        mFileInfo =  new GLWindow::String( 90, 10, mMovieFile.getString() );

        getStringList().push_back( mFileInfo );
        
        mChannelInfo->setVisible( false );
        
        mFileInfo->setVisible( false );
    }
        
    virtual ~MyGLWindow() {
    }
    
    void raise() {
    
        super::raise();        
    }
    
    void move( int x, int y ) {

        int dx = x - bounds().x();
        int dy = y - bounds().y();
        
        super::move( x, y );        
        
        //update();
    }

    void setSkin( const string & skinName ) {
        
        IOObject * obj = IOManager::instance().findObject( skinName );
        
        if( obj != 0 ) {
            
            printf( "found skin %s\n", skinName.c_str() );
            
            if( mCurrentSkin != 0 ) {
                mCurrentSkin->closeSkinForWindow( this );
            }

            mCurrentSkin = (SkinBase*) obj;

            mCurrentSkin->retain();
            
            CRect r = bounds();
            
            mCurrentSkin->createSkinForWindow( this );
        }
    }
    
    void handleDrop( unsigned theType, void * theData, int dataSize, int x, int y ) {

        puts("MyGLWindow::handleDrop");
            
        mController->handleDrop( mOwner->getName(), theType, theData, dataSize, x, y );
    }
    
    void keyDown( const string & key, char keyCode, unsigned modifiers ) {

        //printf("MyGLWindow::keyDown key(%s) code(%x) modifiers(%x)\n", key.c_str(), keyCode, modifiers );

        super::keyDown( key, keyCode, modifiers );
                            
        // not handled, send to controller
        
        mController->keyDown( mOwner->getName(), key, keyCode, modifiers );
    }
    
    void toggleFullScreen() {
    
        int x,y,w,h;
        
        if( ! mFullScreen ) {
        
            CRect screen( 0, 0, 1280, 1024 );

            x = 0;
            y = 0;
            w = screen.w();
            h = screen.h();

            mFullScreen = true;

            move( x, y );
            resize( w, h );
        
        }
        else {

            x = GetPrivateProfileInt( mOwner->getName().c_str(), "OriginX", x );
            y = GetPrivateProfileInt( mOwner->getName().c_str(), "OriginY", y );

            w = GetPrivateProfileInt( mOwner->getName().c_str(), "SizeX", w );
            h = GetPrivateProfileInt( mOwner->getName().c_str(), "SizeY", h );

            mFullScreen = false;


            resize( w, h );
            move( x, y );
        }
                
        super::sizeChanged( w, h );
    }
    
    void update() {
    
        if( mCurrentSkin ) {
            mCurrentSkin->update();
        }
        super::update();
    }
    
    void updateFrom( IOModel * aModel ) {

        printf("%s.updateFrom: %s\n", mOwner->getName().c_str(), aModel->getName().c_str() );
        
        if( aModel->getName() == "Hardware.TVType" ) {

            int val = mTVType.getInt();
        
            int w,h;
        
            switch( val ) {
            case FORMAT_PAL_BDGHI:
            case FORMAT_SECAM:
            case FORMAT_PAL_N:
                w = 360;
                h = 288;
                break;
            case FORMAT_NTSC:

            case FORMAT_PAL_M:
            case FORMAT_PAL60:
            case FORMAT_NTSC_J:
                w = 360;
                h = 240;
                break;
            }
    
            if( w != mMovieWidth || h != mMovieHeight ) {
                mMovieWidth  = w;
                mMovieHeight = h;
            
                mOwner->pause();
                mOwner->resume();
            }
        
        }
        else if( aModel->getName() == "Tuner.CurrentChannel" ) {
        
            mChannelInfo->setString( aModel->getString() );
        }
        else if( aModel->getName() == "Player.CurrentFile" ) {
        
            mFileInfo->setString( aModel->getString() );
        }
    }
    
    void sizeChanged( int width, int height ) {

        super::sizeChanged( width, height );
        
        if( ! mFullScreen ) {
            WritePrivateProfileInt( mOwner->getName().c_str(), "SizeX", width  );
            WritePrivateProfileInt( mOwner->getName().c_str(), "SizeY", height );
        }
        
        update();

    }
    void posChanged ( int x, int y ) {

        if( ! mFullScreen ) {
            WritePrivateProfileInt( mOwner->getName().c_str(), "OriginX", x );
            WritePrivateProfileInt( mOwner->getName().c_str(), "OriginY", y );
        }
    }
};

OutputWindow::OutputWindow( const string & aName, IODeviceID anID )
 : IOOutput( aName, anID ), mWorkThread( aName )
{
    init();
}

OutputWindow::~OutputWindow()
{    
    finit();
}


void * windowThreadEntry( void * _this )
{
    ((OutputWindow*) _this )->run();
    
    return 0;
}

#if 0
static void combine( unsigned char * dst, const unsigned char * src, int w, int h , int bpp )
{
    register int bpl = w * bpp;
    register const unsigned char * src1 = src;
    register const unsigned char * src2 = src + h/2 * bpl;
    
    for( int y = 0; y < h; y+=2 ) {
        
        memcpy( dst, src1, bpl );
        
        dst += bpl;

        memcpy( dst, src2, bpl );

        dst += bpl;

        src1 += bpl;
        src2 += bpl;
    }
}

static void hdec( UInt32 * dst, const UInt32 * src, int w, int h )
{    
    for( register int y = 0; y < h; y++ ) {
        
        for( register int x = 0; x < w/2; x++ ) {

            * dst++ = *src++;
            src++;
        }
    }
}

#endif

void OutputWindow::run() 
{        
    int val = GetPrivateProfileInt( "Hardware", "TVType" , 0 );
        
    int x = 50, y = 50, w,h;
        
    switch( val ) {
    case FORMAT_PAL_BDGHI:
    case FORMAT_SECAM:
    case FORMAT_PAL_N:
        w = 360;
        h = 288;
        break;
    case FORMAT_NTSC:

    case FORMAT_PAL_M:
    case FORMAT_PAL60:
    case FORMAT_NTSC_J:
        w = 360;
        h = 240;
        break;
    }
    
    int wx = GetPrivateProfileInt( getName().c_str(), "OriginX", x );
    int wy = GetPrivateProfileInt( getName().c_str(), "OriginY", y );

    int ww = GetPrivateProfileInt( getName().c_str(), "SizeX", w );
    int wh = GetPrivateProfileInt( getName().c_str(), "SizeY", h );
    
    if( mWindow == 0 ) {
        
        mWindow = new MyGLWindow( this, wx, wy, w, h, ww, wh );
        
    }

    if( mWindow ) {
    
        mWindow->SetupGL( 0, wx, wy, w, h, ww, wh );
        
        mWindow->resize( ww + 1, wh + 1 );
        mWindow->resize( ww, wh );
    }
    else  {
        puts("failed to create window");
        return;
    }
 
    IOThread::Request request;

    mWindow->mFrameCount = 0;
    mWindow->mStartTime  = millitime();

    char fps[64];

    val = GetPrivateProfileInt( "Window", "StayOnTop" , 0 );
         
    if( val != 0 ) {

        mWindow->setLevel( CWindow::StayOnTop );    
    }
    
    do {
    
        request = mWorkThread.serviceCancellation();
        
        switch( request ) {

        case IOThread::ioStop:
            break;
            
        case IOThread::ioPause:
           
            mWindow->mFrameCount = 0;
            mWindow->mStartTime  = millitime();
             
            mWindow->mWindowWidth  = mWindow->bounds().w();
            mWindow->mWindowHeight = mWindow->bounds().h();
            
            mWindow->SetupGL( 0, wx, wy, 
                                mWindow->mMovieWidth, mWindow->mMovieHeight,
                                mWindow->mWindowWidth, mWindow->mWindowHeight );
            
            for( int i = 0; i < 2; i++ ) {

                IOBuffer * b = getBuffer(0);
                
                if( b ) {
                    b->release();
                }
            };
            
            mWindow->mFrameCount = 0;
            
            break;
        
        default:
        
            {
                IOBuffer * buffer = getBuffer( 40 );

                if( buffer ) {
  
                    if( mWindow->mFrameCount % mWindow->mDivider == 0 ) {

                        mWindow->lockFocus();
                        
                        UInt64 diff = millitime() - mWindow->mStartTime;
                        
                        if( diff > 0 ) {
                            
                            sprintf( fps, "%02.2f", (float) ( mWindow->mFrameCount * 1000 ) / (float) diff );
                            
                            mWindow->mFrameRate->setString( fps );
                        }
                        
                        
                        //printf( "draw %s\n", buffer->getName().c_str() );
                        //fflush( stdout );
                    
                        mWindow->Draw( (unsigned char *) buffer->getData() );
            
                        mWindow->unlockFocus();

                    }
                    mWindow->mFrameCount++;
                    
                    buffer->release();
                                        
                }
                else {
                    ; //puts( "no buffer");
                }
            }
            break;
        }
        
    } while( request !=  IOThread::ioStop );
}

bool OutputWindow::pause() 
{
    return mWorkThread.pause();
}

bool OutputWindow::resume() 
{
    return mWorkThread.resume();
}

bool OutputWindow::start() 
{ 
    if( mInited && ! mStarted && isAttached()) {

        mWorkThread.start(  windowThreadEntry, this );
        
        mStarted = true;
    }

    return mStarted;
}

bool OutputWindow::stop() 
{ 
    mStarted = false;

    mWorkThread.stop();
    
    return mStarted;
}

size_t OutputWindow::frameSize()
{
    return 8192;
}

void OutputWindow::init()
{
    mWindow = 0;
    mInited = true;    
}

void OutputWindow::finit()
{
    IOManager::instance().unregisterNode ( this );
}

bool OutputWindow::updateFrom( IOBuffer * ioBuf )
{
    // printf("OutputWindow::updateFrom %s backlog %d\n", ioBuf->getName().c_str(), bufferCount() );
    //fflush( stdout);
    
    if( bufferCount() > 2 )  {
        //printf("OutputWindow::updateFrom %s backlog %d\n", ioBuf->getName().c_str(), bufferCount() );
        //fflush( stdout);
        return false;
    }
    return IOOutput::updateFrom( ioBuf );
}

bool OutputWindow::command( const string & command, 
                            const string & param1,  const string & param2, const string & param3, const string & param4 )
{
    printf("%s.command: %s %s %s\n", getName().c_str(), command.c_str(), param1.c_str(), param2.c_str() );

    if( command == "setWindowSize" ) {

        mWindow->mWindowWidth  = atoi( param1.c_str() );
        mWindow->mWindowHeight = atoi( param2.c_str() );

        if( mWorkThread.isRunning() ) {
            pause();
            resume();
        }
    }
    else if( command == "setMovieSize" ) {

        mWindow->mMovieWidth  = atoi( param1.c_str() );
        mWindow->mMovieHeight = atoi( param2.c_str() );

        if( mWorkThread.isRunning() ) {
            pause();
            resume();
        }
    }
    else if( command == "setFrameRateDivider" ) {

        int div  = atoi( param1.c_str() );

        if( div > 0 ) 
            printf("setFrameRateDivider(%d)\n", div );
            
            mWindow->mDivider = div;
            ResetFrames();
    }
    else if( command == "toggleInfo" ) {
        mWindow->toggleInfo();
    }
    else if( command == "toggleFullScreen" ) {
        mWindow->toggleFullScreen();
    }
    else if( command == "showChannelInfo" ) {
        mWindow->mChannelInfo->setVisible( atoi( param1.c_str()  ) );
    }
    else if( command == "showFileInfo" ) {

        mWindow->mFileInfo->setVisible( atoi( param1.c_str()  ) );
    }
    else if( command == "setSkin" ) {

        mWindow->setSkin( param1 );
    }
    
    return true;
}

extern "C" int initGLWindowPlugin( int argc, const char ** argv )
{
    OutputWindow * ow = new OutputWindow( "MovieWindow", 0 );
    
    IOManager::instance().registerNode (ow );
    
    return true;
}
    
