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

static int START_PORT = 8001;
static int VIDEO_BITRATE_STD = 1150;
static int VIDEO_BITRATE_HIGH = 2300;
static NSString *MANAGE_EYETV_KEY = @"manageEyeTV";
static NSString *CLOSE_TV_WINDOW_WHEN_INACTIVE_KEY = @"closeTVWindowWhenInactive";
static NSString *LIVE_BUFFER_KEY = @"live buffer";
static NSString *VIDEO_BITRATE_KEY = @"video bitrate";
static NSString *BUFFER_IN_RAM_KEY = @"live buffer in RAM";
static NSString *EYETV_ARCHIVE_DIR = @"EyeTV Archive";

// this code is from Jonathan Rentsch (BSD licensed)
OSErr FindProcessBySignature( OSType type, OSType creator, ProcessSerialNumber *psn ) {
    ProcessSerialNumber tempPSN = { 0, kNoProcess };
    ProcessInfoRec procInfo;
    OSErr err = noErr;
    
    ///RequirePtr( psn );
    
    procInfo.processInfoLength = sizeof( ProcessInfoRec );
    procInfo.processName = nil;
    procInfo.processAppSpec = nil;
    
    while( !err ) {
        err = GetNextProcess( &tempPSN );
        if( !err )
            err = GetProcessInformation( &tempPSN, &procInfo );
        if( !err
            && procInfo.processType == type
            && procInfo.processSignature == creator ) {
            *psn = tempPSN;
            return noErr;
        }
    }
    
    return err;
}

@implementation MasterController

- (NSString *) getURL: (NSString*) url {
    if (_urlFetcher == nil) {
        _urlFetcher = [[URLFetcher alloc] init];
    }
    return [_urlFetcher fetchURL:url];
}

- (NSString*) encodeURL: (NSString*) string {
    // encode a string by converting it to UTF-8, then
    // express every non-alphanumeric character in hex notation
    // usable for URLs (e.g. "%2f")
    
    const char* utf8String = [string UTF8String];
    size_t utf8len = strlen(utf8String);
    if (utf8len == 0) {
        return @"";
    }
    int i;
    char* result = malloc(utf8len*3+1);
    char* ptr = result;
    int code;
    for (i = 0; i < utf8len; ++i) {
        code = ((int) utf8String[i]) & 0xff;
        if ((code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z') ||
            (code >= '0' && code <= '9')) {
            *ptr++ = code;
        } else {
            sprintf(ptr, "%%%.2x", ((int) utf8String[i]) & 0xff);
            ptr += 3;
        }
    }
    *ptr = 0;
    
    NSString* retVal = [NSString stringWithCString: result];
    free(result);
    return retVal;
}

// check if EyeTV is running
- (BOOL)isEyeTVRunning {
    ProcessSerialNumber eyeTVPSN;
    if (FindProcessBySignature('APPL', 'EyTV', &eyeTVPSN) == noErr) {
        return TRUE;
    }
    return FALSE;
}

// stop EyeTV
- (void)stopEyeTV {
    if ([self isEyeTVRunning]) {
        [Util shell:[NSString stringWithFormat:@"osascript -e 'tell application \"'%@'\" to quit'",
                     [Util quoteForShell:_EyeTVName]]];
    }
}

// start EyeTV
- (void)startEyeTV {
    [Util shell:[NSString stringWithFormat:@"osascript -e 'tell application \"'%@'\"\nlaunch\nactivate\nend tell'",
                 [Util quoteForShell:_EyeTVName]]];
}

// get EyeTV's bundle location
- (NSString *)getEyeTVPath {
    ProcessSerialNumber eyeTVPSN;
    if (FindProcessBySignature('APPL', 'EyTV', &eyeTVPSN) == noErr) {
        FSRef location;
        UInt8 path[1024];
        if (GetProcessBundleLocation(&eyeTVPSN, &location) == noErr) {
            if (FSRefMakePath(&location, path, 1024) == noErr) {
                return [NSString stringWithUTF8String:path];
            }
        }
    }
    NSURL *temp;
    OSStatus err = LSGetApplicationForInfo(kLSUnknownType, 'EyTV', NULL, kLSRolesAll, NULL, (CFURLRef *)&temp);
    if (err == noErr) {
        [temp autorelease];
        return [temp path];
    }
    return nil;
}

// get EyeTV's preferences
- (id)getEyeTVPreferences {
    // first, read the archive folder data from the preferences
    NSString* prefsPath = [@"~/Library/Preferences/com.elgato.eyetv.plist" stringByExpandingTildeInPath];
    //printf("%s\n", [prefsPath cString]);
    NSData* prefsData = [NSData dataWithContentsOfFile: prefsPath];
    if (prefsData == nil) {
        return nil;
    }
    NSString* error;
    NSPropertyListFormat format;
    id prefs = [NSPropertyListSerialization propertyListFromData: prefsData
                                            mutabilityOption: NSPropertyListImmutable
                                            format: &format
                                            errorDescription: &error];
    if (!prefs) {
        [error release];
        return nil;
    }
    _EyeTVPrefsFormat = format;
    return prefs;
}

// saves EyeTV's preferences
- (void)saveEyeTVPreferences:(id)prefs {
    // first, read the archive folder data from the preferences
    NSString* prefsPath = [@"~/Library/Preferences/com.elgato.eyetv.plist" stringByExpandingTildeInPath];
    //printf("%s\n", [prefsPath cString]);
    NSString* error;
    NSData* prefsData = [NSPropertyListSerialization dataFromPropertyList:prefs
                         format:_EyeTVPrefsFormat errorDescription:&error];
    if (error != nil) {
        [error release];
        printf("Could not save EyeTV's preferences.\n");
        return;
    }
    [prefsData writeToFile:prefsPath atomically:YES];
}

// get EyeTV's archive path
- (NSString *)getEyeTVArchivePath:(id)prefs {
    NSString* retVal = nil;
    
    NSData* archiveFolderData = [prefs objectForKey: @"archive folder"];
    if (!archiveFolderData) {
        return retVal;
    }
    
    // now convert the data to an alias handle and resolve the alias
    Handle handle = NewHandle([archiveFolderData length]);
    if (!handle) {
        return retVal;
    }
    HLock(handle);
    BlockMoveData([archiveFolderData bytes], *handle, [archiveFolderData length]);
    HUnlock(handle);
    FSRef archiveFolderRef;
    Boolean wasChanged;
    OSErr result = FSResolveAlias(NULL, (AliasHandle) handle, &archiveFolderRef, &wasChanged);
    DisposeHandle(handle);
    if (result != noErr) {
        return retVal;
    }

    // finally, get the path in UTF-8 format
    size_t bufferSize = 1000;
    UInt8* pathBytes = malloc(bufferSize);
    OSStatus status;
    do {
        status = FSRefMakePath(&archiveFolderRef, pathBytes, bufferSize);
        if (status == pathTooLongErr) {
            free(pathBytes);
            bufferSize <<= 1;
            pathBytes = malloc(bufferSize);
        }
    } while (status == pathTooLongErr);
    if (status == noErr) {
        retVal = [NSString stringWithUTF8String: pathBytes];
    }
    free(pathBytes);

    return retVal;
}

// get EyeTV's version
- (NSString *)getEyeTVVersion:(NSString *)EyeTVPath {
    // first, read the info plist
    NSString* plistPath = [EyeTVPath stringByAppendingString:@"/Contents/Info.plist"];
    //printf("%s\n", [prefsPath cString]);
    NSData* plistData = [NSData dataWithContentsOfFile:plistPath];
    if (plistData == nil) {
        printf("Error reading EyeTV's 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 EyeTV version: %s\n", [error UTF8String]);
        [error release];
        return nil;
    }
    return [plist objectForKey:@"CFBundleVersion"];
}

- (void)recover {
    NSString *archivePath = [NSString stringWithFormat:@"%@/%@",
                             _EyeTVArchivePath, EYETV_ARCHIVE_DIR];
    NSString *encodedArchivePath = [self encodeURL:archivePath];
    NS_DURING
        [self getURL:[NSString stringWithFormat:@"http://localhost:%d/?recover&%@",
                      _serverPort, encodedArchivePath]];
    NS_HANDLER
        // ignore
    NS_ENDHANDLER
}

- (void)remanage {
    NS_DURING
        [self getURL:[NSString stringWithFormat:@"http://localhost:%d/?eyeTVRestarted",
                      _serverPort]];
    NS_HANDLER
        // ignore
    NS_ENDHANDLER
}

- (void)startServer:(BOOL)waitForEyeTV {
    NSString *command = [NSString stringWithFormat:@"export PYTHONPATH=%@; export PATH=/bin:/sbin:/usr/bin:/usr/sbin:%@; /usr/bin/python %@/cytv.py %d %@/%@ %d %d %d %d %@ >>%@/cytv_server.log 2>&1 & echo $!",
                         [Util quoteForShell:_resourcePath],
                         [Util quoteForShell:_resourcePath],
                         [Util quoteForShell:_resourcePath],
                         _serverPort,
                         [Util quoteForShell:_EyeTVArchivePath], [Util quoteForShell:EYETV_ARCHIVE_DIR],
                         (_manageEyeTV ? 1 : 0),
                         (_closeTVWindowWhenInactive ? 1 : 0),
                         ([preferencesController useAltNumbering] ? 1 : 0),
                         (waitForEyeTV ? 1 : 0),
                         [Util quoteForShell:_EyeTVVersion],
                         [Util quoteForShell:_cytvLogPath]];
    if (_serverPort < 1024) {
        _serverPid = [[Util adminShell:command] intValue];
    } else {
        _serverPid = [[Util shell:command] intValue];
    }
}

- (void)startSecureServer {
    int secureServerPort = [preferencesController secureServerPort];
    NSString *allowedUsers = [preferencesController allowedUsersString];
    if ([allowedUsers length] == 0) {
        NSRunAlertPanel(@"No allowed users", @"The secure server will not be started. Please check your preferences.", nil, nil, nil);
        return;
    }
    _secureServerPid = [[Util adminShell:[NSString stringWithFormat:@"sshd -h %@/server_rsa_key -D -p %d -o AllowUsers=%@ -o PermitRootLogin=no -f %@/sshd_config >/dev/null 2>&1 & echo $!",
                                          [Util quoteForShell:_cytvSupportPath],
                                          secureServerPort,
                                          [Util quoteForShell:allowedUsers],
                                          [Util quoteForShell:_resourcePath]]] intValue];
}

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

- (void)killServer {
    if (_serverPid == 0) {
        return;
    }
    int portToKill = _serverPort;
    int pidToKill = _serverPid;
    _serverPid = 0;
    int tries = 0;
    while ([self getServerPid:pidToKill] != 0) {
        ++tries;
        if (tries > 4) {
            printf("trying final kill of %d\n", pidToKill);
            if (_serverPort < 1024) {
                int reply = NSRunAlertPanel(@"Authorization required", @"Stopping the core server gracefully failed, now trying to force-quit it. Since it's running on a privileged port, you have to provide your admin password - otherwise, it will keep running in the background.", @"Authorize", @"Ignore (keep the server running)", nil);
                if (reply != NSAlertDefaultReturn) {
                    return;
                }
                NS_DURING
                    [Util adminShell:[NSString stringWithFormat:@"kill -9 %d", pidToKill]];
                NS_HANDLER
                    // nothing we can do in this case
                NS_ENDHANDLER
            } else {
                NS_DURING
                    [Util shell:[NSString stringWithFormat:@"kill -9 %d", pidToKill]];
                NS_HANDLER
                    // nothing we can do in this case
                NS_ENDHANDLER
            }
        } else if (tries > 3) {
            printf("trying harder kill of %d\n", pidToKill);
            if (_serverPort < 1024) {
                int reply = NSRunAlertPanel(@"Authorization required", @"The core server is running on a privileged port, and shutting it down normally failed. To stop it, you have to provide your admin password - otherwise, it will keep running in the background.", @"Authorize", @"Ignore (keep the server running)", nil);
                if (reply != NSAlertDefaultReturn) {
                    return;
                }
                NS_DURING
                    [Util adminShell:[NSString stringWithFormat:@"kill %d", pidToKill]];
                NS_HANDLER
                    // ignore
                NS_ENDHANDLER
            } else {
                NS_DURING
                    [Util shell:[NSString stringWithFormat:@"kill %d", pidToKill]];
                NS_HANDLER
                    // ignore
                NS_ENDHANDLER
            }
        } else if (tries == 1) {
            NS_DURING
                [self getURL:[NSString stringWithFormat:@"http://127.0.0.1:%d/?shutdown", portToKill]];
            NS_HANDLER
                // ignore
            NS_ENDHANDLER
        }
        usleep(1000000);
    }
}

- (void)killSecureServer {
    if (_secureServerPid == 0) {
        return;
    }
    int pidToKill = _secureServerPid;
    _secureServerPid = 0;
    int tries = 0;
    while ([self getServerPid:pidToKill] != 0) {
        ++tries;
        if (tries > 3) {
            int reply = NSRunAlertPanel(@"Authorization required", @"Stopping the secure server gracefully failed, now trying to force-quit it. Since it's running as a privileged process, you have to provide your admin password - otherwise, it will keep running in the background.", @"Authorize", @"Ignore (keep the server running)", nil);
            if (reply != NSAlertDefaultReturn) {
                return;
            }
            printf("trying harder admin kill of %d\n", pidToKill);
            NS_DURING
                [Util adminShell:[NSString stringWithFormat:@"kill -9 %d", pidToKill]];
            NS_HANDLER
                // nothing we can do in this case
            NS_ENDHANDLER
        } else if (tries == 1) {
            int reply = NSRunAlertPanel(@"Authorization required", @"The secure server is running as a privileged process. To stop it, you have to provide your admin password - otherwise, it will keep running in the background.", @"Authorize", @"Ignore (keep the server running)", nil);
            if (reply != NSAlertDefaultReturn) {
                return;
            }
            printf("trying normal admin kill of %d\n", pidToKill);
            NS_DURING
                [Util adminShell:[NSString stringWithFormat:@"kill %d", pidToKill]];
            NS_HANDLER
                // ignore
            NS_ENDHANDLER
        }
        usleep(1000000);
    }
}

- (void)applicationWillFinishLaunching:(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]]];
    
    // create the server's SSH key (when not already present)
    NSString *serverKeyFile = [_cytvSupportPath stringByAppendingString:@"/server_rsa_key"];
    [Util shell:[NSString stringWithFormat:@"test -e %@ || ssh-keygen -q -trsa -N '' -f %@",
                 [Util quoteForShell:serverKeyFile],
                 [Util quoteForShell:serverKeyFile]]];
    
    // init preferences
    [preferencesController initPreferences];
    
    // initialize some members and load preferences
    _secureServerPort = [preferencesController secureServerPort];
    if ([preferencesController forceServerPort]) {
        _serverPort = [preferencesController serverPort];
    } else {
        _serverPort = [Util findPortStartingWith:START_PORT excluding:_secureServerPort];
    }
    _serverPid = 0;
    _secureServerPid = 0;
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *manageEyeTVString = [defaults stringForKey:MANAGE_EYETV_KEY];
    if (manageEyeTVString == nil) {
        _manageEyeTV = TRUE;
    } else {
        _manageEyeTV = ([manageEyeTVString intValue] == 0 ? FALSE : TRUE);
    }
    NSString *closeTVWindowWhenInactiveString = [defaults stringForKey:CLOSE_TV_WINDOW_WHEN_INACTIVE_KEY];
    if (closeTVWindowWhenInactiveString == nil) {
        _closeTVWindowWhenInactive = FALSE;
    } else {
        _closeTVWindowWhenInactive = ([closeTVWindowWhenInactiveString intValue] == 0 ? FALSE : TRUE);
    }
    [manageEyeTVControl setState:(_manageEyeTV ? NSOnState : NSOffState)];
    //[closeTVWindowWhenInactiveControl setEnabled:_manageEyeTV];
    [closeTVWindowWhenInactiveControl setState:_closeTVWindowWhenInactive];

    // get EyeTV's preferences
    id prefs = [self getEyeTVPreferences];
    if (prefs == nil) {
        // display an error message
        NSRunAlertPanel(@"EyeTV's preferences could not be found", @"Please start EyeTV and then try again.", nil, nil, nil);
        [[NSApplication sharedApplication] terminate:nil];
        return;        
    }
    [prefs retain]; // so the prefs can be immediately released for later error messages
                    // (I feel it's better to release it reliably, just to make sure
                    // nothing stays locked)
    
    // get EyeTV's path, archive path, and version
    NSString *EyeTVPath = [self getEyeTVPath];
    if (EyeTVPath == nil) {
        // display an error message
        [prefs release];
        NSRunAlertPanel(@"EyeTV could not be found", @"You need EyeTV 1.2 or higher for CyTV to work. Also, please make sure you have the BSD subsystem of OS X installed (which is optional for versions older than OS X 10.4 Tiger).", nil, nil, nil);
        [[NSApplication sharedApplication] terminate:nil];
        return;        
    }
    _EyeTVPath = [EyeTVPath retain];
    NSString *EyeTVArchivePath = [self getEyeTVArchivePath:prefs];
    if (EyeTVArchivePath == nil) {
        // display an error message
        [prefs release];
        NSRunAlertPanel(@"EyeTV's archive could not be found", @"Please start EyeTV and then try again.", nil, nil, nil);
        [[NSApplication sharedApplication] terminate:nil];
        return;        
    }
    _EyeTVName = [[EyeTVPath lastPathComponent] retain];
    _EyeTVArchivePath = [EyeTVArchivePath retain];
    NSString *EyeTVVersion = [self getEyeTVVersion:_EyeTVPath];
    if (EyeTVVersion == nil) {
        // never mind, assume it's a new one
        _EyeTVVersion = @"1.3.2";
    } else {
        _EyeTVVersion = [EyeTVVersion retain];
    }
    _EyeTVVersionAbove131 = ([_EyeTVVersion compare:@"1.3.2"] != NSOrderedAscending);
    
    // check EyeTV's settings
    int liveBuffer = [[prefs objectForKey:LIVE_BUFFER_KEY] intValue];
    int videoBitrate = [[prefs objectForKey:VIDEO_BITRATE_KEY] intValue];
    BOOL bufferInRAM = [[prefs objectForKey:BUFFER_IN_RAM_KEY] boolValue];
    //printf("live buffer: %d\n", liveBuffer);
    //printf("video bitrate: %d\n", videoBitrate);
    //printf("bufferInRAM: %d\n", bufferInRAM);
    if (_EyeTVVersionAbove131 || videoBitrate == 0) {
        [mainMenu removeItem:qualityMenu];
    } else {
        if (videoBitrate <= VIDEO_BITRATE_STD) {
            [standardQualityControl setState:NSOnState];
            [highQualityControl setState:NSOffState];
        } else {
            [highQualityControl setState:NSOnState];
            [standardQualityControl setState:NSOffState];
        }
    }
    BOOL waitForEyeTV = FALSE;
    if (bufferInRAM || liveBuffer < 20) {
        int reply = NSRunAlertPanel(@"EyeTV's preferences need to be adjusted", @"The TV buffer must not reside in RAM and the live buffer must be at least 20 MB.", @"Adjust settings and launch", @"Quit", nil);
        if (reply != NSAlertDefaultReturn) {
            [prefs release];
            [[NSApplication sharedApplication] terminate:nil];
            return;
        }
        if (bufferInRAM) {
            [prefs setObject:[NSNumber numberWithBool:FALSE] forKey:BUFFER_IN_RAM_KEY];
        }
        if (liveBuffer < 20) {
            [prefs setObject:[NSNumber numberWithInt:20] forKey:LIVE_BUFFER_KEY];
        }
        [self saveEyeTVPreferences:prefs];
        [prefs release];
        [self stopEyeTV];
        usleep(1000000);
        [self startEyeTV];
        waitForEyeTV = TRUE;
    } else {
        [prefs release];
        if (![self isEyeTVRunning]) {
            [self startEyeTV];
            waitForEyeTV = TRUE;
        }
    }
    
    NS_DURING
        [self startServer:waitForEyeTV];
        [applicationController publishServiceWithPort:_serverPort];
    NS_HANDLER
        NSRunAlertPanel(@"Could not start the core server", @"Please check if you have the BSD subsystem of OS X installed.", nil, nil, nil);
        [[NSApplication sharedApplication] terminate:nil];
    NS_ENDHANDLER
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
    [self killServer];
    [self killSecureServer];
}

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

- (void)changeEyeTVManagement {
    NSString *answerFromServer = @"";
    while ([answerFromServer length] == 0) {
        answerFromServer = [self getURL:[NSString stringWithFormat:@"http://localhost:%d/?manageEyeTV&%d&%d",
                                         _serverPort,
                                         (_manageEyeTV ? 1 : 0),
                                         (_closeTVWindowWhenInactive ? 1 : 0)]];
        if ([answerFromServer length] == 0) {
            usleep(1000000);
        }
    }
}

- (IBAction)manageEyeTV:(id)sender {
    _manageEyeTV = !_manageEyeTV;
    [manageEyeTVControl setState:(_manageEyeTV ? NSOnState : NSOffState)];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:(_manageEyeTV ? @"1" : @"0") forKey:MANAGE_EYETV_KEY];
    [defaults synchronize];
    [self changeEyeTVManagement];
}

- (IBAction)closeTVWindowWhenInactive:(id)sender {
    _closeTVWindowWhenInactive = !_closeTVWindowWhenInactive;
    [closeTVWindowWhenInactiveControl setState:(_closeTVWindowWhenInactive ? NSOnState : NSOffState)];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:(_closeTVWindowWhenInactive ? @"1" : @"0") forKey:CLOSE_TV_WINDOW_WHEN_INACTIVE_KEY];
    [defaults synchronize];
    [self changeEyeTVManagement];
}

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

- (IBAction)quitCyTVAndEyeTV:(id)sender {
    [self killServer];
    [self killSecureServer];
    [self stopEyeTV];
    [[NSApplication sharedApplication] terminate:nil];
}

- (BOOL)changeBitrate:(int)newBitrate {
    BOOL retVal = TRUE;
    [self stopEyeTV];
    [self recover];
    usleep(1000000);
    id prefs = [self getEyeTVPreferences];
    if (prefs == nil) {
        NSRunAlertPanel(@"Cannot change the bitrate", @"EyeTV will be restarted with the old bitrate setting.", nil, nil, nil);
        retVal = FALSE;
    } else {
        [prefs retain];
        [prefs setObject:[NSNumber numberWithInt:newBitrate] forKey:VIDEO_BITRATE_KEY];
        [self saveEyeTVPreferences:prefs];
        [prefs release];
    }
    [self startEyeTV];
    [self remanage];
    return retVal;
}

- (IBAction)standardBitrate:(id)sender {
    if ([self changeBitrate:VIDEO_BITRATE_STD]) {
        [standardQualityControl setState:NSOnState];
        [highQualityControl setState:NSOffState];
    }
}

- (IBAction)highBitrate:(id)sender {
    if ([self changeBitrate:VIDEO_BITRATE_HIGH]) {
        [highQualityControl setState:NSOnState];
        [standardQualityControl setState:NSOffState];
    }
}

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

- (IBAction)enableSecureServer:(id)sender {
    if (_secureServerPid == 0) {
        [self startSecureServer];
    } else {
        [self killSecureServer];
    }
    [enableSecureServerControl setState:(_secureServerPid == 0 ? NSOffState : NSOnState)];
}

- (IBAction)preferencesOK:(id)sender {
    if ([preferencesController validatePreferences:_serverPort]) {
        NSRunAlertPanel(@"Please restart CyTV", @"Changes to the secure server port will be applied when the secure server is (re)enabled.", nil, nil, nil);
        [preferencesWindow orderOut:nil];
    }
}

@end
