
/*******************************************************************************/
/* Copyright (C) 2010 Jonathan Moore Liles                                     */
/*                                                                             */
/* 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; see the file COPYING.  If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
/*******************************************************************************/

#define __MODULE__ "nsmd" 

#include "debug.h"

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <errno.h>
#include <string.h>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#include <libgen.h>
#include <dirent.h>
#include <ftw.h>
#include <list>
#include <getopt.h>
#include <sys/time.h>

#include <OSC/Endpoint.H>
/* for locking */
#include "file.h"

#include <map>
#include <string>
#include <algorithm>

#pragma GCC diagnostic ignored "-Wunused-parameter"

static OSC::Endpoint *osc_server;
static lo_address gui_addr;
static bool gui_is_active = false;
static int signal_fd;

static int session_lock_fd = 0;
static char *session_root;

#define NSM_API_VERSION_MAJOR 1
#define NSM_API_VERSION_MINOR 0

#define ERR_OK 0
#define ERR_GENERAL_ERROR    -1
#define ERR_INCOMPATIBLE_API -2
#define ERR_BLACKLISTED      -3
#define ERR_LAUNCH_FAILED    -4
#define ERR_NO_SUCH_FILE     -5
#define ERR_NO_SESSION_OPEN  -6
#define ERR_UNSAVED_CHANGES  -7
#define ERR_NOT_NOW          -8
#define ERR_BAD_PROJECT      -9
#define ERR_CREATE_FAILED    -10
#define ERR_SESSION_LOCKED   -11
#define ERR_OPERATION_PENDING -12

#define APP_TITLE "Non Session Manager"

enum {
    COMMAND_NONE = 0,
    COMMAND_QUIT,
    COMMAND_KILL,
    COMMAND_SAVE,
    COMMAND_OPEN,
    COMMAND_START,

    COMMAND_CLOSE,
    COMMAND_DUPLICATE,
    COMMAND_NEW
};

static int pending_operation = COMMAND_NONE;

static void wait ( long );


#define GUIMSG( fmt, args... ) \
{ \
    MESSAGE( fmt, ## args ); \
    if ( gui_is_active ) \
    { \
        char *s;\
        asprintf( &s, fmt, ## args );\
        osc_server->send( gui_addr, "/nsm/gui/server/message", s);\
        free(s);\
    }\
}

struct Client
{
private:

    int _reply_errcode;
    char *_reply_message;

    int _pending_command;                /*  */
    struct timeval _command_sent_time;

    bool _gui_visible;

    char *_label;

public:

    lo_address addr;                   /*  */
    char *name;                        /* client application name */
    char *executable_path;             /* path to client executable */
    int pid;                           /* PID of client process */
    float progress;                    /*  */
    bool active;              /* client has registered via announce */
//    bool stopped; /* the client quit, but not because we told it to--user still has to decide to remove it from the session */
    char *client_id;                                            /* short part of client ID */
    char *capabilities;                                         /* client capabilities... will be null for dumb clients */
    bool dirty;                                                 /* flag for client self-reported dirtiness */
    bool pre_existing;
    const char *status;

    const char *label ( void ) const { return _label; }
    void label ( const char *l )
        {
            if ( _label ) 
                free( _label );
            if ( l )
                _label = strdup( l ); 
            else
                _label = NULL;
        }
        
    bool gui_visible ( void ) const
        {
            return _gui_visible;
        }

    void gui_visible ( bool b ) 
        {
            _gui_visible = b;
        }

    bool
    has_error ( void ) const
        {
            return _reply_errcode != 0;
        }

    int
    error_code ( void ) const
        {
            return _reply_errcode;
        }

    const char * message ( void )
        {
            return _reply_message;
        }

    void
    set_reply ( int errcode, const char *message )
        {
            if ( _reply_message )
                free( _reply_message );

            _reply_message = strdup( message );
            _reply_errcode = errcode;
        }

    bool reply_pending ( void )
        {
            return _pending_command != COMMAND_NONE;
        }

    bool is_dumb_client ( void )
        {
            return capabilities == NULL;
        }

    void pending_command ( int command )
        {
            gettimeofday( &_command_sent_time, NULL );
            _pending_command = command;
        }
    
    double milliseconds_since_last_command ( void ) const
        {
            struct timeval now;

            gettimeofday( &now, NULL );

            double elapsedms = ( now.tv_sec - _command_sent_time.tv_sec ) * 1000.0;
            elapsedms += ( now.tv_usec - _command_sent_time.tv_usec ) / 1000.0;

            return elapsedms;
        }

    int pending_command ( void )
        {
            return _pending_command;
        }

// capability should be enclosed in colons. I.e. ":switch:"
    bool 
    is_capable_of ( const char *capability ) const
        {
            return capabilities &&
                strstr( capabilities, capability );
        }

    Client ( )
        {
            _label = 0;
            _gui_visible = true;
            addr = 0;
            _reply_errcode = 0;
            _reply_message = 0;
            pid = 0;
            progress = -0;
            _pending_command = 0;
            active = false;
            client_id = 0;
            capabilities = 0;
            name = 0;
            executable_path = 0;
            pre_existing = false;
        }

    ~Client ( ) 
        {
            if ( name )
                free(name);
            if (executable_path)
                free(executable_path);
            if (client_id)
                free(client_id);
            if (capabilities)
                free(capabilities);

            name = executable_path = client_id = capabilities = NULL;
        }
};


static std::list< Client* > client;

/* helper macros for defining OSC handlers */
#define OSC_NAME( name ) osc_ ## name
// #define OSCDMSG() DMESSAGE( "Got OSC message: %s", path );
#define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )

static char *session_path = NULL;
static char *session_name = NULL;

bool
clients_have_errors ( )
{
    for ( std::list<Client*>::const_iterator i = client.begin();
          i != client.end();
          ++i )
        if ( (*i)->active && (*i)->has_error() )
            return true;

    return false;
}

Client *
get_client_by_pid ( int pid )
{
    std::list<Client*> *cl = &client;

    for ( std::list<Client*>::const_iterator i = cl->begin();
          i != cl->end();
          ++i )
        if ( (*i)->pid == pid )
            return *i;
    
    return NULL;
}

void clear_clients ( void )
{
    std::list<Client*> *cl = &client;

    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        delete *i;
        i = cl->erase( i );
    }
}


void
handle_client_process_death ( int pid )
{
    Client *c = get_client_by_pid( (int)pid );
        
    if ( c )
    {
        bool dead_because_we_said = ( c->pending_command() == COMMAND_KILL ||
                                      c->pending_command() == COMMAND_QUIT );

        if ( dead_because_we_said )
        {
            GUIMSG( "Client %s terminated because we told it to.", c->name );
        }
        else
        {
            GUIMSG( "Client %s died unexpectedly.", c->name );
        }

        if ( c->pending_command() == COMMAND_QUIT )
        {
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );

            client.remove(c);
            delete c;
        }
        else
        {
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" );
        }
      
        c->pending_command( COMMAND_NONE );
            
        c->active = false;
        c->pid = 0;
     }
}

void handle_sigchld ( )
{
    for ( ;; )
    {
        int status;
        pid_t pid = waitpid(-1, &status, WNOHANG);

        if (pid <= 0) 
            break;
        
        handle_client_process_death( pid );
    }
}


int
path_is_valid ( const char *path )
{
    char *s;

    asprintf( &s, "/%s/", path );

    int r = strstr( s, "/../" ) == NULL;

    free( s );

    return r;
}

int
mkpath ( const char *path, bool create_final_directory )
{
    char *p = strdup( path );

    char *i = p + 1;

    while ( ( i = index( i, '/' ) ) )
    {
        *i = 0;

        struct stat st;

        if ( stat( p, &st ) )
        {
            if ( mkdir( p, 0711 ) )
            {
                free( p );
                return -1;
            }
        }

        *i = '/';
        i++;
    } 

    if ( create_final_directory )
    {
        if ( mkdir( p, 0711 ) )
        {
            free( p );
            return -1;
        }
    }
    
    free( p );

    return 0;
}

void
set_name ( const char *name )
{
    if ( session_name )
        free( session_name );

    char *s = strdup( name );
    
    session_name = strdup( basename( s ) );

    free( s );
}

bool
address_matches ( lo_address addr1, lo_address addr2 )
{
    /* char *url1 = lo_address_get_url( addr1 ); */
    /* char *url2 = lo_address_get_url( addr2 ); */

    char *url1 = strdup( lo_address_get_port( addr1 ) );
    char *url2 = strdup(lo_address_get_port( addr2 ) );

    bool r = !strcmp( url1, url2 );

    free( url1 );
    free( url2 );

    return r;
}
 
Client *
get_client_by_id ( std::list<Client*> *cl, const char *id )
{
    for ( std::list<Client*>::const_iterator i = cl->begin();
          i != cl->end();
          ++i )
        if ( !strcmp( (*i)->client_id, id ) )
            return *i;

    return NULL;
}


Client *
get_client_by_name_and_id ( std::list<Client*> *cl, const char *name, const char *id )
{
    for ( std::list<Client*>::const_iterator i = cl->begin();
          i != cl->end();
          ++i )
        if ( !strcmp( (*i)->client_id, id ) && 
             ! strcmp( (*i)->name, name ) )
            return *i;

    return NULL;
}

Client *
get_client_by_address ( lo_address addr )
{
    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
        if ( (*i)->addr && address_matches( (*i)->addr, addr ) )
            return *i;

    return NULL;
}


char *
generate_client_id ( Client *c ) 
{
    char id_str[6];

    id_str[0] = 'n';
    id_str[5] = 0;

    for ( int i = 1; i < 5; i++)
        id_str[i] = 'A' + (rand() % 25);

    return strdup(id_str);
}


bool
replies_still_pending ( void )
{
    for ( std::list<Client*>::const_iterator i = client.begin();
          i != client.end();
          ++i )
        if ( (*i)->active && (*i)->reply_pending() )
            return true;

    return false;
}

int
number_of_active_clients ( void )
{
    int active = 0;
    for ( std::list<Client*>::const_iterator i = client.begin(); i != client.end(); i++ )
    {
        if ( (*i)->active )
            active++;
    }

    return active;
}

void
wait_for_announce ( void )
{
    GUIMSG( "Waiting for announce messages from clients" );

    int n = 5 * 1000;
    
    long unsigned int active;

    while ( n > 0 )
    {
        n -= 100;

        wait(100);
        
        active = number_of_active_clients();

        if ( client.size() == active )
            break;
    }

    GUIMSG( "Done. %lu out of %lu clients announced within the initialization grace period", active, client.size() );
}

void
wait_for_replies ( void )
{    

    GUIMSG( "Waiting for clients to reply to commands" );

    int n = 60 * 1000;                                          /* 60 seconds */

    while ( n )
    {
        n -= 100;

        wait(100);
        
        if ( ! replies_still_pending() )
            break;
    }

    GUIMSG( "Done waiting" );
    /* FIXME: do something about unresponsive clients */
}


char * 
get_client_project_path  ( const char *session_path, Client *c )
{
    char *client_project_path;

    asprintf( &client_project_path, "%s/%s.%s", session_path, c->name, c->client_id );

    return client_project_path;
}

bool
launch ( const char *executable, const char *client_id )
{
    Client *c;

    if ( !client_id || !( c = get_client_by_id( &client, client_id ) ) )
    {
        c = new Client();

        c->executable_path = strdup( executable );

        {
            char *s = strdup( c->executable_path );
        
            c->name = strdup( basename( s ) );
        
            free( s );
        }
    
        if ( client_id )
            c->client_id = strdup( client_id );
        else
            c->client_id = generate_client_id( c );

        client.push_back( c );
    }

    char * url = osc_server->url();

    int pid;
    if ( ! (pid = fork()) )
    {
        GUIMSG( "Launching %s", executable );

        char *args[] = { strdup( executable ), NULL };

        setenv( "NSM_URL", url, 1 );
        
        if ( -1 == execvp( executable, args ) )
        {
            WARNING( "Error starting process: %s", strerror( errno ) );

            exit(-1);
        }
    }

    c->pending_command( COMMAND_START );
    c->pid = pid;

    MESSAGE( "Process has pid: %i", pid );

    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "launch" );
    }

    return true;
}

void
command_client_to_save ( Client *c )
{
    if ( c->active )
    {
        MESSAGE( "Telling %s to save", c->name );
        osc_server->send( c->addr, "/nsm/client/save" );
        
        c->pending_command( COMMAND_SAVE );

        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "save" );
    }
    else if ( c->is_dumb_client() && c->pid )
    {
        // this is a dumb client... 
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "noop" );
    }
}

void command_client_to_switch ( Client *c, const char *new_client_id )
{
    char *old_client_id = c->client_id;

    c->client_id = strdup( new_client_id );
 
    char *client_project_path = get_client_project_path( session_path, c ); 
    
    MESSAGE( "Commanding %s to switch \"%s\"", c->name, client_project_path );
                
    char *full_client_id;
    asprintf( &full_client_id, "%s.%s", c->name, c->client_id );

    osc_server->send( c->addr, "/nsm/client/open", client_project_path, session_name, full_client_id );
    
    free( full_client_id );
    free( client_project_path );
    
    c->pending_command( COMMAND_OPEN );

    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "switch" );
        osc_server->send( gui_addr, "/nsm/gui/client/switch", old_client_id, c->client_id );
    }

    free( old_client_id );
}

void
purge_inactive_clients ( )
{
    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! (*i)->active )
        {
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", (*i)->client_id, (*i)->status = "removed" );

            delete *i;

            i = client.erase( i );
        }
    }
}

bool
process_is_running ( int pid )
{
    if ( 0 == kill( pid, 0 ) )
    {
        return true;
    }
    else if ( ESRCH == errno )
    {
        return false;
    }

    return false;
}

void
purge_dead_clients ( )
{
    std::list<Client*> tmp( client );

    for ( std::list<Client*>::const_iterator i = tmp.begin();
          i != tmp.end();
          ++i )
    {
        const Client *c = *i;
        if ( c->pid )
        {
            if ( ! process_is_running( c->pid ) )
                handle_client_process_death( c->pid );
        }
    }
}

/************************/
/* OSC Message Handlers */
/************************/

OSC_HANDLER( add )
{
    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "Cannot add to session because no session is loaded." );
                          

        return 0;
    }

    if ( strchr( &argv[0]->s, '/' ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_LAUNCH_FAILED,
                          "Absolute paths are not permitted. Clients must be in $PATH" );        
        return 0;
    }

    if ( ! launch( &argv[0]->s, NULL ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_LAUNCH_FAILED,
                          "Failed to launch process!" );
    }
    else
    {
        osc_server->send( lo_message_get_source( msg ), "/reply", path,
                          ERR_OK,
                          "Launched." );
    }
 
    return 0;
}

OSC_HANDLER( announce )
{
    const char *client_name = &argv[0]->s;
    const char *capabilities = &argv[1]->s;
    const char *executable_path = &argv[2]->s;
    int major = argv[3]->i;
    int minor = argv[4]->i;
    int pid = argv[5]->i;

    GUIMSG( "Got announce from %s", client_name );

    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error",
                          path,
                          ERR_NO_SESSION_OPEN,
                          "Sorry, but there's no session open for this application to join." );
        return 0;
    }

    bool expected_client = false;

    Client *c = NULL;

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! strcmp( (*i)->executable_path, executable_path ) &&
             ! (*i)->active &&
             (*i)->pending_command() == COMMAND_START )
        {
            // I think we've found the slot we were looking for.
            MESSAGE( "Client was expected." );
            c = *i;
            break;
        }
    } 
    
    if ( ! c )
    {
        c = new Client();
        c->executable_path = strdup( executable_path );
        c->client_id = generate_client_id( c );
    }
    else
        expected_client = true;
   
    if ( major > NSM_API_VERSION_MAJOR )
    {
        MESSAGE( "Client is using incompatible and more recent API version %i.%i", major, minor ); 

        osc_server->send( lo_message_get_source( msg ), "/error",
                          path,
                          ERR_INCOMPATIBLE_API,
                          "Server is using an incompatible API version." );

        return 0;
    }

    c->pid = pid;
    c->capabilities = strdup( capabilities );
    c->addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
    c->name = strdup( client_name );
    c->active = true;

    MESSAGE( "Process has pid: %i", pid );
    
    if ( ! expected_client )
        client.push_back( c );

    MESSAGE( "The client \"%s\" at \"%s\" informs us it's ready to receive commands.", &argv[0]->s, lo_address_get_url( c->addr ) );

    osc_server->send( lo_message_get_source( msg ), "/reply",
                      path,
                      expected_client ? 
                      "Howdy, what took you so long?" :
                      "Well hello, stranger. Welcome to the party.",
                      APP_TITLE,
                      ":server-control:broadcast:optional-gui:" );

    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "open" );

        if ( c->is_capable_of( ":optional-gui:" ) )
            osc_server->send( gui_addr, "/nsm/gui/client/has_optional_gui", c->client_id );
    }

    {
        char *full_client_id;
        asprintf( &full_client_id, "%s.%s", c->name, c->client_id );
        
        char *client_project_path = get_client_project_path( session_path, c );
        
        osc_server->send( lo_message_get_source( msg ), "/nsm/client/open", client_project_path, session_name, full_client_id );
        
        c->pending_command( COMMAND_OPEN );

        free( full_client_id );
        free( client_project_path );
    }

    return 0;
}

void
save_session_file ( )
{
    char *session_file = NULL;
    asprintf( &session_file, "%s/session.nsm", session_path );
  
    FILE *fp = fopen( session_file, "w+" );

    /* FIXME: handle errors. */

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        fprintf( fp, "%s:%s:%s\n", (*i)->name, (*i)->executable_path, (*i)->client_id );
    } 

    fclose( fp );
}

Client *
client_by_name ( const char *name,
                 std::list<Client*> *cl )
{
    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        if ( !strcmp( name, (*i)->name ) )
            return *i;
    }

    return NULL;
}

bool
dumb_clients_are_alive ( )
{
    std::list<Client*> *cl = &client;
    
    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        if ( (*i)->is_dumb_client() && (*i)->pid > 0 )
            return true;
    }

    return false;
}

void
wait_for_dumb_clients_to_die ( )
{
    struct signalfd_siginfo fdsi;
    
    GUIMSG( "Waiting for any dumb clients to die." );
    
    for ( int i = 0; i < 6; i++ )
    {
        MESSAGE( "Loop %i", i );

        if ( ! dumb_clients_are_alive() )
            break;
        
        ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
        
        if (s == sizeof(struct signalfd_siginfo))
        {
            if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
        }
        
        usleep( 50000 );
    }
    
    GUIMSG( "Done waiting" );

    /* FIXME: give up on remaining clients and purge them */
}


bool
killed_clients_are_alive ( )
{
    std::list<Client*> *cl = &client;
    
    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        if ( ( (*i)->pending_command() == COMMAND_QUIT ||
               (*i)->pending_command() == COMMAND_KILL ) &&
             (*i)->pid > 0 )
            return true;
    }

    return false;
}

void
wait_for_killed_clients_to_die ( )
{
    struct signalfd_siginfo fdsi;
    
    MESSAGE( "Waiting for killed clients to die." );
    
    for ( int i = 0; i < 60; i++ )
    {
        MESSAGE( "Loop %i", i );

        if ( ! killed_clients_are_alive() )
            goto done;
        
        ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
        
        if (s == sizeof(struct signalfd_siginfo))
        {
            if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
        }
        
        purge_dead_clients();

        /* check OSC so we can get /progress messages. */
        osc_server->check();

        sleep(1);
    }

    WARNING( "Killed clients are still alive" );
    
    return;

done:

    MESSAGE( "All clients have died." );
}


void
command_all_clients_to_save ( )
{
    if ( session_path )
    {
        GUIMSG( "Commanding attached clients to save." );
        
        for ( std::list<Client*>::iterator i = client.begin();
              i != client.end();
              ++i )
        {
            command_client_to_save( *i );
        }
    
        wait_for_replies();
    
        save_session_file();
    }
}

void
command_client_to_stop ( Client *c )
{
    GUIMSG( "Stopping client %s", c->name );

    if ( c->pid > 0 )
    {
        c->pending_command( COMMAND_KILL );

        kill( c->pid, SIGTERM );

        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" );
    }
}

void
command_client_to_quit ( Client *c )
{
    MESSAGE( "Commanding %s to quit", c->name );
    
    if ( c->active )
    {
        c->pending_command( COMMAND_QUIT );

        kill( c->pid, SIGTERM );

        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "quit" );
    }
    else if ( c->is_dumb_client() )
    {
        if ( c->pid > 0 )
        {
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "quit" );
            
            /* should be kill? */
            c->pending_command( COMMAND_QUIT );
    
            // this is a dumb client... try and kill it
            kill( c->pid, SIGTERM );
        }
        else
        {
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
        }
    }
}

void
close_session ( )
{
    if ( ! session_path )
        return;

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        command_client_to_quit( *i );
    }

    wait_for_killed_clients_to_die();

    purge_inactive_clients();
    
    clear_clients();

    if ( session_path )
    {
        char *session_lock;
        asprintf( &session_lock, "%s/.lock", session_path );

        release_lock( &session_lock_fd, session_lock );

        free(session_lock);

        free(session_path);
        session_path = NULL;
        free(session_name);
        session_name = NULL;
    }

    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/session/name", "", "" );
    }
}

void
tell_client_session_is_loaded( Client *c )
{
    if ( c->active )
//!c->is_dumb_client() )
    {
        MESSAGE( "Telling client %s that session is loaded.", c->name );
        osc_server->send( c->addr, "/nsm/client/session_is_loaded" );
    }
}

void
tell_all_clients_session_is_loaded ( void )
{
    MESSAGE( "Telling all clients that session is loaded..." );

   for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        tell_client_session_is_loaded( *i );
    }
}

int
load_session_file ( const char * path )
{
    char *session_file = NULL;
    asprintf( &session_file, "%s/session.nsm", path );
    char *session_lock = NULL;
    asprintf( &session_lock, "%s/.lock", path );
    
    if ( ! acquire_lock( &session_lock_fd, session_lock ) )
    {
        free( session_file );
        free( session_lock );

        WARNING( "Session is locked by another process" );
        return ERR_SESSION_LOCKED;
    }


    FILE *fp;

    if ( ! ( fp = fopen( session_file, "r" ) ) )
    {
        free( session_file );
        return ERR_CREATE_FAILED;
    }

    session_path = strdup( path );

    set_name( path );

    std::list<Client*> new_clients;

    {
        char * client_name = NULL;
        char * client_executable = NULL;
        char * client_id = NULL;
        
        // load the client list
        while ( fscanf( fp, "%a[^:]:%a[^:]:%a[^:\n]\n", &client_name, &client_executable, &client_id ) > 0 )
        {
            Client *c = new Client();
            
            c->name = client_name;
            c->executable_path = client_executable;
            c->client_id =  client_id;
            
            new_clients.push_back( c );
        }
    }

    MESSAGE( "Commanding unneeded and dumb clients to quit" );
    
    std::map<std::string,int> client_map;

    /* count how many instances of each client are needed in the new session */
    for ( std::list<Client*>::iterator i = new_clients.begin();
          i != new_clients.end();
          ++i )
    {
        if ( client_map.find( (*i)->name) != client_map.end() )
            client_map[(*i)->name]++;
        else
            client_map[(*i)->name] = 1;
    }

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! (*i)->is_capable_of( ":switch:" ) || client_map.find((*i)->name ) == client_map.end()  )
        {
            /* client is not capable of switch, or is not wanted in the new session */
            command_client_to_quit( *i );
        }
        else
        {
            /* client is switch capable and may be wanted in the new session */
            if ( client_map[ (*i)->name ]-- <= 0 )
                /* nope,, we already have as many as we need, stop this one */
                command_client_to_quit( *i );
        }
        
    }

//    wait_for_replies();

    wait_for_killed_clients_to_die();

//    wait_for_dumb_clients_to_die();

    purge_inactive_clients();

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        (*i)->pre_existing = true;
    }    

    MESSAGE( "Commanding smart clients to switch" );

    for ( std::list<Client*>::iterator i = new_clients.begin();
          i != new_clients.end();
          ++i )
    {
        Client *c = NULL;
            
        /* in a duplicated session, clients will have the same
         * IDs, so be sure to pick the right one to avoid race
         * conditions in JACK name registration. */
        c = get_client_by_name_and_id( &client, (*i)->name, (*i)->client_id );
        
        if ( ! c )
            c = client_by_name( (*i)->name, &client );
        
        if ( c && c->pre_existing && !c->reply_pending() )
        {
            // since we already shutdown clients not capable of 'switch', we can assume that these are.
            command_client_to_switch( c, (*i)->client_id );
        }
        else
        {
            /* sleep a little bit because liblo derives its sequence
             * of port numbers from the system time (second
             * resolution) and if too many clients start at once they
             * won't be able to find a free port. */
            usleep( 100 * 1000 );

            launch( (*i)->executable_path, (*i)->client_id );
        }
    }

    /* this part is a little tricky... the clients need some time to
     * send their 'announce' messages before we can send them 'open'
     * and know that a reply is pending and we should continue waiting
     * until they finish. wait_for_replies() must check for OSC
     * messages immediately, even if no replies seem to be pending
     * yet. */

    /* dumb clients will never send an 'announce message', so we need
     * to give up waiting on them fairly soon. */

    wait_for_announce();

    wait_for_replies();

    tell_all_clients_session_is_loaded();

    MESSAGE( "Loaded." );

    new_clients.clear();

    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/session/name", session_name, session_path + strlen( session_root ));
    }
    
    return ERR_OK;
}

OSC_HANDLER( save )
{
    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path, 
                          ERR_NO_SESSION_OPEN,
                          "No session to save.");
                          
        goto done;
    }
    
    command_all_clients_to_save();

    MESSAGE( "Done." );

    osc_server->send( lo_message_get_source( msg ), "/reply", path, "Saved." );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}

OSC_HANDLER( duplicate )
{
    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_DUPLICATE;

    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path, 
                          ERR_NO_SESSION_OPEN,
                          "No session to duplicate.");
        goto done;
    }

    if ( ! path_is_valid( &argv[0]->s ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_CREATE_FAILED,
                          "Invalid session name." );

        goto done;
    }     

    command_all_clients_to_save();
    
    if ( clients_have_errors() )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_GENERAL_ERROR,
                          "Some clients could not save" );

        goto done;
    }

//    save_session_file();

    char *spath;
    asprintf( &spath, "%s/%s", session_root, &argv[0]->s );

    mkpath( spath, false );

    /* FIXME: code a recursive copy instead of calling the shell */

    char *cmd;
    asprintf( &cmd, "cp -R \"%s\" \"%s\"", session_path, spath);

    system( cmd );

    free( cmd );

    osc_server->send( gui_addr,  "/nsm/gui/session/session", &argv[0]->s  );

    MESSAGE( "Attempting to open %s", spath );

    if ( !load_session_file( spath ) )
    {
        MESSAGE( "Loaded" );
        osc_server->send( lo_message_get_source( msg ), "/reply", path,
                          "Loaded." );
    }
    else
    {
        MESSAGE( "Failed" );
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_NO_SUCH_FILE,
                          "No such file." );
        free(spath);
        return -1;
    }

    free( spath );
 
    MESSAGE( "Done" );

    osc_server->send( lo_message_get_source( msg ), "/reply", path, "Duplicated." );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}

OSC_HANDLER( new )
{
    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_NEW;

    if ( ! path_is_valid( &argv[0]->s ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_CREATE_FAILED,
                          "Invalid session name." );

        pending_operation = COMMAND_NONE;

        return 0;
    }     

    if ( session_path )
    {
        command_all_clients_to_save();
        
        close_session();
    }

    GUIMSG( "Creating new session \"%s\"", &argv[0]->s );

    char *spath;
    asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
   
    if ( mkpath( spath, true ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_CREATE_FAILED,
                          "Could not create the session directory" );

        free(spath);

        pending_operation = COMMAND_NONE;

        return 0;
    }

    session_path = strdup( spath );
    
    set_name( session_path );

    osc_server->send( lo_message_get_source( msg ), "/reply", path, "Created." );

    if ( gui_is_active )
    {
        osc_server->send( gui_addr,  "/nsm/gui/session/session", &argv[0]->s  );

        osc_server->send( gui_addr, "/nsm/gui/session/name", &argv[0]->s, &argv[0]->s );
    }

    save_session_file();

    free( spath );

    osc_server->send( lo_message_get_source( msg ), "/reply", path,
                              "Session created" );

    pending_operation = COMMAND_NONE;
        
    return 0;
}

static lo_address list_response_address;

int
list_file ( const char *fpath, const struct stat *sb, int tflag )
{
    char *s;

    if ( tflag == FTW_F )
    {
        s = strdup( fpath );
        if ( ! strcmp( "session.nsm", basename( s ) ) )
        {
            free( s );
            s = strdup( fpath );

            s = dirname( s );

            memmove( s, s + strlen( session_root ) + 1, (strlen( s ) - strlen( session_root )) + 1);

            osc_server->send( list_response_address, "/reply", "/nsm/server/list", s  );

            free( s );
        }
        else
            free( s );
    }

    return 0;
}

OSC_HANDLER( list )
{
    GUIMSG( "Listing sessions" );

    list_response_address = lo_message_get_source( msg );
    
    ftw( session_root, list_file, 20 );

    osc_server->send( lo_message_get_source( msg ), path,
                      ERR_OK,
                      "Done." );
    return 0;
}

OSC_HANDLER( open )
{
    GUIMSG( "Opening session %s", &argv[0]->s );
    
    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_OPEN;


    if ( session_path )
    {

        command_all_clients_to_save();
        
        if ( clients_have_errors() )
        {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_GENERAL_ERROR,
                              "Some clients could not save" );

            pending_operation = COMMAND_NONE;
            return 0;
        }
        
//        save_session_file();
    }


    char *spath;
    asprintf( &spath, "%s/%s", session_root, &argv[0]->s );

    MESSAGE( "Attempting to open %s", spath );

    int err = load_session_file( spath );
        
    if ( ! err )
    {
        MESSAGE( "Loaded" );
        osc_server->send( lo_message_get_source( msg ), "/reply", path,
                          "Loaded." );
    }
    else
    {
        MESSAGE( "Failed" );
        const char *m = NULL;
        switch ( err )
        {
            case ERR_CREATE_FAILED:
                m = "Could not create session file!";
                break;
            case ERR_SESSION_LOCKED:
                m = "Session is locked by another process!";
                break;
            case ERR_NO_SUCH_FILE:
                m = "The named session does not exist.";
                break;
            default:
                m = "Unknown error";
        }


        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          err,
                          m );
    }

    free( spath );
 
    MESSAGE( "Done" );

    pending_operation = COMMAND_NONE;

    return 0;
}


OSC_HANDLER( quit )
{
    close_session();

    exit(0);

    return 0;
}

OSC_HANDLER( abort )
{
  if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }
  
  pending_operation = COMMAND_CLOSE;


    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "No session to abort." );

        goto done;
    }

    GUIMSG( "Commanding attached clients to quit." );

    close_session();

    osc_server->send( lo_message_get_source( msg ), "/reply", path,
                      "Aborted." );
                      
    MESSAGE( "Done" );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}

OSC_HANDLER( close )
{
    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( lo_message_get_source( msg ), "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_CLOSE;

    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "No session to close." );

        goto done;
    }
    command_all_clients_to_save();

    GUIMSG( "Commanding attached clients to quit." );

    close_session();

    osc_server->send( lo_message_get_source( msg ), "/reply", path,
                      "Closed." );
                      
    MESSAGE( "Done" );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}


OSC_HANDLER( broadcast )
{
    const char *to_path = &argv[0]->s;

    /* don't allow clients to broadcast NSM commands */
    if ( ! strncmp( to_path, "/nsm/", strlen( "/nsm/" ) ) )
        return 0;

    std::list<OSC::OSC_Value> new_args;

    for ( int i = 1; i < argc; ++i )
    {
        switch ( types[i] )
        {
            case 's':
                new_args.push_back( OSC::OSC_String( &argv[i]->s ) );
                break;
            case 'i':
                new_args.push_back( OSC::OSC_Int( argv[i]->i ) );
                break;
            case 'f':
                new_args.push_back( OSC::OSC_Float( argv[i]->f ) );
                break;
        }
    }

    char *sender_url = lo_address_get_url( lo_message_get_source( msg ) );

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! (*i)->addr )
            continue;

        char *url = lo_address_get_url( (*i)->addr );

        if ( strcmp( sender_url, url ) )
        {
            osc_server->send( (*i)->addr, to_path, new_args );
        }

        free( url );
    }

    /* also relay to attached GUI so that the broadcast can be
     * propagated to another NSMD instance */
    if ( gui_is_active )
    {
        
        char *u1 = lo_address_get_url( gui_addr );
     
        if ( strcmp( u1, sender_url ) )
        {
            new_args.push_front( OSC::OSC_String( to_path ) );
            
            osc_server->send( gui_addr, path, new_args );
        }
        
        free(u1);
    }
    
    free( sender_url );

    return 0;
}



/*********************************/
/* Client Informational Messages */
/*********************************/

OSC_HANDLER( progress )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( c )
    {
        c->progress = argv[0]->f;

        /* MESSAGE( "%s progress: %i%%", c->name, (int)(c->progress * 100.0f) ); */

        if ( gui_is_active )
        {
            osc_server->send( gui_addr, "/nsm/gui/client/progress", c->client_id, (float)c->progress );
        }
    }

    return 0;
}

OSC_HANDLER( is_dirty )
{
    MESSAGE( "Client sends dirty" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );
    
    if ( ! c )
        return 0;

    c->dirty = 1;

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );

    return 0;
}

OSC_HANDLER( is_clean )
{
    MESSAGE( "Client sends clean" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->dirty = 0;

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );

    return 0;
}

OSC_HANDLER( gui_is_hidden )
{
    MESSAGE( "Client sends gui hidden" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->gui_visible( false );

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );

    return 0;
}

OSC_HANDLER( gui_is_shown )
{
    MESSAGE( "Client sends gui shown" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->gui_visible( true );

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );

    return 0;
}

OSC_HANDLER( message )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/message", c->client_id, argv[0]->i, &argv[1]->s );

    return 0;
}

OSC_HANDLER( label )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    if ( strcmp( types, "s" ) )
        return -1;
    
    c->label( &argv[0]->s );

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, &argv[0]->s );

    return 0;
}

/**********************/
/* Response Handlers  */
/**********************/


OSC_HANDLER( error )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
    {
        WARNING( "Error from unknown client" );
        return 0;
    }

//    const char *rpath = &argv[0]->s;

    int err_code = argv[1]->i;
    
    const char *message = &argv[2]->s;

    c->set_reply( err_code, message );

    MESSAGE( "Client \"%s\" replied with error: %s (%i) in %fms", c->name, message, err_code, c->milliseconds_since_last_command() ); 
    c->pending_command( COMMAND_NONE );
    
    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "error" );

    return 0;
}


OSC_HANDLER( reply )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

//    const char *rpath = &argv[0]->s;
    const char *message = &argv[1]->s;
    
    if ( c )
    {
        c->set_reply( ERR_OK, message );

        MESSAGE( "Client \"%s\" replied with: %s in %fms", c->name, message, c->milliseconds_since_last_command() ); 
        
        c->pending_command( COMMAND_NONE );
        
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "ready" );
    }
    else
        MESSAGE( "Reply from unknown client" );

    return 0;
}



/******************/
/* GUI operations */
/******************/


OSC_HANDLER( stop )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    if ( c )
    {
        command_client_to_stop( c );
        
        if ( gui_is_active )
            osc_server->send( gui_addr, "/reply", "Client stopped." );
    }
    else
    {
        if ( gui_is_active )
            osc_server->send( gui_addr, "/error", -10, "No such client." );
    }

   
    return 0;
}

OSC_HANDLER( remove )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    if ( c )
    {
        if ( c->pid == 0 &&
             ! c->active )
        {
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
            
            client.remove( c );
            
            delete c;

            if ( gui_is_active )
                osc_server->send( gui_addr, "/reply", "Client removed." );
        }
    }
    else
    {
        if ( gui_is_active )
            osc_server->send( gui_addr, "/error", -10, "No such client." );
    }

   
    return 0;
}

OSC_HANDLER( resume )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );
    
    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->pid == 0 &&
             ! c->active )
        {
            if ( ! launch( c->executable_path, c->client_id ) )
            {
                
            }
        }
    }
    
    return 0;
}


OSC_HANDLER( client_save )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->active )
        {
            command_client_to_save( c );
        }
    }

    return 0;
}

OSC_HANDLER( client_show_optional_gui )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->active )
        {
            osc_server->send( c->addr, "/nsm/client/show_optional_gui" );
        }
    }

    return 0;
}

OSC_HANDLER( client_hide_optional_gui )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->active )
        {
            osc_server->send( c->addr, "/nsm/client/hide_optional_gui" );
        }
    }

    return 0;
}

void
announce_gui( const char *url, bool is_reply )
{
    gui_addr = lo_address_new_from_url( url );
    gui_is_active = true;

    if ( is_reply )
        osc_server->send( gui_addr, "/nsm/gui/gui_announce", "hi" );
    else
        osc_server->send( gui_addr, "/nsm/gui/server_announce", "hi" );

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        Client *c = *i;

        osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );        
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );

    }

    osc_server->send( gui_addr, "/nsm/gui/session/name", session_name ? session_name : "", session_path ? session_path : "" );

    DMESSAGE( "Registered with GUI" );
}


OSC_HANDLER( gui_announce )
{
    announce_gui( lo_address_get_url( lo_message_get_source( msg ) ), true );

    return 0;
}

OSC_HANDLER( ping )
{
    osc_server->send( lo_message_get_source( msg ), "/reply", path );

    return 0;
}

OSC_HANDLER( null )
{
    WARNING( "Unrecognized message with type signature \"%s\" at path \"%s\"", types, path );

    return 0;
}



static void
wait ( long timeout )
{
    struct signalfd_siginfo fdsi;
    
    ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
    
    if (s == sizeof(struct signalfd_siginfo))
    {
        if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
    }
    
    osc_server->wait( timeout );
    
    purge_dead_clients();
}

int main(int argc, char *argv[])
{
    sigset_t mask;
    sigemptyset( &mask );
    sigaddset( &mask, SIGCHLD );
    sigprocmask(SIG_BLOCK, &mask, NULL );
    signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );

    /* generate random seed for client ids */
    {
        time_t seconds;
        time(&seconds);
        
        srand( (unsigned int) seconds );
    }

//    char *osc_port = "6666";
    char *osc_port = NULL;
    const char *gui_url = NULL;

    static struct option long_options[] = 
    {
        { "detach", no_argument, 0, 'd' },
        { "session-root", required_argument, 0, 's' },
        { "osc-port", required_argument, 0, 'p' },
        { "gui-url", required_argument, 0, 'g' },
        { "help", no_argument, 0, 'h' },
        { 0, 0, 0, 0 }
    };

    int option_index = 0;
    int c = 0;

    bool detach = false;

    while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index  ) ) != -1 )
    {
        switch ( c )
        {
            case 'd':
                detach = true;
                break;
            case 's':
            {
                session_root = optarg;

                /* get rid of trailing slash */
                char *s = rindex(session_root,'/');
                if ( s == &session_root[strlen(session_root) - 1] )
                    *s = '\0';
                break;
            }
            case 'p':
                DMESSAGE( "Using OSC port %s", optarg );
                osc_port = optarg;
                break;
            case 'g':                
                DMESSAGE( "Going to connect to GUI at: %s", optarg );
                gui_url = optarg;
                break;
            case 'h':
                printf( "Usage: %s [--osc-port portnum] [--session-root path]\n\n", argv[0] );
                exit(0);
                break;
        }
    }

    if ( !session_root )
        asprintf( &session_root, "%s/%s", getenv( "HOME" ), "NSM Sessions" );
  
    struct stat st;

    if ( stat( session_root, &st ) )
    {
        if ( mkdir( session_root, 0771 ) )
        {
            FATAL( "Failed to create session directory: %s", strerror( errno ) );
        }
    }

    MESSAGE( "Session root is: %s", session_root );

    osc_server = new OSC::Endpoint();

    if ( osc_server->init( LO_UDP, osc_port ) )
    {
        FATAL( "Failed to create OSC server." );
    }

    printf( "NSM_URL=%s\n", osc_server->url() );

    if ( gui_url )
    {
        announce_gui( gui_url, false );
    }

    /*  */
    osc_server->add_method( "/nsm/server/announce", "sssiii", OSC_NAME( announce ), NULL, "client_name,capabilities,executable,api_version_major,api_version_minor,client_pid" );

    /* response handlers */
    osc_server->add_method( "/reply", "ss", OSC_NAME( reply ), NULL, "err_code,msg" );
    osc_server->add_method( "/error", "sis", OSC_NAME( error ), NULL, "err_code,msg" );
  
    osc_server->add_method( "/nsm/client/progress", "f", OSC_NAME( progress ), NULL, "progress" );
    osc_server->add_method( "/nsm/client/is_dirty", "", OSC_NAME( is_dirty ), NULL, "dirtiness" );
    osc_server->add_method( "/nsm/client/is_clean", "", OSC_NAME( is_clean ), NULL, "dirtiness" );
    osc_server->add_method( "/nsm/client/message", "is", OSC_NAME( message ), NULL, "message" );
    osc_server->add_method( "/nsm/client/gui_is_hidden", "", OSC_NAME( gui_is_hidden ), NULL, "message" );
    osc_server->add_method( "/nsm/client/gui_is_shown", "", OSC_NAME( gui_is_shown ), NULL, "message" );
    osc_server->add_method( "/nsm/client/label", "s", OSC_NAME( label ), NULL, "message" );
    
    /*  */
    osc_server->add_method( "/nsm/gui/gui_announce", "", OSC_NAME( gui_announce ), NULL, "" );
    osc_server->add_method( "/nsm/gui/client/stop", "s", OSC_NAME( stop ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/remove", "s", OSC_NAME( remove ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/resume", "s", OSC_NAME( resume ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/save", "s", OSC_NAME( client_save ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/show_optional_gui", "s", OSC_NAME( client_show_optional_gui ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/hide_optional_gui", "s", OSC_NAME( client_hide_optional_gui ), NULL, "client_id" );

    osc_server->add_method( "/osc/ping", "", OSC_NAME( ping ), NULL, "" );

    osc_server->add_method( "/nsm/server/broadcast", NULL, OSC_NAME( broadcast ), NULL, "" );
    osc_server->add_method( "/nsm/server/duplicate", "s", OSC_NAME( duplicate ), NULL, "" );
    osc_server->add_method( "/nsm/server/abort", "", OSC_NAME( abort ), NULL, "" );
    osc_server->add_method( "/nsm/server/list", "", OSC_NAME( list ), NULL, "" );
    osc_server->add_method( "/nsm/server/add", "s", OSC_NAME( add ), NULL, "executable_name" );
    osc_server->add_method( "/nsm/server/new", "s", OSC_NAME( new ), NULL, "name" );
    osc_server->add_method( "/nsm/server/save", "", OSC_NAME( save ), NULL, "" );
    osc_server->add_method( "/nsm/server/open", "s", OSC_NAME( open ), NULL, "name" );
    osc_server->add_method( "/nsm/server/close", "", OSC_NAME( close ), NULL, "" );
    osc_server->add_method( "/nsm/server/quit", "", OSC_NAME( quit ), NULL, "" );

    osc_server->add_method( NULL, NULL, OSC_NAME( null ),NULL, "" );

    if ( detach )
    {
        MESSAGE( "Detaching from console" );
        if ( fork() )
        {
            exit( 0 );            
        }
        else
        {
            fclose( stdin );
            fclose( stdout );
            fclose( stderr );
        }
    }

    /* listen for sigchld signals and process OSC messages forever */
    for ( ;; )
    {
        wait( 1000 );
    }
    
//    osc_server->run();

    return 0;
}
