//
// $Id: ServerController.m,v 1.10 1997/10/31 04:52:07 nygard Exp $
//

//
//  This file is a part of Empire, a game of exploration and conquest.
//  Copyright (C) 1996  Steve Nygard
//
//  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., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//  You may contact the author by:
//     e-mail:  nygard@telusplanet.net
//

#import "Empire.h"

RCSID ("$Id: ServerController.m,v 1.10 1997/10/31 04:52:07 nygard Exp $");

#import "ServerController.h"

#import "Client.h"
#import <Foundation/NSProtocolChecker.h>
#import <Foundation/NSDistantObject.h>

//======================================================================
// The ServerController provides a window for interactively establishing
// and disconnecting the named port of the server.  It also lists the
// clients that are currently connected, and provides a textview for
// reporting status messages.
//
// A client can be "pinged" by double clicking its row in the table.
//
// This also provides notification to observers when clients are added
// and removed.  This is used, for example, by the NewGameController to
// provide popup lists of the available remote hosts.
//======================================================================

#define ServerController_VERSION 1

@implementation ServerController

+ (void) initialize
{
    if (self == [ServerController class])
    {
        [self setVersion:ServerController_VERSION];
    }
}

//----------------------------------------------------------------------

- (void) awakeFromNib
{
    [serverWindow setTitle:[NSString stringWithFormat:@"Server  %c  %@", 208, [[NSHost currentHost] name]]];

    // There doesn't appear to be a way of setting this in InterfaceBuilder.
    [clientTable setDoubleAction:@selector (doubleAction:)];
    [clientTable setTarget:self];
}

//----------------------------------------------------------------------

- init
{
    NSString *nibFile;
    BOOL loaded;

    [super init];

    nibFile = @"ServerWindow.nib";
    loaded = [NSBundle loadNibNamed:nibFile owner:self];
    if (loaded == NO)
    {
        NSLog (@"Could not load %@.", nibFile);
        [super dealloc];
        return nil;
    }

    serverConnection = nil;
    clientList = [[NSMutableArray array] retain];

    observers = [[NSMutableArray array] retain];

    return self;
}

//----------------------------------------------------------------------

- (void) dealloc
{
    SNRelease (clientList);

    // If this were really deallocated, we might want to notify the
    // remaining observers that we are going away, so that they don't
    // try detaching.

    SNRelease (observers);
    SNRelease (serverConnection);

    [super dealloc];
}

//----------------------------------------------------------------------

- (void) showPanel
{
    [serverWindow makeKeyAndOrderFront:self];
}

//----------------------------------------------------------------------

- (void) establishServer:sender
{
    NSProtocolChecker *protocolChecker;
    BOOL success;

    NSAssert (serverConnection == nil, @"Server already running");

    if (serverConnection != nil )
    {
        [self showText:@"The server is already running."];
        return;
    }

    protocolChecker = [NSProtocolChecker protocolCheckerWithTarget:self
                                         protocol:@protocol (EmpireServerConnectionProtocol)];

    NSAssert (protocolChecker != nil, @"A protocol checker could not be allocated.");

    serverConnection = [[NSConnection alloc] init];
    NSAssert (serverConnection != nil, @"Could not allocate server connection.");

    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector (connectionDidDie:)
                                          name:NSConnectionDidDieNotification
                                          object:serverConnection];

    [serverConnection setRootObject:protocolChecker];

    success = [serverConnection registerName:SERVER_PORT_NAME];
    NSAssert (success == YES, @"Could not name server connection.");

    [statusTextfield setStringValue:@"Running..."];
    [serverConnection setDelegate:self];
}

//----------------------------------------------------------------------

- (void) stopServer:sender
{
    NSAssert (serverConnection != nil, @"Server not running.");

    [serverConnection registerName:nil];
    [serverConnection invalidate];

    SNRelease (serverConnection);
    
    [statusTextfield setStringValue:@"Stopped."];
}

//----------------------------------------------------------------------

- (void) doubleAction:sender
{
    int row = [sender selectedRow];

    if (row >= 0 && row < [clientList count])
    {
        NS_DURING
            {
                [[[clientList objectAtIndex:row] client] ping];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
}

//----------------------------------------------------------------------

- (void) showText:(NSString *)format, ...
{
    NSMutableString *message;
    NSRange selected;
    va_list ap;

    va_start(ap, format);
    message = [[[NSMutableString alloc] initWithFormat:format arguments:ap] autorelease];
    [message appendString:@"\n"];
    va_end(ap);

    [messageText selectAll:nil];
    selected = [messageText selectedRange];
    selected.location = selected.length;
    selected.length = 0;
    [messageText setSelectedRange:selected];
    [messageText replaceCharactersInRange:selected withString:message];
    [messageText scrollRangeToVisible:selected];
}

//----------------------------------------------------------------------

- (void) connectionDidDie:(NSNotification *)notification
{
    id object;
    Client *client;
    NSEnumerator *clientEnumerator;
    int l = 0;

    object = [notification object];
    
    clientEnumerator = [clientList objectEnumerator];
    while (client = [clientEnumerator nextObject])
    {
        if ([client clientConnection] == object)
        {
            [clientList removeObjectAtIndex:l];
            [self removedClientHostNumber:l];
            [clientTable reloadData];
            break;
        }

        l++;
    }

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                          name:nil
                                          object:object];
}

//----------------------------------------------------------------------

- (BOOL) connection:(NSConnection *)parentConnection shouldMakeNewConnection:(NSConnection *)newConnection
{
    return YES;
}

//----------------------------------------------------------------------

- (Client *) clientAtIndex:(int)index
{
    return [clientList objectAtIndex:index];
}

//======================================================================
// EmpireServerConnectionProtocol
//======================================================================

- (void) ping
{
    [self showText:@"We were pinged."];
}

//----------------------------------------------------------------------
// I can't seem to get it to accept (id <EmpireClientConnectionProtocol>) as the type,
// since it will complain that -connectionForProxy isn't part of the protocol!
//----------------------------------------------------------------------

- (void) client:clientController onHost:(NSString *)hostname
{
    Client *client;

    [self showText:@"Client hostname is %@", hostname];
    
    [clientController setProtocolForProxy:@protocol (EmpireClientConnectionProtocol)];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector (connectionDidDie:)
                                          name:NSConnectionDidDieNotification
                                          object:[clientController connectionForProxy]];

    client = [[[Client alloc] initWithClient:clientController fromHost:hostname] autorelease];

    [clientList addObject:client];
    [self newClientHost:[client numberedHostname]];
    [clientTable reloadData];
}

//----------------------------------------------------------------------

- (void) aboutToDisconnect:sender
{
    [[sender connectionForProxy] invalidate];
}

//======================================================================
// NSTableDataSource
//======================================================================

- (int) numberOfRowsInTableView:(NSTableView *)aTableView
{
    return [clientList count];
}

//----------------------------------------------------------------------

- tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    NSString *identifier;
    id value;

    value = nil;
    identifier = [aTableColumn identifier];
    
    if ([identifier isEqualToString:@"Client"] == YES)
    {
        value = [[clientList objectAtIndex:rowIndex] hostname];
    }
    else if ([identifier isEqualToString:@"Index"] == YES)
    {
        value = [NSNumber numberWithInt:rowIndex + 1];
    }
    else if ([identifier isEqualToString:@"Identifier"] == YES)
    {
        value = [NSNumber numberWithUnsignedLong:(unsigned long)[clientList objectAtIndex:rowIndex]];
    }

    return value;
}

//======================================================================
// ObserverPattern
//======================================================================

- (void) attach:observer
{
    [observers addObject:observer];

    if ([observer respondsToSelector:@selector (newClientHost:)] == YES)
    {
        NSEnumerator *enumerator;
        id object;

        enumerator = [clientList objectEnumerator];
        while (object = [enumerator nextObject])
        {
            [observer newClientHost:[object numberedHostname]];
        }
    }
}

//----------------------------------------------------------------------

- (void) detach:observer
{
    int l, count;
    
    [observers removeObject:observer];

    if ([observer respondsToSelector:@selector (removedClientHostNumber:)] == YES)
    {
        count = [observers count];
        
        for (l = count - 1; l >= 0; l--)
            [observer removedClientHostNumber:l];
    }
}

//----------------------------------------------------------------------

- (void) newClientHost:(NSString *)hostname
{
    NSEnumerator *enumerator;
    id observer;

    enumerator = [observers objectEnumerator];
    while (observer = [enumerator nextObject])
    {
        if ([observer respondsToSelector:@selector (newClientHost:)] == YES)
            [observer newClientHost:hostname];
    }
}

//----------------------------------------------------------------------

- (void) removedClientHostNumber:(int)index
{
    NSEnumerator *enumerator;
    id observer;

    enumerator = [observers objectEnumerator];
    while (observer = [enumerator nextObject])
    {
        if ([observer respondsToSelector:@selector (removedClientHostNumber:)] == YES)
            [observer removedClientHostNumber:index];
    }
}

@end
