/*
 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/Manager/IOManager.hpp>
#include <IO/Manager/IOStream.hpp>
#include <IO/Manager/IOModel.hpp>
#include <IO/Input/IOAudioInput.hpp>
#include <IO/Input/BtAudioInput.hpp>
#include <IO/Input/IOVideoInput.hpp>
#include <IO/Input/TeleTextInput.hpp>
#include <IO/Output/IOAudioOutput.hpp>
#include <IO/Output/OutputWindow.hpp>
#include <IO/Output/TeleTextOutput.hpp>

#include <lib/tvcards.h>
#include <lib/Preferences.h>
#include <util/file.h>
#include <util/Loader.h>

#include <AppKit/AppKit.h>

#include <IO/Output/QTRecorder.hpp>
#include <IO/Input/QTInput.hpp>

#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/errno.h>

extern "C" OSErr FSSpecToNativePathName( FSSpecPtr theFile, char * pathname, UInt16 pathnameMaxBufferSize, long flags );

typedef enum Mode { eNone, eTV, ePlay, eTranscode };

IOStream * audioStream = 0;
IOStream * videoStream = 0;
IOStream * teleTextStream = 0;
    
IOAudioOutput * audioOutput = 0;
IOAudioInput  * audioInput  = 0;

IOVideoInput   * videoInput = 0;
TeleTextInput  * teleTextInput = 0;
TeleTextOutput * teleTextOutput = 0;
OutputWindow   * outputWindow = 0;
QTRecorder     * recorder = 0;
QTInput        * player = 0;

int inputVolumeLeft = 50, inputVolumeRight = 50;

static QTRecorder * getRecorder()
{
    if( recorder == 0 ) {
    
        recorder = (QTRecorder*) IOManager::instance().openNode( "QTRecorder.QTRecorder" );

        if( recorder ) {
    
            IOManager::instance().setAlias("AVRecorder", recorder->getName() );
        }
    }
    return recorder;
}

static QTInput * getPlayer()
{
    if( player == 0 ) {
    
        player =  (QTInput *) IOManager::instance().openNode( "QTPlayer.QTPlayer" );
    
        if( player != 0 ) {
    
            IOManager::instance().setAlias("AVPlayer", player->getName() );
        }
    }
    return player;
}

static bool playerLoaded()
{
    return ( player != 0 );
}

static bool videoLoaded()
{
    return ( videoInput != 0 );
}

static bool recorderLoaded()
{
    return ( recorder != 0 );
}

static IOVideoInput * getVideoInput()
{
    if( videoInput == 0 ) {
    
        videoInput = (IOVideoInput  *) IOManager::instance().openNode("Bt8xx.Bt8xx Video", -1 );

        if( videoInput != 0 ) {
    
            IOManager::instance().setAlias("Tuner", videoInput->getName() );
        }
    }
    return videoInput;
}

@interface XTelevision : NSObject
{
    Mode             mCurrentMode;
    int              mCurrentFileOfList;
    int              mCurrentChunk;
    NSString       * mPlugInLocation;
    NSString       * mCurrentMovie;
    int              mMixerPID;
}

- (void) loadDriver;
- (void) updateAudioDefaults;
- (void) loadSkin;
- (void) finitStreams;
- (void) initStreams;
- (void) launchPreferences;
- (void) startMixer;
- (void) stopMixer;
- (void) showFatalError:(const char *) windowTitle message:(const char *) message  errcode:(int) errcode;
- (void) freeNode:(IONode *) node;
- (void) closeNode:(IONode *) node;
- (NSString*) getRecordingFileName;
- (void) beforeExit;
- (void) beforeRunning;
- (void) idle;
- (void) stopPlaying;
- (void) startPlaying;
- (void) handleTVKeyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers;
- (void) handlePlayerKeyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers; 
- (void) handleMovieKeyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers;
- (void) keyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers;
- (void) updateFrom:( const char *) aModel;
- (void) onVolumeChange:(float) vol bal:(float) bal;
- (void) buttonPressed:(const char*) action;
- (NSString*) currentMovie;
- (void) handleDrop:(const char *) wName fileName:(const char *) fileName;
- (void) handleWheel:(const char *) wName dx:(int) dx dy:(int) dy dz:(int) dz;
- (void) toggleRecord;
- (void) playMovie:(NSString*) fileName;
- (void) togglePlayMovieList;
- (void) stopRecording;
- (void) startRecording;
- (void) setCurrent:(NSString*) newOne;
- (void) onQuit;
- (void) onPreferences;
@end

static string i2str( int i )
{
    char buf[64];
    
    sprintf( buf, "%d", i );
    
    return buf;
}

@implementation XTelevision

- (void) launchPreferences
{
    string cmd = "open  ";
    cmd += [ mPlugInLocation cString ];
    cmd += "/Preferences.app";
    
    system( cmd.c_str() );
    
}

- (void) startMixer
{
    [ self stopMixer ];
    
    if( mMixerPID == -1 ) {
        
        char prog[512];

        strcpy( prog,  [[NSString stringWithFormat:@"%@/Mixer", mPlugInLocation] cString ] );
        
        printf("%d: launching %s\n", getpid(), prog );
    
        int pid = fork();

        printf("pid is now %d\n", pid );
    
        switch( pid ) {
        case 0:
    
            // child goes here
            {
                int rc;
#if 0                
                int fd = open( "/tmp/mixer.log", O_RDWR|O_CREAT, 0666 );
                
                close(0);
                close(1);
                dup(fd);
                dup(fd);
                
                printf("%d: launching %s\n", getpid(), prog );
#endif
                rc  = execl(  prog,  prog, 0 );
            
                printf("exec error rc(%d) errno(%d)\n", rc , errno );
                
                exit(0);
            }
            break;
        case -1:
            printf("fork error cause %d\n", errno );
            break;
        default:
    
            mMixerPID = pid;
            break;
        }
    }
}

- (void) stopMixer
{
    puts( "stop mixer" );
    
    if( mMixerPID != -1 ) {
    
        int rc = kill( mMixerPID, SIGTERM );

        usleep( 50 * 1000 );
    
        rc = kill( mMixerPID, 0 );

        if( rc == 0 ) {
            kill( mMixerPID, SIGKILL );
        }
    
        int status;
    
        waitpid( mMixerPID, &status, 0 );
    
        mMixerPID = -1;
    }
}

- (void) showFatalError:(const char *) windowTitle message:(const char *) message  errcode:(int) errcode
{
    char buf[1024];
    
    sprintf( buf, "%s - error code %d", message, errcode );
    
    NSString * title  = [ NSString stringWithCString: windowTitle ];
    NSString * msg    = [ NSString stringWithCString: buf         ];
    NSString * button = [ NSString stringWithCString: "ok"        ];

    NSRunCriticalAlertPanelRelativeToWindow( title, msg, button, nil, nil, nil, nil, 0 );
}

- (void) onPreferences
{
    [ self launchPreferences ];
}

- (void) onQuit
{
    IOManager::instance().sendCommand( "IOManager", "quit" );
}

- (void) closeNode:(IONode *) node
{
    if( node != 0 ) {
    
        node->detachFromStream();
        
        IOManager::instance().closeNode( node );        
    }
}

- (void) freeNode:(IONode *) node 
{
    if( node != 0 ) {
    
        [ self closeNode: node ];
        
        IOManager::instance().unregisterNode ( node );

        node->release();
    }
}

- (id) init
{
    [ super init ];
            
    mPlugInLocation = [ [NSBundle mainBundle] builtInPlugInsPath ];

    [ mPlugInLocation retain ];
        
    mCurrentMode       = eNone;
    mCurrentFileOfList = -1;
    mCurrentChunk      = 0;
    
    mCurrentMovie      = nil;
    mMixerPID          = -1;
    
    [ self setCurrent: @"noname" ];
    
    return self;
}

- (void) idle
{
    if( mCurrentMode == ePlay ) {
    }
}
        
- (BOOL) command:(NSString*) command  param:(NSString*) param
{
    return true;
}

- (NSString*) currentMovie
{
    printf( "mCurrentMovie = %p\n", mCurrentMovie );
    
    return mCurrentMovie;
}

- (void) togglePlayMovieList
{
    puts( "togglePlayMovieList" );
    
    IOManager::instance().sendCommand( "PlayListWindow", "playCurrentFile" );                
}

- (void) setCurrent:(NSString*) newOne
{
    if( mCurrentMovie ) {

        [ mCurrentMovie release ];
    }

    mCurrentMovie = newOne;
    
    if( mCurrentMovie ) {
        [ mCurrentMovie retain ];
    }
}

- (void) playMovie:(NSString*) fileName
{
    [ self setCurrent:fileName ];

    [ self stopPlaying  ];
    [ self startPlaying ];
}

- (void) toggleRecord
{
    if( getRecorder() ) {
                
        if( getRecorder()->isRecording() ) {
            [ self stopRecording ];
        }
        else {
            [ self startRecording ];
        }
    }
}

- (void) handleWheel:(const char *) wName dx:(int) dx dy:(int) dy dz:(int) dz
{
#if 0
    if( dy < 0 ) {
        IOManager::instance().sendCommand( "Tuner", "changeChannel", "-1");
    }
    else if( dy > 0 ) {
        IOManager::instance().sendCommand( "Tuner", "changeChannel", "+1");    
    }
#else
    if( dy < 0 ) {
        IOManager::instance().sendCommand( "ProgramList", "previous" );
    }
    else if( dy > 0 ) {
        IOManager::instance().sendCommand( "ProgramList", "next" );
    }

#endif
}

- (void) handleDrop:(const char *) wName fileName:(const char *) fileName 
{
    IOManager::instance().sendCommand( "PlayListWindow", "add", fileName );
    
    [ self setCurrent:[ NSString stringWithCString: fileName ]];
    
    [ self stopPlaying  ];
    [ self startPlaying ];
}
                
- (NSString*) getRecordingFileName
{
    char prefix[1024];
            
    GetPrivateProfileString( "Recording", "Prefix", "movie", prefix, sizeof(prefix) );

    char dir[1024];
            
    GetPrivateProfileString( "Recording", "Directory", "/tmp", dir, sizeof(dir) );

    char file[1024];
            
    sprintf( file, "%s/%s%d", dir, prefix, ++mCurrentChunk );
            
    return [ NSString stringWithCString:file ];            
}

- (void) stopTV
{
    if( videoLoaded() ) {

        [ self stopMixer ];

        IOManager::instance().sendCommand( "GLWindow", "showChannelInfo", "0" );
            
        if( recorderLoaded() && getRecorder() && getRecorder()->isRecording() ) {
            IOManager::instance().sendCommand( "AVRecorder", "toggle", "" );
        }
                
        if( videoStream != 0 ) {
            videoStream->stopProducers();    
        }
                
        if( audioStream != 0 ) {
            audioStream->stopProducers();    
        }
                
        getVideoInput()->detachFromStream();
                        
        if( audioInput != 0 ) {
            audioInput->detachFromStream();
        }
    }
}
        
- (void) switchToTV
{
    if( getVideoInput() && mCurrentMode != eTV ) {

        string currentProgram = IOManager::instance().getValue( "ProgramList", "current" );

        if( currentProgram != "" && atoi( currentProgram.c_str() ) != -1 ) {
        
            IOManager::instance().sendCommand( "ProgramList", "current" );
        }
        else {
    
            int prevChannel = GetPrivateProfileInt( "Tuner", "CurrentChannel", 0 );
 
            char num[24];
    
            sprintf( num, "%d", prevChannel );

            IOManager::instance().sendCommand( "Tuner", "setChannel", num  );

        }
        
        IOManager::instance().sendCommand( "GLWindow", "showChannelInfo", "1" );

        mCurrentMode = eTV;

        [ self stopPlaying ];

        outputWindow->stop();

        videoStream->stopProducers();    
        audioStream->stopProducers();    
                    
        if( playerLoaded() ) {
            getPlayer()->detachFromStream();
        }

        string frameWidth  = IOManager::instance().getValue( "Tuner", "frameWidth"  );
        string frameHeight = IOManager::instance().getValue( "Tuner", "frameHeight" );
                    
        outputWindow->command( "setMovieSize" , frameWidth, frameHeight );
        outputWindow->command( "setWindowSize", frameWidth, frameHeight );
    
        getVideoInput()->attachTo( videoStream, true );

        if( audioInput ) {
            audioInput->attachTo( audioStream, true );
        }
                    
        if( ! outputWindow->isAttached() ) {
            outputWindow->attachTo( videoStream );
        }
        
        outputWindow->start();
        getVideoInput()->start();

        IOManager::instance().sendCommand( "GLWindow", "showTVControls" );
        
        videoStream->start();
        audioStream->start();

        IOManager::instance().sendCommand( "GLWindow", "show" );

        WritePrivateProfileString( "Application", "Mode", i2str( eTV ).c_str()  );

        [ self startMixer ];
    }
}

- (void) startPlayer
{
    if( ! getPlayer()->isPlaying() ) {

        IOManager::instance().sendCommand( "GLWindow", "showFileInfo", "1" );

        outputWindow->stop();
                                        
        IOManager::instance().sendCommand( "AVPlayer", "setFilename",  [[ self currentMovie  ] cString ], "true" );

        string frameWidth  = IOManager::instance().getValue( "AVPlayer", "frameWidth"  );
        string frameHeight = IOManager::instance().getValue( "AVPlayer", "frameHeight" );
                    
        outputWindow->command( "setMovieSize" , frameWidth, frameHeight );
        outputWindow->command( "setWindowSize", frameWidth, frameHeight );
                    
        IOManager::instance().sendCommand( "AVPlayer", "setVideoStream", videoStream->getName() );
        IOManager::instance().sendCommand( "AVPlayer", "setAudioStream", audioStream->getName() );
        IOManager::instance().sendCommand( "AVPlayer", "toggle", "" );

        IOManager::instance().sendCommand( "GLWindow", "showPlayerControls" );

        if( ! outputWindow->isAttached() ) {
            outputWindow->attachTo( videoStream );
        }

        outputWindow->start();

        videoStream->start();
        audioStream->start();

        IOManager::instance().sendCommand( "GLWindow", "show" );
    }
}
        
- (void) switchToPlayer
{
    if( getPlayer() ) {

        if( mCurrentMode == eTV ) {
            [ self stopTV ];
        }
                
        [ self startPlayer ];
                
        mCurrentMode = ePlay;                

        WritePrivateProfileString( "Application", "Mode", i2str( ePlay ).c_str()  );
    }
}
       
- (void) startPlaying
{
    if( getPlayer() != 0 &&  [ self currentMovie ]  != 0 ) {
        [ self switchToPlayer ];
    }
}

- (void) stopPlaying
{
    IOManager::instance().sendCommand( "GLWindow", "showFileInfo", "0" );            

    if( playerLoaded() && getPlayer() != 0 && mCurrentMode == ePlay && getPlayer()->isPlaying() ) {
        IOManager::instance().sendCommand( "AVPlayer", "toggle", "" );
    }    
}
 
- (void) startRecording
{
    if( getRecorder() ) {

        if( ! getRecorder()->isRecording() ) {

            NSString * fileName = [ self getRecordingFileName ];
            
            [ self setCurrent:fileName ];

            IOManager::instance().sendCommand( "PlayListWindow", "add", [ fileName cString ] );
            
            IOManager::instance().sendCommand( "GLWindow", "setFrameRateDivider", "2" );
    
            IOManager::instance().sendCommand( "AVRecorder", "setFilename",    [ fileName cString ]  );
            IOManager::instance().sendCommand( "AVRecorder", "setVideoStream", "io.stream.video" );
            IOManager::instance().sendCommand( "AVRecorder", "setAudioStream", "io.stream.audio" );
        }
        else {
            IOManager::instance().sendCommand( "GLWindow", "setFrameRateDivider", "1" );
        }
                    
        IOManager::instance().sendCommand( "AVRecorder", "toggle", "" );
    }
}

- (void) stopRecording
{
    if( getRecorder() && getRecorder()->isRecording() ) {
        IOManager::instance().sendCommand( "AVRecorder", "toggle", "" );
        IOManager::instance().sendCommand( "GLWindow", "setFrameRateDivider", "1" );
    }
}
        
- (void) handleTVKeyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers 
{
    printf("handleTVKeyDown window(%s) key(%s) code(%x) modifiers(%x)\n", wName, key, keyCode, modifiers );

    switch( key[0] ){

    case 'V':
                
        if( inputVolumeLeft + 10 <= 100 ) {
            inputVolumeLeft += 10;
        }
        if( inputVolumeRight + 10 <= 100 ) {                
            inputVolumeRight += 10;
        }
                
        IOManager::instance().sendCommand( "LineIn", "setVolume", i2str( inputVolumeLeft), i2str( inputVolumeRight ) );

        break;
                
    case 'v':
                
        if( inputVolumeLeft >= 10 ) {
            inputVolumeLeft -= 10;
        }
        if( inputVolumeRight >= 10 ) {                
            inputVolumeRight -= 10;
        }
                
        IOManager::instance().sendCommand( "LineIn", "setVolume", i2str( inputVolumeLeft), i2str( inputVolumeRight ) );
                
        break;

    case 'r':

        [ self toggleRecord];
                
        break;

    case 'p':

        IOManager::instance().sendCommand( "PlayListWindow", "playCurrentFile" );                
                
        break;

    case '+':

        IOManager::instance().sendCommand( "Tuner", "changeChannel", "+1");

        //setChannelInfo( getChannel() );
        break;

    case '-':

        IOManager::instance().sendCommand( "Tuner", "changeChannel", "-1");

        //setChannelInfo( getChannel() );
        break;

    case '*':
    
        IOManager::instance().sendCommand( "Tuner", "fineTune", "+1");
        break;

    case '/':
        IOManager::instance().sendCommand( "Tuner", "fineTune", "-1");
        break;

    case 'm':
        IOManager::instance().sendCommand( "ProgramList", "store", IOManager::instance().getValue( "Tuner", "channel") );
        break;
            
    }

    int chn = -1;
            
    switch( keyCode ) {
    case 0x7a: chn =  0; break;
    case 0x78: chn =  1; break;
    case 0x63: chn =  2; break;
    case 0x76: chn =  3; break;
    case 0x60: chn =  4; break;
    case 0x61: chn =  5; break;
    case 0x62: chn =  6; break;
    case 0x64: chn =  7; break;
    case 0x65: chn =  8; break;
    case 0x6d: chn =  9; break;
    case 0x67: chn = 10; break;
    case 0x6f: chn = 11; break;
    }
     
    if( chn >= 0 ) {

        if( modifiers & 0x20000 ) { // shift            
            chn += 12;
        }
        else if( modifiers & 0x80000 ) { // alt
            chn += 24;
        }
                    
        char chnStr[255];
                    
        sprintf( chnStr, "%d", chn );
                
        printf("channel(%s)\n", chnStr);
                
        IOManager::instance().sendCommand( "ProgramList", "select", chnStr );
                
    }

    switch( keyCode ) {
    case 0x73: // home

        IOManager::instance().sendCommand( "ProgramList", "first" );
        break;

    case 0x77: // end
            
        IOManager::instance().sendCommand( "ProgramList", "last" );                    
        break;
                    
    case 0x7b: // left

        IOManager::instance().sendCommand( "ProgramList", "previous" );                    
        
        break;
                    
    case 0x7c: // right

        IOManager::instance().sendCommand( "ProgramList", "next" );
        
        break;

    case 0x7e: // up

        [ self handleTVKeyDown:wName  key:"V" keyCode:9 modifiers:0x20000  ];
        
        break;

    case 0x7d: // down

        [ self handleTVKeyDown:wName  key:"v" keyCode:9 modifiers:0  ];
        
        break;
        
    case 0x74: // page up


        break;
                    
    case 0x79: // page down
                
        break;
    }
}

- (void) handlePlayerKeyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers
{
    //printf("handlePlayerKeyDown window(%s) key(%s) code(%x) modifiers(%x)\n", wName.c_str(), key.c_str(), keyCode, modifiers );

    switch( key[0] ){

    case 'x':

        [ self switchToTV ];
                
        break;

    case 'q':

        if( playerLoaded() && getPlayer()->isPlaying() ) {
            [ self stopPlaying ];
        }
        else {
            [ self startPlaying ];
        }
                
        break;

    case 'f':

        if( getPlayer()->isPlaying() ) {

            IOManager::instance().sendCommand( "AVPlayer", "rate", "2.0" );
        }
                
        break;

    case 's':

        if( getPlayer()->isPlaying() ) {
                
            IOManager::instance().sendCommand( "AVPlayer", "rate", "0.5" );
        }
                
        break;

    case ' ':
            
        if( getPlayer()->isPlaying() ) {
                
            IOManager::instance().sendCommand( "AVPlayer", "togglePause" );
        }
        break;
                
    case 'b':
                
        IOManager::instance().sendCommand( "PlayListWindow", "playFirstFile" );                

        break;

    case 'n':
                
        IOManager::instance().sendCommand( "PlayListWindow", "playNextFile" );                
        break;

    case 'p':

        IOManager::instance().sendCommand( "PlayListWindow", "playPreviousFile" );                
                
        break;
    }
    
    switch( keyCode ) {
    case 0x73: // home

        IOManager::instance().sendCommand( "AVPlayer", "goto", "0.0" );
        break;

    case 0x77: // end
            
        IOManager::instance().sendCommand( "AVPlayer", "goto", "1.0" );                    
        break;
                    
    case 0x7b: // left

        if( modifiers & 0x40000 ) {
            IOManager::instance().sendCommand( "AVPlayer", "step", "fastbackward" );
        }
        else {
            IOManager::instance().sendCommand( "AVPlayer", "step", "backward" );                    
        }
        break;
                    
    case 0x7c: // right

        if( modifiers & 0x40000 ) {
            IOManager::instance().sendCommand( "AVPlayer", "step", "fastforward" );
        }
        else {
            IOManager::instance().sendCommand( "AVPlayer", "step", "forward" );                                    
        }
        break;

    case 0x74: // page up


        break;
                    
    case 0x79: // page down
                
        break;
    }
}
                
- (void) handleMovieKeyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers
{
    printf("handleMovieKeyDown window(%s) key(%s) code(%x) modifiers(%x)\n", wName, key, keyCode, modifiers );
        
    // handle general keys first

    switch( key[0] ){

    case 27:
                
        IOManager::instance().sendCommand( "IOManager", "quit" );
                
        break;

    case 'z':

        IOManager::instance().sendCommand( "GLWindow", "toggleFullScreen" );
                
        break;

    case 'i':

        IOManager::instance().sendCommand( "GLWindow", "toggleInfo" );
                
        break;

    case 'h':

        IOManager::instance().sendCommand( "GLWindow", "toggleControls" );
                
        break;

    case 'e':

        [ self launchPreferences ];
                
        break;

    }

    switch( mCurrentMode ) {
            
    case eTranscode:
            
        /* if( transcoder && transcoder->isRunning() ) {
                
            handleTranscoderKeyDown( wName, key, keyCode, modifiers );

        }
        */
        break;
            
    case ePlay:
            
        [ self handlePlayerKeyDown: wName  key:key keyCode:keyCode modifiers:modifiers ];
        break;
                
    case eTV:
            
        [ self handleTVKeyDown:wName key:key keyCode:keyCode modifiers:modifiers ];
                
        break;
    default:
        break;
    }
}
        
- (void) keyDown:(const char *) wName  key:(const char *) key keyCode:(char) keyCode modifiers:(unsigned) modifiers
{
    //printf("Controller::keyDown window(%s) key(%s) code(%x) modifiers(%x)\n", wName.c_str(), key.c_str(), keyCode, modifiers );

    if( ! strcmp(wName, "MovieWindow" )) {
        [ self handleMovieKeyDown:wName key:key keyCode:keyCode modifiers:modifiers ];
    }
}

- (void) buttonPressed:(const char*) action
{
    string cmd = action;

    if( cmd == "lbdrag" ) {
        IOManager::instance().sendCommand( "GLWindow", "lbdrag" );
        return;
    }
    else if( cmd == "quit" || cmd == "exit" ) {
        IOManager::instance().sendCommand( "IOManager", "quit" );
    }
    else if( cmd == "playlist" ) {
        IOManager::instance().sendCommand( "PlayListWindow", "toggleShow" );
    }
    else if( cmd == "zoom" ) {
        IOManager::instance().sendCommand( "GLWindow", "toggleFullScreen" );
    }
    
    switch( mCurrentMode ) {
    case eTV:

        if( cmd == "up" ) {
            IOManager::instance().sendCommand( "ProgramList", "next" );
            //IOManager::instance().sendCommand( "Tuner", "changeChannel", "+1");
        }
        else if( cmd == "down" ) {
            IOManager::instance().sendCommand( "ProgramList", "previous" );
            //IOManager::instance().sendCommand( "Tuner", "changeChannel", "-1");        
        }
        else if( cmd == "record" ) {
                [ self toggleRecord ];
        }
        else if( cmd == "pause" ) {

            int wasPaused = atoi( IOManager::instance().getValue( "Tuner", "isPaused"  ).c_str() );
            
            IOManager::instance().sendCommand( "Tuner", "togglePause" );
            
            if( wasPaused ) {

                audioStream->start();

                [ self startMixer ];
            }
            else {
                
                audioStream->stop();

                [ self stopMixer ];
            }
        }
        else if( cmd == "playmovies" ) {
            [ self togglePlayMovieList ];
        }
        break;

    case ePlay:

        if( cmd == "forward" ) {
            IOManager::instance().sendCommand( "AVPlayer", "step", "forward" );                                    
        }
        else if( cmd == "fastforward" ) {
            IOManager::instance().sendCommand( "AVPlayer", "step", "fastforward" );
        }
        else if( cmd == "fastbackward" ) {
            IOManager::instance().sendCommand( "AVPlayer", "step", "fastbackward" );
        }
        else if( cmd == "backward" ) {
            IOManager::instance().sendCommand( "AVPlayer", "step", "backward" );
        }
        else if( cmd == "stop" ) {
            [ self switchToTV ];
        }
        else if( cmd == "end" ) {
            IOManager::instance().sendCommand( "AVPlayer", "goto", "1.0" );
        }
        else if( cmd == "begin" ) {
            IOManager::instance().sendCommand( "AVPlayer", "goto", "0.0" );        
        }
        else if( cmd == "pause" ) {
            IOManager::instance().sendCommand( "AVPlayer", "togglePause" );
        }
        
        break;
    default:
        break;
    }
}

- (void) updateFrom:( const char *) model
{
    string aModel = model;
    
    printf("Controller.updateFrom %s\n", model );
            
    if( aModel == "Sound.AudioInput" ) {
                    
        if( audioInput != 0 ) {

            audioInput->stop();

            [ self closeNode: audioInput ];

            audioInput = 0;
        }

        char result[128];
            
        GetPrivateProfileString( "Sound", "AudioInput", "none", result, sizeof(result) );

        WritePrivateProfileInt( "Sound", "AudioInputDeviceID", -1 );
        
        if( result[0] != 0 ) {
            
            audioInput = (IOAudioInput*) IOManager::instance().openNode( result );
                    
            if( audioInput != 0 ) {

                if( audioInput->getName() == "iMic USB audio system input" ) {

                    printf("Setting volume for iMic\n");

                    audioInput->setVolume( inputVolumeLeft, inputVolumeRight );        
                }
                else if( audioInput->getName() == "Bt8xx Audio" ) {

                    inputVolumeLeft = inputVolumeRight = 20;

                    audioInput->setVolume( inputVolumeLeft, inputVolumeRight );        

                }

                printf("using %s as LineIn\n", audioInput->getName().c_str() );

                audioInput->attachTo ( audioStream, true );

                IOManager::instance().setAlias("LineIn", audioInput->getName() );
                    
                audioInput->start();

                audioStream->start();
                
                WritePrivateProfileInt( "Sound", "AudioInputDeviceID", audioInput->getDeviceID() );
                
            }
        }

        [ self startMixer ];
    }
    else if( aModel == "Sound.AudioOutput" ) {

        if( audioOutput ) {
                                    
            audioOutput->stop();

            [ self closeNode: audioOutput ];

            audioOutput = 0;
        }

        char result[128];
            
        GetPrivateProfileString( "Sound", "AudioOutput", "none", result, sizeof(result) );

        WritePrivateProfileInt( "Sound", "AudioOutputDeviceID", -1 );
        
        if( result[0] != 0 ) {
                    
            audioOutput = (IOAudioOutput*) IOManager::instance().openNode( result );
                    
            if( audioOutput != 0 ) {

                printf("using %s as LineOut\n", audioOutput->getName().c_str() );

#if 0
                audioOutput->attachTo ( audioStream, true );

                IOManager::instance().setAlias("LineOut", audioOutput->getName() );
                    
                audioOutput->start();

                //audioStream->start();

                if( audioInput ) {
                    audioInput->playThrough( audioOutput );
                }
#else
                WritePrivateProfileInt( "Sound", "AudioOutputDeviceID", audioOutput->getDeviceID() );
#endif
            }
        }
        
        [ self startMixer ];
    }
    else if( aModel == "Sound.Volume" || aModel == "Sound.Balance" ) {

        float vol = GetPrivateProfileInt( "Sound", "Volume",  50 );
        float bal = GetPrivateProfileInt( "Sound", "Balance", 50 );

        [ self onVolumeChange: vol bal:bal ];
    }
    else if( aModel == "Player.MovieDone" ) {
                
        if( mCurrentMode == ePlay ) {
            
            IOManager::instance().sendCommand( "PlayListWindow", "playNextFile" );                
        }
    }
}

- (void) onVolumeChange:(float) vol bal:(float) bal
{
    int l,r;
    
    r = (int) vol;
    l = (int) vol;
     
    if( bal > 50 ) {
                    
        bal -= 50;
        bal *= 2;
        l = (int) (vol - bal);
        
        if( l < 0 )
            l = 0;
    }
    else if( bal < 50 ) {
                    
        bal -= 50;
        bal *= 2;
        r = (int) (vol + bal);
        
        if( r < 0 ) 
            r = 0;
    }

    char r_str[32];
    char l_str[32];

    sprintf( l_str, "%d", l );
    sprintf( r_str, "%d", r );
    
    IOManager::instance().sendCommand( "LineOut", "leftVolume", l_str );
    IOManager::instance().sendCommand( "LineOut", "rightVolume", r_str );
}

- (void) beforeExit
{
    puts( "beforeExit" );
    
    [ self stopMixer ];
    
    [ self finitStreams ];
}
    
- (void) beforeRunning
{
    //[ self loadDriver ];
        
    [ self initStreams ];

    [ self loadSkin ];

    loadPlugin("AudioPlugin", 0, 0 );

    loadPlugin("ProgramListPlugin", 0, 0 );

    [ self updateAudioDefaults ];
    
    int mode = GetPrivateProfileInt( "Application", "Mode", ePlay );

    if( mode == eTV ) {
        [ self switchToTV ];
    }
    else {
        [ self switchToPlayer ];
    }
}

- (void) initStreams
{
    audioStream    = IOStream::create( "io.stream.audio"    );
    videoStream    = IOStream::create( "io.stream.video"    );
    //teleTextStream = IOStream::create( "io.stream.teletext" );
    
    IOManager::instance().registerStream( videoStream );
    IOManager::instance().registerStream( audioStream );

    outputWindow   = (OutputWindow  *) IOManager::instance().openNode("GLWindow.MovieWindow", 0 );

    if( outputWindow != 0 ) {
        
        IOManager::instance().setAlias("GLWindow", outputWindow->getName() );
    
    }
    
    //teleTextInput  = (TeleTextInput  *) IOManager::instance().openNode("Bt8xx TeleText device 2");
    //teleTextOutput = (TeleTextOutput *) IOManager::instance().openNode("TeleTextOutput 0");
    //teleTextInput->attachTo( teleTextStream );
    //teleTextOutput->attachTo( teleTextStream );
    //teleTextStream->start();
        
    if( GetPrivateProfileInt( "Hardware", "CardType", TVCARD_UNKNOWN ) == TVCARD_UNKNOWN ) {   

        [ self launchPreferences ];
            
    }
    
    mCurrentMode = eNone;    
}

- (void) finitStreams
{
    if( videoStream != 0 ) {
        videoStream->stop();
    }

    if( audioStream != 0 ) {
        audioStream->stop();
    }
    
    [ self freeNode: recorder    ];
    [ self freeNode: videoInput  ];
    [ self freeNode: audioInput  ];
    [ self freeNode: audioOutput ];


    IOManager::instance().unregisterStream( videoStream );
    IOManager::instance().unregisterStream( audioStream );

    if( audioStream != 0 ) {
    
        //IOManager::instance().closeStream( audioStream );
        
        audioStream->release();
    }
    
    if( videoStream != 0 ) {
    
        //IOManager::instance().closeStream( videoStream );
        
        videoStream->release();        
    }
}    
    
- (void) updateAudioDefaults
{
    WritePrivateProfileString( "Sound", "AudioInputDevice0", "none"  );
    WritePrivateProfileString( "Sound", "AudioOutputDevice0", "none"  );

    IOManager::DeviceList & devList = IOManager::instance().getDeviceList();
        
    IOManager::DeviceList::iterator iter = ( devList.begin() );
        
    int inputs = 1;
    int outputs = 1;
        
    while( iter != devList.end() ) {
    
        IONode * n = *iter;
        
        string devName = n->getName();

        if( ( strstr( devName.c_str(), "Audio" ) || strstr( devName.c_str(), "audio" ) ) ) {
            
            if( n->getType() == "IOInput" ) {
                
                char name[128];
                
                sprintf( name, "AudioInputDevice%d", inputs++ );
                
                WritePrivateProfileString( "Sound", name, devName.c_str()  );
            }
            else if( n->getType() == "IOOutput" ) {

                char name[128];
                
                sprintf( name, "AudioOutputDevice%d", outputs++ );
                
                WritePrivateProfileString( "Sound", name, devName.c_str()  );
            }
        }
        iter++;
    }
        
    WritePrivateProfileInt( "Sound", "NumberAudioInputDevices" , inputs );
    WritePrivateProfileInt( "Sound", "NumberAudioOutputDevices" , outputs );

    bool done = false;
    
    do {

        char name  [128];
        char result[128];
            
        sprintf( name, "AudioInputDevice%d", inputs++ );
            
        GetPrivateProfileString( "Sound", name, "", result, sizeof(result) );
        
        if( result[0] != 0 ) {
            RemovePrivateProfileString( "Sound", name );
        }
        else {
            done = true;
        }
            
    } while( ! done );
        
    done = false;
        
    do {

        char name  [128];
        char result[128];
            
        sprintf( name, "AudioOutputDevice%d", outputs++ );
            
        GetPrivateProfileString( "Sound", name, "", result, sizeof(result) );
        
        if( result[0] != 0 ) {
            RemovePrivateProfileString( "Sound", name );
        }
        else {
            done = true;
        }
            
    } while( ! done );
}

- (void) loadSkin
{
    char result[128];
    GetPrivateProfileString( "Application", "Skin", "AquaSkin", result, sizeof(result) );

    loadPlugin(result, 0, 0 );
        
    IOManager::instance().sendCommand( "GLWindow", "setSkin", result );
    
    loadPlugin( "PlayListPlugin", 0, 0 );
    
    IOManager::instance().sendCommand( "PlayListWindow", "setSkin", result );
}
    
- (void) loadDriver
{
    const char * appDir = GetApplicationDirectory();
    
    NSString * loadKextScript = [ NSString stringWithCString:appDir ];

    loadKextScript = [ loadKextScript stringByAppendingString: [NSString stringWithCString: "/Contents/Resources/LoadKext.sh" ]];
    
    printf( "Controller::loadDriver: LoadKextScript: %s\n", [ loadKextScript cString ] );
    
    NSString * cmd = loadKextScript;
    
    cmd = [ cmd stringByAppendingString:@" " ];
    cmd = [ cmd stringByAppendingString: [NSString stringWithCString:appDir ]];
        
    printf("Controller::loadDriver: cmd(%s)\n", [ cmd cString ] );
        
    int rc = system( [ cmd cString ] );
    
    if( rc != 0 ) {
    
        [ self showFatalError: "XTelevision"  message:"Failed to load kernel extension Bt8xx.kext" errcode:rc  ];
    }
}

@end

class Controller : public IOOutput, public IOView {

    IOModel       mAudioInput;
    IOModel       mAudioOutput;
    IOModel       mAudioVolume;
    IOModel       mAudioBalance;
    IOModel       mMovieDone;
    
    XTelevision * xTelevision;
    
public:

    Controller( const string & aName, IODeviceID anID ) 
    : IOOutput( aName, anID ) 
    , mAudioInput  ( "Sound.AudioInput" )
    , mAudioOutput ( "Sound.AudioOutput" )
    , mAudioVolume ( "Sound.Volume"     )
    , mAudioBalance( "Sound.Balance"    )
    , mMovieDone   ( "Player.MovieDone" ) {

        NSAutoreleasePool * pool =[ [ NSAutoreleasePool alloc ] init ];
    
        mAudioInput.addView( this );                
        mAudioOutput.addView( this );                
        mAudioVolume.addView( this );                
        mAudioBalance.addView( this );                
        mMovieDone.addView( this );                

        xTelevision = [[ XTelevision alloc ] init ];
    
        [ xTelevision retain ];
    
        [ pool release ];
    }
    
    virtual ~Controller() {
        
        [ xTelevision release ];
    }
    
    bool start() {
        mStarted = true;
        return true;
    }
        
    bool stop(){
        mStarted = false;
        return true;
    }

    bool pause(){
        return true;
    }
        
    bool resume(){
        return true;
    }
    
    size_t frameSize(){
        return 0;
    }

    bool updateFrom( IOBuffer * ioBuf ){
        return false;
    }

    bool command( const string & command, const string & param1 = "", const string & param2 = "", 
                  const string & param3 = "", const string & param4 = "" ){

        printf("Controller::command is(%s) param1(%s)\n",  command.c_str() , param1.c_str() );
        
        if( command == "beforeRunning" ) {

            [ xTelevision beforeRunning ];

            mAudioInput.changed();
            mAudioOutput.changed();

        }
        else if( command == "beforeExit" ) {
            [ xTelevision beforeExit ];
        }
        else if( command == "keyDown" ) {
            [ xTelevision keyDown:param1.c_str() key:param2.c_str() keyCode:atoi(param3.c_str()) modifiers:atoi(param4.c_str()) ];
        }
        else if( command == "buttonPressed" ) {
            [ xTelevision buttonPressed:param1.c_str() ];
        }
        else if( command == "handleDrop" ) {
            [ xTelevision handleDrop:param1.c_str() fileName:param2.c_str() ];
        }
        else if( command == "scrollWheel" ) {
            [ xTelevision handleWheel:param1.c_str() dx:atoi(param2.c_str()) dy:atoi(param3.c_str()) dz:atoi(param4.c_str()) ];
        }
        else if( command == "stopPlaying" ) {
            [ xTelevision stopPlaying ];
        }
        else if( command == "playMovie" ) {

            [ xTelevision playMovie: [NSString stringWithCString: param1.c_str() ] ];
        }
        else if( command == "onQuit" ) {

            [ xTelevision onQuit ];
        }
        else if( command == "onPreferences" ) {

            [ xTelevision onPreferences ];
        }
        
        return true;
    }
    
    void updateFrom( IOModel * aModel ) {
    
        if( aModel->getName() == "Player.MovieDone" && mMovieDone.getString() == "0" ) {
            return;
        }
        
        [ xTelevision updateFrom: aModel->getName().c_str() ];
    }
    
};

extern "C" int initControllerPlugin( int argc, const char ** argv  )
{
    Controller * c = new Controller( "WindowController", 0 );
    
    IOManager::instance().registerNode ( c );
    
    return true;
}

