#import <Carbon/Carbon.h>
#import "MasterController.h"
#import "Util.h"
#import <unistd.h>

static NSString *DEINTERLACE_KEY = @"deinterlace";
static NSString *ASPECT_RATIO_KEY = @"aspect ratio";
static NSString *GAMMA_KEY = @"gamma";
static NSString *MANUAL_ADDRESS_KEY = @"manualAddress";
static NSString *PLAYER_KEY = @"player";
static NSString *CLIENT_ID_KEY = @"clientId";
static int START_PORT = 8001;

@implementation MasterController

// get the version from a bundle
- (NSString *)getVersion:(NSString *)bundlePath {
    // first, read the info plist
    NSString* plistPath = [bundlePath stringByAppendingString:@"/Contents/Info.plist"];
    NSData* plistData = [NSData dataWithContentsOfFile:plistPath];
    if (plistData == nil) {
        printf("Error reading Info.plist from %s\n", [plistPath UTF8String]);
        return nil;
    }
    NSString* error;
    NSPropertyListFormat format;
    id plist = [NSPropertyListSerialization propertyListFromData: plistData
                                            mutabilityOption: NSPropertyListImmutable
                                            format: &format
                                            errorDescription: &error];
    if (!plist) {
        printf("Error reading version from %s: %s\n", [plistPath UTF8String], [error UTF8String]);
        [error release];
        return nil;
    }
    return [plist objectForKey:@"CFBundleVersion"];
}

- (NSString *) choosePlayer {
    [choosePlayerInfoControl retain];
    
    NSView* superview = [choosePlayerInfoControl superview];
    if (superview != nil) {
        [choosePlayerInfoControl removeFromSuperview];
    }
    NSOpenPanel* openPanel = [NSOpenPanel openPanel];
    NSString* title = @"Choose a video player";
    [openPanel setTitle: title];
    [openPanel setAccessoryView: choosePlayerInfoControl];
    [openPanel setDelegate: self];

    // run the dialog
    NSString* retVal;
    if ([openPanel runModalForDirectory: @"/Applications" file: nil types: [NSArray arrayWithObject: @"app"]] == NSOKButton) {
        retVal = [openPanel filename];
    } else {
        retVal = @"";
    }
    [openPanel setAccessoryView: nil];
    
    if (superview != nil) {
        [superview addSubview:choosePlayerInfoControl];
    }
    
    [choosePlayerInfoControl release];
    
    return [retVal retain];
}

- (BOOL) panel: (id) sender isValidFilename: (NSString*) filename {
    NSString* executable = [filename stringByAppendingString: @"/Contents/MacOS/VLC"];
    if (![[NSFileManager defaultManager] isExecutableFileAtPath: executable]) {
        NSBeginAlertSheet(@"Unknown player", nil, nil, nil, sender, nil, nil, nil, nil, @"Only VLC is currently supported. Also, VLC versions other than 0.6.0 through 0.6.2 might not work.", nil, nil, nil);
        return FALSE;
    }
    return TRUE;
}

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
    [preferencesController initPreferences];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // find out our resource path
    _resourcePath = [[[NSBundle mainBundle] resourcePath] retain];
    
    // create the support and log directories
    _cytvSupportPath = [[Util getCyTVSupportPath] retain];
    [Util shell:[NSString stringWithFormat:@"mkdir -p %@",
                 [Util quoteForShell:_cytvSupportPath]]];
    _cytvLogPath =  [[Util getCyTVLogPath] retain];
    [Util shell:[NSString stringWithFormat:@"mkdir -p %@",
                 [Util quoteForShell:_cytvLogPath]]];
    //printf("_resourcePath: %s\n", [[Util quoteForShell:_resourcePath] cString]);
    //printf("_cytvSupportPath: %s\n", [[Util quoteForShell:_cytvSupportPath] cString]);
    //printf("_cytvLogPath: %s\n", [[Util quoteForShell:_cytvLogPath] cString]);

    // copy the corkscrew binary (if not already present)
    [Util shell:[NSString stringWithFormat:@"test -e %@/corkscrew || cp %@/corkscrew %@",
                 [Util quoteForShell:_cytvSupportPath],
                 [Util quoteForShell:_resourcePath],
                 [Util quoteForShell:_cytvSupportPath]]];
    
    // load some defaults for the client GUI
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
    int deinterlace = [defaults integerForKey:DEINTERLACE_KEY];
	int aspectRatio = [defaults integerForKey:ASPECT_RATIO_KEY];
    float gamma;
    if ([defaults objectForKey:GAMMA_KEY] == nil) {
        gamma = 1;
    } else {
        gamma = [defaults floatForKey:GAMMA_KEY];
    }
    [deinterlaceControl selectItemAtIndex:deinterlace];
    [aspectRatioControl selectItemAtIndex:aspectRatio];
    [gammaControl setFloatValue:gamma];
    
    // initialize the stream info list
    [applicationController initStreamInfo];
    
    // initialize the settings of the server connection window
    // and the preferences
    [chooseServerController initSettings];
    [chooseServerController initAdvancedSettings];
    
    // initialize some members
    _secureClientPid = 0;
    _streamCachePid = 0;
    _playerPid = 0;
    _clientId = @"";
    _currentQuality = @"--";
    _idleEnabled = FALSE;
    
    // finish setting up the stream list
    [streamsControl setDrawsGrid:TRUE];
    [streamsControl setTarget:self];
    [streamsControl setDoubleAction:@selector(switchStreamDoubleClick:)];
    [streamsControl setEnterAction:@selector(switchStreamEnter:)];
    
    // load some more previously saved preferences
    _manualAddress = [defaults stringForKey:MANUAL_ADDRESS_KEY];
    if (_manualAddress != nil) {
        [_manualAddress retain];
        [manualAddressControl setStringValue:_manualAddress];
    }
    _player = [defaults stringForKey:PLAYER_KEY];
    if (_player != nil) {
        [_player retain];
    } else {
        NSURL *temp;
        OSStatus err = LSGetApplicationForInfo(kLSUnknownType, 'VLC#', NULL, kLSRolesAll, NULL, (CFURLRef *)&temp);
        if (err == noErr) {
            _player = [[temp path] retain];
            [temp release];
        } else {
            // ask the user for the right player if none is found
            _player = [[self choosePlayer] retain];
            if ([_player length] == 0) {
                [[NSApplication sharedApplication] terminate:nil];
                return;
            }
        }
        [defaults setObject:_player forKey:PLAYER_KEY];
        [defaults synchronize];
    }
    [playerInfoControl setStringValue:_player];

    // search for Rendezvous servers
    _rendezvousClient = [[RendezvousClient alloc] init];
    [_rendezvousClient setDelegate:self];
    [searchProgressControl startAnimation:nil];
    [searchingForServersWindow center];
    [searchingForServersWindow makeKeyAndOrderFront:nil];
}

- (void)showAvailableServers {
    // fill the server list
    NSArray *servers = [_rendezvousClient resultsOfBrowsing];
    [serversDataSource setServers:servers];
    [serversControl reloadData];
    
    // check if the manual checkbox must be selected
    // (because nothing was found)
    if ([servers count] == 0) {
        [chooseServerController setManual];
    }
    
    // switch the active window
    [searchingForServersWindow orderOut:nil];
    [searchProgressControl stopAnimation:nil];
    [chooseServerWindow center];
    [chooseServerWindow makeKeyAndOrderFront:nil];
}

- (void)browsingFinished:(NSArray*)results {
    [self showAvailableServers];
}

- (IBAction)abortSearch:(id)sender {
    [_rendezvousClient stopBrowsing];
    [self showAvailableServers];
}

- (IBAction)changePlayer:(id)sender {
    NSString *newPlayer = [self choosePlayer];
    if ([newPlayer length] > 0) {
        [_player autorelease];
        _player = [newPlayer retain];
        [playerInfoControl setStringValue:_player];
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        [defaults setObject:_player forKey:PLAYER_KEY];
        [defaults synchronize];
    }
}

- (void)startSecureClient:(int)listenPort {
    int securePort = [chooseServerController securePort];
    NSString *secureUser = [chooseServerController secureUser];
    NSArray *serverParts = [_server componentsSeparatedByString:@":"];
    NSString *server = [serverParts objectAtIndex:0];
    int serverPort = [[serverParts objectAtIndex:1] intValue];
    NSString *command = [NSString stringWithFormat:
                         @"export DISPLAY=localhost SSH_ASKPASS=%@/SSHAskPassHelper.app/Contents/MacOS/SSHAskPassHelper; cd %@; ssh -o UserKnownHostsFile=server_keys -N -p %d -L%d:127.0.0.1:%d",
                         [Util quoteForShell:_resourcePath],
                         [Util quoteForShell:_cytvSupportPath],
                         securePort,
                         listenPort,
                         serverPort];
    if ([chooseServerController proxy]) {
        NSString *proxyTemp = [chooseServerController proxyServer];
        NSArray *proxyParts = [proxyTemp componentsSeparatedByString:@":"];
        NSString *proxyServer = [proxyParts objectAtIndex:0];
        int proxyPort = [[proxyParts objectAtIndex:1] intValue];
        command = [NSString stringWithFormat:@"%@ -o ProxyCommand=./corkscrew' '%@' %d %%h %%p'",
                   command,
                   [Util quoteForShell:proxyServer],
                   proxyPort];
        if ([chooseServerController proxyAuth]) {
            command = [command stringByAppendingString:@"' proxy_auth'"];
        }
    }
    command = [NSString stringWithFormat:@"%@ %@@%@ </dev/null >/dev/null 2>&1 & echo $!",
               command,
               [Util quoteForShell:secureUser],
               [Util quoteForShell:server]];
    _actualServer = [[NSString stringWithFormat:@"127.0.0.1:%d", listenPort] retain];
    _secureClientPid = [[Util shell:command] intValue];
}

- (void)stopSecureClient {
    if (_secureClientPid > 0) {
        NS_DURING
            [Util shell:[NSString stringWithFormat:@"kill %d", _secureClientPid]];
        NS_HANDLER
            // do nothing for now
        NS_ENDHANDLER
    }
}

// check if a specified pid still belongs to a running process
- (int)getPid:(int)pid {
    int retVal;
    NS_DURING
        retVal = [[Util shell:[NSString stringWithFormat:@"ps ax | grep '^ *%d '", pid]] intValue];
    NS_HANDLER
        retVal = 0;
    NS_ENDHANDLER
    return retVal;
}

// kill a process (and make sure it's dead)
- (void)killPid:(int)pid {
    if (pid > 0) {
        NS_DURING
            [Util shell:[NSString stringWithFormat:@"kill %d", pid]];
        NS_HANDLER
            // ignore
        NS_ENDHANDLER
        int tries = 0;
        while ([self getPid:pid] > 0) {
            ++tries;
            if (tries > 6) {
                NS_DURING
                    printf("trying final kill of %d\n", pid);
                    [Util shell:[NSString stringWithFormat:@"kill -9 %d", pid]];
                NS_HANDLER
                    // ignore
                NS_ENDHANDLER
                return;
            }
            usleep(500000);
        }
    }
}

- (int)getSecureClientPid {
    int retVal;
    NS_DURING
        retVal = [[Util shell:[NSString stringWithFormat:@"ps x | grep '^ *%d '",
                               _secureClientPid]] intValue];
    NS_HANDLER
        retVal = 0;
    NS_ENDHANDLER
    return retVal;
}

- (void)startStreamCacheCore {
    _streamCachePid =
        [[Util shell:[NSString stringWithFormat:
                      @"/usr/bin/python %@/streamcache.py %d %@ %d status prepare/ 0 %@ >%@/cytv_streamcache.log 2>&1 & echo $!",
                      [Util quoteForShell:_resourcePath],
                      _streamCachePort,
                      [Util quoteForShell:[NSString stringWithFormat:@"http://%@",
                                           _actualServer]],
                      _cacheSize*1024,
                      [Util quoteForShell:[NSString stringWithFormat:@"http://%@/?cacheEmpty&",
                                           _actualServer]],
                      [Util quoteForShell:_cytvLogPath]]] intValue];
}

- (void)waitForStreamCache {
    NSString *cacheStatus = @"";
    while ([cacheStatus length] == 0) {
        cacheStatus = [applicationController getURL:
                       [NSString stringWithFormat:@"http://127.0.0.1:%d/status",
                        _streamCachePort]];
        if ([cacheStatus length] == 0) {
            usleep(500000);
        }
    }
}

- (void)startStreamCache {
    [self startStreamCacheCore];
    [self waitForStreamCache];
}

- (void)killStreamCache {
    int pidToKill = _streamCachePid;
    _streamCachePid = 0;
    [self killPid:pidToKill];
}

- (NSString *)makeURL:(NSString *)params {
    return [NSString stringWithFormat:@"http://%@/%@",
            _actualServer, params];
}

- (NSString *)makeStreamURL:(NSString *)params {
    return [NSString stringWithFormat:@"http://%@/%@",
            _streamingServer, params];
}

- (NSString *)sendCommand:(NSString *)command {
    NSString *actualCommand = command;
    if ([_clientId length] > 0) {
        actualCommand = [NSString stringWithFormat:@"%@&clientId=%@",
                         command, _clientId];
    }
    return [applicationController getURL:[self makeURL:actualCommand]];
}

- (void)startPlayer:(NSString *)streamId quality:(NSString *)quality {
    NSString *streamURL;
    if ([_clientId length] > 0) {
        NSString *streamIdParam = ([streamId length] > 0 ?
                                   [@"&streamId=" stringByAppendingString:streamId] : @"");
        NSString *transcodingParam = ([_transcoding length] > 0 ?
                                      [@"&" stringByAppendingString:_transcoding] : @"");
        streamURL = [NSString stringWithFormat:@"?clientId=%@&cacheSize=%d%@%@",
                     _clientId, _cacheSize, streamIdParam, transcodingParam];
    } else {
        streamURL = @"";
    }
    int aspectRatioTag = [[aspectRatioControl selectedItem] tag];
    NSString *aspectRatio = @"";
    if (aspectRatioTag == 1) {
        aspectRatio = @"4:3";
    } else if (aspectRatioTag == 2) {
        aspectRatio = @"14:9";
    } else if (aspectRatioTag == 3) {
        aspectRatio = @"16:9";
    } else if (aspectRatioTag == 4) {
        aspectRatio = @"235:100";
    }
    int deinterlaceTag = [[deinterlaceControl selectedItem] tag];
    NSString *deinterlace = @"";
    if (deinterlaceTag == 1) {
        deinterlace = @"discard";
    } else if (deinterlaceTag == 2) {
        deinterlace = @"blend";
    }
    float gamma = [gammaControl floatValue];
    NSString *additionalOptions = @"";
    NSString *filters = @"";
    if ([aspectRatio length] > 0) {
        additionalOptions = [NSString stringWithFormat:@"%@--crop-aspect=%@ ",
                             additionalOptions, aspectRatio];
        filters = [filters stringByAppendingString:@":crop2"];
    }
    if ([deinterlace length] > 0) {
        additionalOptions = [NSString stringWithFormat:@"%@--deinterlace-mode=%@ ",
                             additionalOptions, deinterlace];
        filters = [filters stringByAppendingString:@":deinterlace"];
    }
    if (gamma != 1) {
        additionalOptions = [NSString stringWithFormat:@"%@--gamma=%.1f ",
                             additionalOptions, gamma];
        filters = [filters stringByAppendingString:@":adjust"];
    }
    if ([filters length] > 0) {
        if ([_playerVersion compare:@"0.8.2"] != NSOrderedAscending) {
            additionalOptions = [NSString stringWithFormat:@"%@--vout-filter=%@ ",
                                 additionalOptions, [filters substringFromIndex:1]];
        } else {
            additionalOptions = [NSString stringWithFormat:@"%@--filter=%@ ",
                                 additionalOptions, [filters substringFromIndex:1]];
        }
    }
    if ([quality isEqualToString:@"M4"]) {
        additionalOptions = [NSString stringWithFormat:@"%@--aspect-ratio 4:3 ",
                             additionalOptions];
    }
    //printf("player command line: %s\n", [[NSString stringWithFormat:_playerCommandLine,
    //                           [NSString stringWithFormat:@"%@'%@'",
    //                            additionalOptions, [self makeStreamURL:streamURL]]] UTF8String]);
    _playerPid = [[Util shell:[NSString stringWithFormat:_playerCommandLine,
                               [NSString stringWithFormat:@"%@'%@'",
                                additionalOptions, [self makeStreamURL:streamURL]]
                              ]] intValue];
}

- (void)stopPlayer {
    if (_playerPid > 0) {
        NS_DURING
            [Util shell:[NSString stringWithFormat:@"kill %d", _playerPid]];
        NS_HANDLER
            // ignore
        NS_ENDHANDLER
    }
}

- (IBAction)importKey:(id)sender {
    NSString *server;
    if ([manualControl state] != NSOnState) {
        int selectedRow = [serversControl selectedRow];
        server = [serversDataSource addressForRow:selectedRow];
    } else {
        server = [manualAddressControl stringValue];
    }
    if ([server length] == 0) {
        NSBeginAlertSheet(@"No server specified", nil, nil, nil, chooseServerWindow,
                          nil, nil, nil, nil, @"Please enter the name or IP address of the desired server.");
    } else {
        if ([server rangeOfString:@" "].location != NSNotFound ||
            [server rangeOfString:@","].location != NSNotFound ||
            [server rangeOfString:@"\n"].location != NSNotFound) {
            NSBeginAlertSheet(@"Illegal server name", nil, nil, nil, chooseServerWindow,
                              nil, nil, nil, nil, @"The server name contains at least one illegal character (space or comma).");
        } else {
            server = [[server componentsSeparatedByString:@":"] objectAtIndex:0];
            [applicationController importServerKeyForHost:server];
        }
    }
}

- (IBAction)connect:(id)sender {
    // validate input fields
    if (![chooseServerController validateSettings]) {
        return;
    }
    _transcoding = [[chooseServerController createTranscodeURLFragment] retain];
    _cacheSize = [chooseServerController cacheSize];
    
    // check whether the specified player (still) exists
    [_playerBinary autorelease];
    _playerBinary = [[_player stringByAppendingString:@"/Contents/MacOS/VLC"] retain];
    if ([[Util shell:[NSString stringWithFormat:@"test -e %@ ; echo $?",
                      [Util quoteForShell:_playerBinary]]] intValue] != 0) {
        NSBeginAlertSheet(@"Cannot find the specified player", nil, nil, nil, chooseServerWindow,
                          nil, nil, nil, nil, @"Please choose an existing player.");
        return;
    }
    _playerVersion = [[self getVersion:_player] retain];

    // determine the server's address
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *server;
    if ([manualControl state] != NSOnState) {
        int selectedRow = [serversControl selectedRow];
        server = [serversDataSource addressForRow:selectedRow];
    } else {
        server = [manualAddressControl stringValue];
        [defaults setObject:server forKey:MANUAL_ADDRESS_KEY];
        [defaults synchronize];
        if ([server rangeOfString:@":"].location == NSNotFound) {
            server = [NSString stringWithFormat:@"%@:%d",
                      server, START_PORT];
        }
    }
    _server = [server retain];
    
    // display a progress window
    [chooseServerWindow orderOut:nil];
    [startProgressControl setUsesThreadedAnimation:TRUE];
    [startProgressControl startAnimation:nil];
    [startingPlayerWindow center];
    [startingPlayerWindow makeKeyAndOrderFront:nil];
    
    // steart the secure client and the stream cache
    if ([chooseServerController secureConnection]) {
        _secureClientPort = [Util findPortStartingWith:START_PORT];
        [self startSecureClient:_secureClientPort];
    } else {
        _secureClientPort = 0;
        _actualServer = [_server retain];
    }
    _scheduledStream = @"-1";
    _streamCachePort = [Util findPortStartingWith:START_PORT excluding:_secureClientPort];
    _streamingServer = [[NSString stringWithFormat:@"127.0.0.1:%d", _streamCachePort] retain];
    [self startStreamCache];

    // get the list of channels
    NSString *channelList;
    while (TRUE) {
        NS_DURING
            channelList = [self sendCommand:@"?getChannelList"];
        NS_HANDLER
            channelList = @"";
        NS_ENDHANDLER
        if ([channelList length] > 0 ||
            ![chooseServerController secureConnection] ||
            [self getSecureClientPid] == 0) {
            break;
        }
        usleep(1000000);    // wait until the ssh process is up
    }
    if ([channelList length] == 0) {
        [startingPlayerWindow orderOut:nil];
        [startProgressControl stopAnimation:nil];
        int serverPort;
        if ([chooseServerController secureConnection]) {
            serverPort = [chooseServerController securePort];
        } else {
            serverPort = [[[_server componentsSeparatedByString:@":"] objectAtIndex:1] intValue];
        }
        NSRunAlertPanel(@"Cannot connect to the server", @"Please check if the server is running, the server's address is correct, and that port %d is not blocked.", nil, nil, nil, serverPort);
        [[NSApplication sharedApplication] terminate:nil];
        return;
    }
    
    // process the channel list
    NSArray *channels = [channelList componentsSeparatedByString:@"\n"];
    NSString *channel;
    NSArray *channelParts;
    NSMutableArray *info = [[NSMutableArray alloc] init];
    _channelsAvailable = FALSE;
    BOOL recording = FALSE;
    int i;
    for (i = 0; i < [channels count]; ++i) {
        channel = [channels objectAtIndex:i];
        if ([channel length] > 0) {
            channelParts = [channel componentsSeparatedByString:@":"];
            if ([channelParts count] == 1) {
                [info addObject:channel];
            } else {
                if (!_channelsAvailable) {
                    [channelControl removeAllItems];
                    _channelsAvailable = TRUE;
                }
                [channelControl addItemWithTitle:channel];
                [[channelControl lastItem] setTag:[[channelParts objectAtIndex:0] intValue]];
            }
        }
    }
    if ([info count] == 3 || [info count] == 4) {
        int offset = 0;
        if ([info count] == 4) {
            int currentChannelNumber = [[info objectAtIndex:0] intValue];
            int index = [channelControl indexOfItemWithTag:currentChannelNumber];
            if (index != -1) {
                [channelControl selectItemAtIndex:index];
            }
            offset = 1;
        }
        recording = ([[info objectAtIndex:offset] intValue] != 0 ? TRUE : FALSE);
        _clientId = [[info objectAtIndex:offset+1] retain];
        _currentQuality = [[info objectAtIndex:offset+2] retain];
    }
    [info release];
    [applicationController updateButtonsWithRecordingState:recording];
    
    // make the remote control visible and start the player
    NSString *clientIdOverride = [defaults stringForKey:CLIENT_ID_KEY];
    if (clientIdOverride != nil) {
        _clientId = clientIdOverride;
    }
    [applicationController initBackgroundHelperWithBaseURL:[NSString stringWithFormat:@"http://%@/",
                                                            _actualServer]
     clientId:_clientId
     cacheURL:[NSString stringWithFormat:@"http://%@/", _streamingServer]];
    [applicationController startInfoCollector];
    _playerCommandLine = [[NSString stringWithFormat:@"%@ --plugin-path=%@/vlc --http-caching=250 --quiet %%@ >/dev/null 2>&1 & echo $!",
                           [Util quoteForShell:_playerBinary],
                           [Util quoteForShell:_resourcePath]] retain];
    [startingPlayerWindow orderOut:nil];
    [startProgressControl stopAnimation:nil];
    [controlWindow makeKeyAndOrderFront:nil];
    _idleEnabled = TRUE;
    [self startPlayer:@"" quality:_currentQuality];
}

- (void)updateChannel:(NSString *)info {
    if ([info length] > 0) {
        if (_channelsAvailable) {
            NSArray *infoParts = [info componentsSeparatedByString:@"\n"];
            int channel = [[infoParts objectAtIndex:0] intValue];
            int index = [channelControl indexOfItemWithTag:channel];
            if (index != -1) {
                [channelControl selectItemAtIndex:index];
            }
        }
    }
}

- (IBAction)record:(id)sender {
    [applicationController stopInfoCollector];
    NSString *info;
    if ([recordControl state] != NSOnState) {
        NS_DURING
            info = [self sendCommand:@"?stopRecording"];
            [self updateChannel:info];
            [applicationController updateButtonsWithRecordingState:FALSE];
        NS_HANDLER
            [recordControl setState:NSOnState];
        NS_ENDHANDLER
    } else {
        NS_DURING
            [applicationController updateButtonsWithRecordingState:TRUE];
            info = [self sendCommand:@"?startRecording"];
            [self updateChannel:info];
        NS_HANDLER
            [recordControl setState:NSOffState];
        NS_ENDHANDLER
    }
    [applicationController startInfoCollector];
}

- (IBAction)channelUp:(id)sender {
    NSString *serverCommand;
    //if ([channelControl isEnabled]) {
    //    int newIndex = [channelControl indexOfSelectedItem] + 1;
    //    if (newIndex >= [channelControl numberOfItems]) {
    //        newIndex = 0;
    //    }
    //    int newChannel = [[channelControl itemAtIndex:newIndex] tag];
    //    serverCommand = [NSString stringWithFormat:@"?setChannel&%d", newChannel];
    //} else {
        serverCommand = @"?channelUp";
    //}
    [applicationController stopInfoCollector];
    NS_DURING
        NSString *info = [self sendCommand:serverCommand];
        [self updateChannel:info];
    NS_HANDLER
        // do nothing for now (should always work as long as the stream is running)
    NS_ENDHANDLER
    [applicationController startInfoCollector];
}

- (IBAction)channelDown:(id)sender {
    NSString *serverCommand;
    //if ([channelControl isEnabled]) {
    //    int newIndex = [channelControl indexOfSelectedItem] - 1;
    //    if (newIndex <= 0) {
    //        newIndex = [channelControl numberOfItems] - 1;
    //    }
    //    int newChannel = [[channelControl itemAtIndex:newIndex] tag];
    //    serverCommand = [NSString stringWithFormat:@"?setChannel&%d", newChannel];
    //} else {
        serverCommand = @"?channelDown";
    //}
    [applicationController stopInfoCollector];
    NS_DURING
        NSString *info = [self sendCommand:serverCommand];
        [self updateChannel:info];
    NS_HANDLER
        // do nothing for now (should always work as long as the stream is running)
    NS_ENDHANDLER
    [applicationController startInfoCollector];
}

- (IBAction)channelScan:(id)sender {
    if ([channelScanControl state] == NSOnState) {
        [applicationController startChannelScanner];
    } else {
        [applicationController stopChannelScanner];
    }
}

- (IBAction)live:(id)sender {
    if ([_scheduledStream isEqualToString:@"-1"]) {
        [_scheduledStream autorelease];
        _scheduledStream = @"";
        [_scheduledQuality autorelease];
        _scheduledQuality = [_currentQuality retain];
        [applicationController stopInfoCollector];
        [self stopPlayer];
        [self killStreamCache];
        [self startStreamCache];
        [self startPlayer:_scheduledStream quality:_scheduledQuality];
        [applicationController startInfoCollector];
        _scheduledStream = @"-1";
    } else {
        [_scheduledStream autorelease];
        _scheduledStream = @"";
        [_scheduledQuality autorelease];
        _scheduledQuality = [_currentQuality retain];
    }
}

- (IBAction)unlimitedBuffer:(id)sender {
    int protect = ([unlimitedBufferControl state] == NSOnState ? 1 : 0);
    [self sendCommand:[NSString stringWithFormat:@"?protect&%d", protect]];
}

- (IBAction)refreshChannel:(id)sender {
    [self updateChannel:[self sendCommand:@"?getCurrentChannel"]];
}

- (void)switchStream {
    NSString *streamId = [applicationController getSelectedStreamId];
    NSString *quality = [applicationController getSelectedStreamQuality];
    if ([streamId length] > 0) {
        if ([_scheduledStream isEqualToString:@"-1"]) {
            [_scheduledStream autorelease];
            _scheduledStream = [streamId retain];
            [_scheduledQuality autorelease];
            _scheduledQuality = [quality retain];
            [applicationController stopInfoCollector];
            [self stopPlayer];
            [self killStreamCache];
            [self startStreamCache];
            [self startPlayer:_scheduledStream quality:_scheduledQuality];
            [applicationController startInfoCollector];
            [_scheduledStream autorelease];
            _scheduledStream = @"-1";
        } else {
            [_scheduledStream autorelease];
            _scheduledStream = [streamId retain];
            [_scheduledQuality autorelease];
            _scheduledQuality = [quality retain];
        }
    }
}

- (IBAction)switchStreamDoubleClick:(id)sender {
    if ([streamsControl clickedRow] != -1) {
        [self switchStream];
    }
}

- (IBAction)switchStreamEnter:(id)sender {
    [self switchStream];
}

- (IBAction)setChannel:(id)sender {
    int newChannel = [[channelControl selectedItem] tag];
    [applicationController stopInfoCollector];
    NS_DURING
        [self sendCommand:[NSString stringWithFormat:@"?setChannel&%d", newChannel]];
    NS_HANDLER
        // do nothing for now (should always work as long as the stream is running)
    NS_ENDHANDLER
    [applicationController startInfoCollector];
}

- (IBAction)setStreamPos:(id)sender {
    NSString *streamId = [applicationController getCurrentStreamId];
    printf("streamId: %s\n", [streamId cString]);
    if ([streamId length] > 0) {
        double streamSize = [applicationController getCurrentStreamSize];
        long long streamPos = (long long)([streamPosControl doubleValue] / 100.0 * streamSize);
        [applicationController stopInfoCollector];
        NS_DURING
            [self sendCommand:[NSString stringWithFormat:@"?jumpToPosition&%@&%qi",
                               streamId, streamPos]];
        NS_HANDLER
            // do nothing for now
        NS_ENDHANDLER
        [applicationController startInfoCollector];
    }
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // save VLC related preferences
    int deinterlace = [[deinterlaceControl selectedItem] tag];
    int aspectRatio = [[aspectRatioControl selectedItem] tag];
    float gamma = [gammaControl floatValue];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setInteger:deinterlace forKey:DEINTERLACE_KEY];
    [defaults setInteger:aspectRatio forKey:ASPECT_RATIO_KEY];
    [defaults setFloat:gamma forKey:GAMMA_KEY];
    [defaults synchronize];
    [self stopPlayer];
    [self killStreamCache];
    [self stopSecureClient];
}

- (void)applicationDidBecomeActive:(NSNotification *)aNotification {
    NS_DURING
        [applicationController becameActive];
    NS_HANDLER
        // do nothing for now
    NS_ENDHANDLER
}

- (void)applicationDidResignActive:(NSNotification *)aNotification {
    NS_DURING
        [applicationController becameInactive];
    NS_HANDLER
        // do nothing for now
    NS_ENDHANDLER
}

- (ApplicationController *)applicationController {
    return applicationController;
}

- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem {
    if (menuItem == closeControl) {
        return [preferencesWindow isVisible];
    }
    return TRUE;
}

- (IBAction)preferences:(id)sender {
    [preferencesWindow makeKeyAndOrderFront:nil];
}

- (IBAction)close:(id)sender {
    [preferencesWindow close];
}

@end
