/*
 * Multi-Simulation Interface (MSI)
 * $Id: msi.c,v 1.15 2006/10/24 20:21:31 arubin Exp $
 * Copyright (C) 2002-2006 Aron Rubin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <math.h>
#include "soc_lib2.h"
#include "general_utils.h"
#include "msi.h"

int msi_debug_level = 0;
int msi_active_connection_count = 0;
int msi_wait_count = 0;
NetAsync *msi_server = NULL;
HashTreeNode *msi_connections = NULL;
MSIConn *msi_connection_list = NULL;
MSINodeID msi_node_id_counter = 0;
HashTreeNode *msi_node_ids = NULL;
HashTreeNode *msi_node_names = NULL;
HashTreeNode *msi_type_names = NULL;
FILE *msi_dump_file = NULL;

void msi_start_element( char *name, char **attrs, void *ud );
void msi_end_element( char *name, void *ud );
void msi_chars( const char *chars, void *ud );


void msi_write_debug( MSIConn *conn, const char *buf, uint len ) {
  printf( "MSI: conn write: " );
  fwrite( buf, 1, len, stdout );
  puts( "" );
}


char *msi_key_address( char *key, size_t len ) {
  static HashTreeNode *key_dict = NULL;
  void **val;

  if( !key_dict )
    key_dict = hash_tree_new( 8192 );
  val = hash_tree_access( key_dict, key, len, 0, 1 );
  if( !*val ) {
    *val = memcpy( malloc( len + 1 ), key, len );
    ((char *)*val)[len] = '\0';
  }

  return( (char *)*val );
}

/* ------------------------------
   Function: msi_conn_by_conn
     Lookup a <MSIConn> by a net connection. If no connection was previously
     created, create a new one.

   Parameters:
     conn - a net connection

   Returns:
     An existing MSIConn structure or a newly allocated one.
   ------------------------------ */
MSIConn *msi_conn_by_conn( NetAsync *conn ) {
  MSIConn *msiconn;
  char buf[512];
  void **val;

  val = hash_tree_direct_access( msi_connections, conn, 1 );

  if( !*val ) { // if not found, create it
    msiconn = c_new0( MSIConn, 1 );
    msiconn->type = MSI_CONN_NETWORK_SOCKET;
    msiconn->conn = conn;
    if( conn->name ) {
      snprintf( buf, 512, "%s:%d", conn->name, conn->port );
      msiconn->name = strdup( buf );
    }

    *val = msiconn;
    msiconn->next = msi_connection_list;
    msi_connection_list = msiconn;
  } else {
    msiconn = MSI_CONN(*val);
  }

  return( msiconn );
}

/* ------------------------------
   Function: msi_conn_free
     Find and remove all references to a <MSIConn> and then free the <MSIConn>.

   Parameters:
     msiconn - the <MSIConn> to be destroyed.
   ------------------------------ */
void msi_conn_free( MSIConn *msiconn ) {
  MSISubscription *sub, *sub2;
  DataList *list;

  // remove all subscriptions
  while( msiconn->subscriptions ) {
    sub = msiconn->subscriptions;
    msiconn->subscriptions = msiconn->subscriptions->cnext;
    if( sub->type ) { // if the subscription is on a type
      if( sub->type->subscriptions == sub ) {
	sub->type->subscriptions = sub->tnext;
      } else {
	sub2 = sub->type->subscriptions;
	while( sub2->tnext && sub2->tnext != sub )
	  sub2 = sub2->tnext;
	if( sub2->tnext )
	  sub2->tnext = sub->tnext;
      }
    } else if( sub->node ) { // if the subscription is on a node
      if( sub->node->subscriptions == sub ) {
	sub->node->subscriptions = sub->nnext;
      } else {
	sub2 = sub->node->subscriptions;
	while( sub2->nnext && sub2->nnext != sub )
	  sub2 = sub2->nnext;
	if( sub2->nnext )
	  sub2->nnext = sub->nnext;
      }
    }
    if( sub->attributes ) // if the subscription specified attributes
      hash_tree_destroy( sub->attributes );
    free( sub );
  }

  // remove all publishings
  while( msiconn->publishings ) {
    list = msiconn->publishings;
    msiconn->publishings = msiconn->publishings->next;
    MSI_NODE(list->data)->publisher = NULL;
    free( list );
  }

  // parse context
  if( msiconn->pc )
    free( msiconn->pc );

  // remove this connection from the socket list if needed
  if( msiconn->type != MSI_CONN_FILE )
    hash_tree_remove( msi_connections, &msiconn->conn, sizeof(NetAsync *)  );

  // remove this connection from the connection list
  if( msi_connection_list == msiconn ) {
    msi_connection_list = msi_connection_list->next;
  } else {
    MSIConn *conn = msi_connection_list;
    while( conn->next && conn->next != msiconn )
      conn = conn->next;
    if( conn->next )
      conn->next = conn->next->next;
  }

  free( msiconn );
}


/* ------------------------------
   Function: msi_node_by_name
     Lookup a <MSINode> by its name. If no node was previously created, create
     a new one.

   Parameters:
     name - a name of a node

   Returns:
     An existing <MSINode> structure or a newly allocated one.
   ------------------------------ */
MSINode *msi_node_by_name( char *name ) {
  MSINode *node;
  void **val;

  val = hash_tree_access( msi_node_names, name, strlen(name), 0, 1 );

  // if not found make it
  if( !*val ) {
    node = c_new0( MSINode, 1 );
    node->id = msi_node_id_counter++;
    node->name = strdup( name );
    *val = node;

    // insert into hash tables
    val = hash_tree_direct_access( msi_node_ids, (void *)node->id, 1 );
    *val = node;
  }

  return( MSI_NODE(*val) );
}

/* ------------------------------
   Function: msi_node_by_id
     Lookup a <MSINode> by its id.

   Parameters:
     id - a node id

   Returns:
     An existing <MSINode> structure or NULL on failure.
   ------------------------------ */
MSINode *msi_node_by_id( unsigned long id ) {
  void **val = hash_tree_direct_access( msi_node_ids, (void *)id, 0 );

  return( val ? MSI_NODE(*val) : NULL );
}

/* ------------------------------
   Function: msi_type_by_name
     Lookup a <MSIType> by its name. If no type was previously created, create
     a new one.

   Parameters:
     name - a name of a type

   Returns:
     An existing <MSIType> structure or a newly allocated one.
   ------------------------------ */
MSIType *msi_type_by_name( char *name ) {
  MSIType *type = NULL;
  void **val;

  name = msi_key_address( name, strlen( name ) );
  val = hash_tree_access( msi_type_names, name, strlen(name), 0, 1 );

  // if not found make it and insert into the table
  if( !*val ) {
    type = c_new0( MSIType, 1 );
    type->name = name;
    *val = type;
  } else {
    type = *val;
  }

  return( type );
}

/* ------------------------------
   Function: msi_node_attributes_from_tag
     Extract attributes and their values from the attribute list on an XML tag.
     Return an array of attributes that were processed.

   Parameters:
     node - the <MSINode> to attach the attributes.

   Returns:
     
   ------------------------------ */
char **msi_node_attributes_from_tag( MSINode *node, char **attrs, char *processed[] ) {
  int i, len, na = 0;
  void **val;
  char *str, *name, *value;

  for( i = 0; attrs[i]; ++i ) {
    name = attrs[i];
    if( *name && strcmp( name, "/" ) ) { // if we have not hit a tag termitor
      if( !node->attributes )
	node->attributes = hash_tree_new( 13 );
      len = strlen( name );
      if( name[len - 1] == '=' ) {
	if( attrs[i + 1] ) {
	  value = attrs[i + 1];
	  i++;
	} else {
	  value = "";
	}
	if( len > 1 && name[len - 2] == '.' ) { // processing append operator .=
	  name = msi_key_address( name, len - 2 );
	  processed[na] = name;
	  na++;
	  val = hash_tree_direct_access( node->attributes, name, 1 );
	  if( *val ) {
	    str = strcat( (char *)realloc( *val, strlen((char *)*val) +
					   strlen(value) + 1 ), value );
	    *val = str;
	  } else {
	    *val = strdup( value );
	  }
	} else { // processing normal name, value pair
	  name = msi_key_address( name, len - 1 );
	  processed[na] = name;
	  na++;
	  val = hash_tree_direct_access( node->attributes, name, 1 );
	  if( *val )
	    free( *val );
	  *val = strdup( value );
	}
      } else { // 
	name = msi_key_address( name, len );
	processed[na] = name;
	na++;
	val = hash_tree_direct_access( node->attributes, name, 1 );
	if( *val )
	  free( *val );
	*val = strdup( name );
      }
    }
  }

  processed[na] = NULL;
  return( processed );
}

typedef struct MSISprintAttrContext {
  MSINode *node;
  char *spool;
  unsigned int len;
} MSISprintAttrContext;

void msi_sprint_attr( void *key, size_t len, void *data, void *user_data ) {
  MSISprintAttrContext *con = (MSISprintAttrContext *)user_data;
  void **val;

  if( con->node )
    val = hash_tree_direct_access( con->node->attributes, key, 0 );
  else
    val = &(data);
  if( val && *val ) {
    con->spool = memspool( con->spool, &(con->len), " ", 1 );
    con->spool = sxml_spool_escaped( con->spool, &(con->len), (char *)key );
    con->spool = memspool( con->spool, &(con->len), "=\"", 2 );
    con->spool = sxml_spool_escaped( con->spool, &(con->len), (char *)*val );
    con->spool = memspool( con->spool, &(con->len), "\"", 1 );
  }
}

void msi_update_subscriber( MSINode *node, char **updated,
			    MSISubscription *sub ) {
  int i, emptylen, rv;
  MSISprintAttrContext con;
  char idstr[128];

  if( !sub->conn->conn )
    return;

  con.node = node;
  con.len = 0;
  con.spool = memspool( NULL, &con.len, "<update id=\"", 12 );
  snprintf( idstr, 128, "%ld", node->id );
  con.spool = memspool( con.spool, &con.len, idstr, strlen( idstr ) );
  con.spool = memspool( con.spool, &con.len, "\"", 1 );
  emptylen = con.len;

  if( sub->attributes ) { // substription applies to particular attributes
    if( updated ) {
      for( i = 0; updated[i]; i++ ) {
	if( hash_tree_direct_access( sub->attributes, updated[i], 0 ) )
	  msi_sprint_attr( updated[i], 0, NULL, &con );
      }
    } else {
      hash_tree_foreach( sub->attributes, msi_sprint_attr, &con );
    }
  } else { // substription applies to all attributes
    if( updated ) {
      for( i = 0; updated[i]; i++ )
	msi_sprint_attr( updated[i], 0, NULL, &con );
    } else {
      con.node = NULL;
      hash_tree_foreach( node->attributes, msi_sprint_attr, &con );
    }
  }
  if( con.len > emptylen ) {
    con.spool = memspool( NULL, &con.len, " />\n", 5 );
    rv = net_connection_send( sub->conn->conn->socket, con.spool, con.len );
    if( rv < 0 ) {
      net_async_delete( sub->conn->conn );
      sub->conn->conn = NULL;
    }
  }
  free( con.spool );
}

void msi_update_subscriber_descending( MSINode *node, char **updated,
				       MSISubscription *sub ) {
  MSISubscription sub2 = { sub->conn, NULL, NULL, 0, NULL };
  MSINode *child;

  msi_update_subscriber( node, updated, sub );
  if( sub->children ) {
    child = node->children;
    while( child ) {
      msi_update_subscriber_descending( child, NULL, &sub2 );
      child = child->next;
    }
  }
}

void msi_update_subscribers( MSIConn *msiconn, MSINode *node, char **updated ) {
  MSISubscription *sub;
  MSINode *parent;
  int is_orig = 1;

  parent = node;
  while( parent ) {
    if( parent->type ) {
      sub = parent->type->subscriptions;
      while( sub ) {
	if( msiconn != sub->conn && (is_orig || sub->children) )
	  msi_update_subscriber( node, updated, sub );
	sub = sub->tnext;
      }
    }
    sub = parent->subscriptions;
    while( sub ) {
      if( msiconn != sub->conn && (is_orig || sub->children) )
	msi_update_subscriber( node, updated, sub );
      sub = sub->nnext;
    }

    if( is_orig )
      is_orig = 0;

    parent = parent->parent;
  }

  // update publisher if necessary
  if( node->publisher && msiconn != node->publisher ) {
    MSISubscription sub2 = { node->publisher, NULL, NULL, 0, NULL };
    msi_update_subscriber( node, updated, &sub2 );
  }
}

int msi_file_func( void *user_data );

void msi_decide_tone() {
  MSIConn *conn;
  double thetone;
  char buf[512];
  int rv;
  //int num_conns = 0;

  if( msi_active_connection_count < msi_wait_count ) {
    printf( "MSI: %d conns, waiting for %d\n", msi_active_connection_count, msi_wait_count );
    return;
  }

  // determine if every one has reported in and find the smallest tone
  thetone = HUGE_VAL;
  conn = msi_connection_list;
  while( conn && conn->conn && (conn->time_unconstrained || conn->waiting) ) {
    if( !conn->time_unconstrained && conn->tone < thetone )
      thetone = conn->tone;
    //num_conns++;
    conn = conn->next;
  }

  if( conn )
    return;

  if( msi_debug_level > 2 ) {
    printf( "MSI: ready to send time: %.28g\n", thetone );
  }

  // tell everyone the tone to use
  conn = msi_connection_list;
  while( conn ) {
    if( conn->conn ) {
      if( conn->type == MSI_CONN_FILE ) {
	int was_waiting = conn->waiting;
	conn->waiting = conn->tone == thetone;
	if( was_waiting && !conn->waiting )
	  ;//fixme: g_idle_add_full( G_PRIORITY_HIGH_IDLE, msi_file_func, conn, NULL );
      } else if( !conn->time_unconstrained ) {
	conn->waiting = 0; // reset waiting flag while we're at it

	snprintf( buf, 512, "<tone t=\"%.28g\" />\n", thetone );
	if( msi_debug_level > 2 ) {
	  printf( "MSI: sending time: %s", buf );
	}
	rv = net_connection_send( conn->conn->socket, buf, strlen( buf ) );
	if( rv < 0 ) {
	  net_async_delete( conn->conn );
	  conn->conn = NULL;
	}
      }
    }
    conn = conn->next;
  }
}

void msi_process_tone_command( MSIConn *conn, char **attrs ) {
  char *name;
  double rtone = -1.0;
  int got_time = 0, i;

  for( i = 0; attrs[i]; ++i ) {
    name = attrs[i];
    if( attrs[i + 1] )
      i++;
    if( !strcmp( name, "t=" ) ) {
      rtone = atof( attrs[i] );
      got_time = 1;
    } else if( !strcmp( name, "constrained=" ) ) {
      conn->time_unconstrained = !(strcmp( attrs[i], "false" ) &&
				   strcmp( attrs[i], "no" ));
    } else if( strcmp( name, "/" ) ) {
      fprintf( stderr, "MSI: I was not programmed to understand"
	               " 'tone %s', ignoring...\n", name );
    }
  }
  if( conn->time_unconstrained ) {
    conn->tone = HUGE_VAL;
  } else {  
    if( !got_time ) {
      fprintf( stderr, "MSI: tone command without time, ignoring...\n" );
      return;
    }
    conn->tone = rtone;
    conn->waiting = 1;
    msi_decide_tone();
  }
}

void msi_process_register_command( MSIConn *conn, char **attrs ) {
  int i, len, rv;
  char *buf, *updated[256];
  void **val;
  MSINode *node;
  DataList *list;
  static char *type_key = NULL, *parent_key = NULL;

  if( !conn->conn )
    return;

  // get node name
  for( i = 0; attrs[i] && strcmp( attrs[i], "name=" ); ++i ) if( attrs[i + 1] ) i++;
  if( !attrs[i] ) {
    fprintf( stderr, "MSI: No name in a register/publish, ignoring...\n" );
    return;
  }

  // create a new node
  node = msi_node_by_name( attrs[i + 1] );
  node->publisher = conn;
  list = c_new( DataList, 1 );
  list->data = node;
  list->next = conn->publishings;
  conn->publishings = list;
  msi_node_attributes_from_tag( node, attrs, updated );

  // handle type info
  if( !type_key )
    type_key = msi_key_address( "type", 4 );
  if( (val = hash_tree_direct_access( node->attributes, type_key, 0 )) &&
      *val ) {
    node->type = msi_type_by_name( (char *)*val );
    node->tnext = node->type->members;
    node->type->members = node;
  }

  // handle hierarchy info
  if( !parent_key )
    type_key = msi_key_address( "parent", 6 );
  if( (val = hash_tree_direct_access( node->attributes, parent_key, 0 )) &&
      *val ) {
    node->parent = msi_node_by_name( (char *)*val );
    node->next = node->parent->children;
    node->parent->children = node;
  }

  // reply with id
  len = strlen( node->name ) + 256;
  buf = malloc( len );
  snprintf( buf, len, "<update name=\"%s\" id=\"%ld\" />\n", node->name,
	    node->id );
  rv = net_connection_send( conn->conn->socket, buf, strlen( buf ) );
  if( rv < 0 ) {
    net_async_delete( conn->conn );
    conn->conn = NULL;
  }
  free( buf );

  // update subscribers
  msi_update_subscribers( conn, node, NULL );
}

typedef struct MSITransaction MSITransaction;
struct MSITransaction {
  char **updated;
};
void msi_process_update_command( MSIConn *conn, char **attrs ) {
  int i;
  MSINodeID id;
  MSINode *node = NULL;
  char *updated[256];

  // get node id
  for( i = 0; attrs[i] && strcmp( attrs[i], "id=" ); ++i ) if( attrs[i + 1] ) i++;
  if( attrs[i] ) {
    id = atol( attrs[i + 1] );
    node = msi_node_by_id( id );
  } else { // no id, try name
    for( i = 0; attrs[i] && strcmp( attrs[i], "name=" ); ++i ) if( attrs[i + 1] ) i++;
    if( attrs[i] )
      node = msi_node_by_name( attrs[i + 1] );
  }
  if( !node ) {
    fprintf( stderr, "MSI: No valid form of identification in update,"
	     " ignoring...\n" );
    return;
  }

  // update cache
  msi_node_attributes_from_tag( node, attrs, updated );

  // update subscribers
  if( updated[0] )
    msi_update_subscribers( conn, node, updated );
}
  
void msi_process_subscribe_command( MSIConn *conn, char **attrs, int remove ) {
  int i;
  MSINode *node = NULL, *member;
  MSISubscription *sub = NULL, *sub2;
  MSIType *type = NULL;
  char *name, *attr, *save, **attra = NULL;
  uint attra_len = 0;
  void **val;
  int children = 0;

  for( i = 0; attrs[i]; ++i ) {
    name = attrs[i];
    if( attrs[i + 1] )
      i++;
    if( !strcmp( name, "name=" ) ) {
      node = msi_node_by_name( attrs[i] );
    } else
    if( !strcmp( name, "type=" ) ) {
      type = msi_type_by_name( attrs[i] );
    } else
    if( !strcmp( name, "attributes=" ) ) {
      attr = c_strtok_nd( attrs[i], " \t,", &save );
      while( attr ) {
	attr = msi_key_address( attr, save - attr );
	attra = (char **)memspool( (char *)attra, &attra_len, &attr,
				   sizeof(char *) );
	attr = c_strtok_nd( NULL, " \t,", &save );
      }
    } else if( !strcmp( name, "children=" ) ) {
      children = !(strcmp( "false", attrs[i] ) &&
		   strcmp( "no", attrs[i] ));
    } else if( strcmp( name, "/" ) ) {
      fprintf( stderr, "MSI: I was not programmed to understand subscribe %s,"
	       " ignoring...\n", name );
    }
  }

  if( attra )
    attra = (char **)memspool( (char *)attra, &attra_len,
			       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
			       sizeof(char *) );

  // find existing
  if( node ) {
    sub = node->subscriptions;
    while( sub && sub->conn != conn )
      sub = sub->nnext;
  } else if( type ) {
    sub = type->subscriptions;
    while( sub && sub->conn != conn )
      sub = sub->tnext;
  } else {
    fprintf( stderr, "MSI: subscribe with no named target node or type\n" );
    if( attra )
      free( attra );
    return;
  }

  if( !remove ) {
    // make a new subscription structure if needed
    if( !sub ) {
      sub = c_new0( MSISubscription, 1 );
      if( node ) {
	sub->node = node;
	sub->nnext = node->subscriptions;
	node->subscriptions = sub;
      } else { // type
	sub->type = type;
	sub->tnext = type->subscriptions;
	type->subscriptions = sub;
      }
      sub->conn = conn;
      sub->children = children;
      if( attra )
	sub->attributes = hash_tree_new( 7 );
      sub->cnext = conn->subscriptions;
      conn->subscriptions = sub;
    }

    // add the attribute subscriptions
    if( attra ) {
      for( i = 0; attra[i]; i++ ) {
	val = hash_tree_direct_access( sub->attributes, attra[i], 1 );
	*val = attra[i];
      }
    }

    // update new subscriber
    if( node ) {
      if( node->publisher )
	msi_update_subscriber( node, NULL, sub );
    } else { // type
      member = type->members;
      while( member ) {
	msi_update_subscriber_descending( member, NULL, sub );
	member = member->next;
      }
    }
  } else { // remove
    if( attra ) { // only attributes
      for( i = 0; attra[i]; i++ )
	hash_tree_direct_access( sub->attributes, attra[i], -1 );
    } else {
      if( node ) {
	// remove from node subscription list
	if( sub == node->subscriptions ) {
	  node->subscriptions = sub->nnext;
	} else {
	  sub2 = node->subscriptions;
	  while( sub2->nnext && sub2->nnext != sub )
	    sub2 = sub2->nnext;
	  if( sub2->nnext )
	    sub2->nnext = sub->nnext;
	}
      } else {
	// remove from type subscription list
	if( sub == type->subscriptions ) {
	  type->subscriptions = sub->tnext;
	} else {
	  sub2 = type->subscriptions;
	  while( sub2->tnext && sub2->tnext != sub )
	    sub2 = sub2->tnext;
	  if( sub2->tnext )
	    sub2->tnext = sub->tnext;
	}
      }
      if( sub == conn->subscriptions ) {
	conn->subscriptions = sub->cnext;
      } else {
	sub2 = conn->subscriptions;
	while( sub2->cnext && sub2->cnext != sub )
	  sub2 = sub2->cnext;
	if( sub2->cnext )
	  sub2->cnext = sub->cnext;
      }
      if( sub->attributes )
	hash_tree_destroy( sub->attributes );
      free( sub );
    }
  }
  if( attra )
    free( attra );

}

void msi_process_message_command( MSIConn *conn, char **attrs ) {
  MSISubscription *sub;
  MSIType *type = NULL;
  char *typename, *spool, *name, *value;
  uint len, slen;
  int i, rv;

  // get the message type
  for( i = 0; attrs[i] && strcmp( attrs[i], "type=" ); ++i )
    if( attrs[i + 1] )
      i++;
  if( !attrs[i] ) {
    fprintf( stderr, "MSI: message type missing or unknown, ignoring...\n" );
    return;
  }
  i++; // move to attr value (save some adds)
  typename = msi_key_address( attrs[i], strlen( attrs[i] ) );
  type = msi_type_by_name( typename );

  if (!type) {
    fprintf(stderr,"Warning: Ignoring unregistered message type %s\n",typename);
    return;
  }

  // repeat to subscribers
  sub = type->subscriptions;
  while( sub ) {
    // begin constructing XML message
    spool = memspool( NULL, &len, "<message", 8 );

    // move beyond typename and add attributes until end encoutered
    for( i = 0; attrs[i] && attrs[i][0] != '/'; ++i ) {
      name = attrs[i];
      if( *name ) { // only proper XML attributes
	slen = strlen( name );
	if( name[slen - 1] == '=' ) {
	  name = msi_key_address( attrs[i], slen - 1 );
	  if( attrs[i + 1] && attrs[i + 1][0] != '/' ) {
	    value = attrs[i + 1];
	    i++;
	  } else {
	    value = "";
	  }
	} else {
	  name = msi_key_address( attrs[i], slen );
	  value = name;
	}

	// fixme: only add 'type' attribute or subscribed attributes
	spool = memspool( spool, &len, " ", 1 );
	spool = sxml_spool_escaped( spool, &len, name );
	spool = memspool( spool, &len, "=\"", 2 );
	spool = sxml_spool_escaped( spool, &len, value );
	spool = memspool( spool, &len, "\"", 1 );
      }
    }
    spool = memspool( spool, &len, "/>\n", 3 );
    if( msi_debug_level > 1 )
	 msi_write_debug( sub->conn, spool, len );
    rv = net_connection_send( sub->conn->conn->socket, spool, len );
    if( rv < 0 ) {
      net_async_delete( conn->conn );
      sub->conn->conn = NULL;
    }
    if( spool ) {
      free( spool );
      spool = NULL;
      len = 0;
    }

    sub = sub->tnext;
  }
}

void msi_process_msi_tag( MSIConn *conn, char **attrs ) {
  char *phony_attrs[8] = { "name=", "", "type=", "__FEDERATE__", NULL };
  int i;

  for( i = 0; attrs[i] && strcmp( attrs[i], "name=" ); ++i )
    if( attrs[i + 1] )
      i++;

  if( !attrs[i] )
    return;

  phony_attrs[1] = attrs[i + 1];
  
  msi_process_register_command( conn, phony_attrs );
}

void msi_start_element( char *name, char **attrs, void *ud ) {
  MSIConn *conn = (MSIConn *)ud;

  if( !conn->conn )
    return;

  if( !strcmp( name, "tone" ) ) {
    msi_process_tone_command( conn, attrs );
  } else if( !(strcmp( name, "register" ) &&
	       strcmp( name, "publish" )) ) {
    msi_process_register_command( conn, attrs );
  } else if( !strcmp( name, "update" ) ) {
    msi_process_update_command( conn, attrs );
  } else if( !strcmp( name, "a" ) ) {
    conn->do_spool = 1;
  } else if( !strcmp( name, "subscribe" ) ) {
    msi_process_subscribe_command( conn, attrs, 0 );
  } else if( !strcmp( name, "unsubscribe" ) ) {
    msi_process_subscribe_command( conn, attrs, 1 );
  } else if( !(strcmp( name, "message" ) &&
	       strcmp( name, "interaction" )) ) {
    msi_process_message_command( conn, attrs );
  } else if( !strcmp( name, "msi" ) ) {
    // beginning of msi stream
    msi_process_msi_tag( conn, attrs );
  } else {
    fprintf( stderr, "MSI: I was not programmed to understand %s tags,"
	     " ignoring...\n", name );
  }
}

void msi_end_element( char *name, void *ud ) {
  MSIConn *conn = (MSIConn *)ud;

  if( conn->spool )
    conn->spool = memspool( conn->spool, &conn->slen, "", 1 );

  if( !conn->conn )
    return;

  if( conn->do_spool ) {
    if( conn->spool ) {
      free( conn->spool );
      conn->spool = NULL;
      conn->slen = 0;
    }
    conn->do_spool = 0;
  }
}

void cp_scenario_chars( const char *chars, void *ud ) {
  MSIConn *conn = (MSIConn *)ud;

  if( conn->do_spool )
    conn->spool = memspool( conn->spool, &conn->slen, chars, strlen( chars ) );
}

void msi_conn_func( char *buf, uint len, void *ud ) {
  MSIConn *conn = (MSIConn *)ud;

  if( !conn->conn ) {
    msi_conn_free( conn );
    return;
  }

  if( conn->conn->state < 0 )
    fprintf( stderr, "MSI: Connection to federate (%s) failed\n", conn->name );

  if( msi_debug_level > 1 ) {
    printf( "MSI: conn_read: " );
    fwrite( buf, 1, len, stdout );
    puts( "" );
  }
  if( msi_dump_file ) {
    fwrite( buf, 1, len, msi_dump_file );
  }

  conn->pc = sxml_parse( conn->pc, &(conn->pc_len), buf, len,
			 msi_start_element, NULL, NULL, conn );
  if( 0 ) {
    fprintf( stderr, "MSI: read from (%s): %s\n", conn->name, "error" );
    exit( 1 );
  }
}

int msi_file_func( void *ud ) {
  MSIConn *conn = (MSIConn *)ud;
  int chr;
  char *str = NULL, chrc;
  uint len = 0;

  /* make sure this file was not waiting on a time advance */
  if( conn->waiting )
    return( 0 );

  /* get the next line from the file */
  while( (chr = fgetc( conn->file )) != EOF && chr != '\n' ) {
    chrc = chr;
    str = memspool( str, &len, &chrc, 1 );
  }
  if( chr == '\n' )
    str = memspool( str, &len, &chrc, 1 );
  str = memspool( str, &len, "", 1 );

  if( 0 ) {
    fprintf( stderr, "MSI: read from (%s): %s\n", conn->name, "error" );
    exit( 1 );
  }

  /* if we hit the end of the file, clean up */
  if( chr == EOF ) {
    /* close file and free io channel */
    fclose( conn->file );

    /* clean up data structures */
    msi_conn_free( conn ); /* remove msiconn from the list */
    return( 0 );
  }

  if( msi_debug_level > 1 )
    printf( "MSI: file read: %s\n", str );

  /* process the line */
  conn->pc = sxml_parse( conn->pc, &(conn->pc_len), str, len,
			 msi_start_element, NULL, NULL, conn );
  if( 0 ) {
    fprintf( stderr, "MSI: read from (%s): %s\n", conn->name, "error" );
    exit( 1 );
  }

  return( !conn->waiting );
}

static void msi_server_func( NetAsync *child, void *ud ) {
  MSIConn *msiconn = NULL;

  msiconn = msi_conn_by_conn( child );
  printf( "MSI: Connected to federate (%s)\n", msiconn->name );
  child->user_data = msiconn;
  net_async_make_async( child );
  msi_active_connection_count++;
}

void msi_sig_int( int signum ) {
  net_async_server_close( msi_server );
  exit( 1 );
}


void msi_sig_io( int sig ) {
  if( msi_debug_level > 10 )
    puts( "got io" );
  net_async_iteration( msi_server );
}

int main( int argc, char *argv[] ) {
  int i, port = 30002;
  char *str;
  MSIConn *conn;

  puts( "Copyright (C) 2002-2006 Aron Rubin\n"
	"Author: Aron Rubin <arubin@atl.lmco.com>\n\n"
	"This program is licensed under the terms of the GNU General Public\n"
	"License as published by the Free Software Foundation; either\n"
	"version 2 of the License, or (at your option) any later version.\n"
	"\n" );

  msi_connections = hash_tree_new( 13 );
  msi_node_ids = hash_tree_new( 8192 );
  msi_node_names = hash_tree_new( 8192 );
  msi_type_names = hash_tree_new( 8192 );

  for( i = 1; i < argc; ++i ) {
    if( !(strcmp( argv[i], "-p" ) && strncmp( argv[i], "--port=", 7 )) ) {
      if( argv[i][1] == '-' ) // --port=
	str = argv[i] + 7;
      else
	str = argv[++i];

      port = atoi( str );
    } else if( !(strcmp( argv[i], "-f" ) && strncmp( argv[i], "--file=", 7 )) ) {
      if( argv[i][1] == '-' ) // --file=
	str = argv[i] + 7;
      else
	str = argv[++i];

      if( access( str, R_OK ) ) {
	perror( str );
	exit( 1 );
      }
      conn = c_new0( MSIConn, 1 );
      conn->file = fopen( str, "r" );
      conn->next = msi_connection_list;
      msi_connection_list = conn;
      //g_idle_add_full( G_PRIORITY_HIGH_IDLE, msi_file_func, conn, NULL );
    } else if( !(strcmp( argv[i], "-o" ) && strncmp( argv[i], "--dump=", 7 )) ) {
      if( argv[i][1] == '-' ) // --dump=
	str = argv[i] + 7;
      else
	str = argv[++i];

      msi_dump_file = fopen( str, "w" );
    } else if( !(strcmp( argv[i], "-d" ) && strncmp( argv[i], "--debug=", 8 )) ) {
      if( argv[i][1] == '-' ) // --debug=
	str = argv[i] + 8;
      else
	str = argv[++i];

      msi_debug_level = atoi( str );
      printf( "MSI: Setting debug level to %d\n", msi_debug_level );
    } else if( !(strcmp( argv[i], "-w" ) && strncmp( argv[i], "--wait=", 7 )) ) {
      if( argv[i][1] == '-' ) /* --wait= */
	str = argv[i] + 7;
      else
	str = argv[++i];

      msi_wait_count = atoi( str );
      printf( "MSI: Setting federate wait count to %d\n", msi_wait_count );

    } else if( !(strcmp( argv[i], "-h" ) && strcmp( argv[i], "--help" )) ) {
      puts( "The Multi-Simulator Interface (MSI) is a simulation"
	    " interconnection engine.\n"
	    "Usage: msi [OPTION]...\n\n"
	    "\t-d, --debug=LEVEL\tSet the amount of debugging prints\n"
	    "\t-f, --file=FILE\tUse FILE as a federate\n"
	    "\t-h, --help\tPrint this message to the screen\n"
	    "\t-o, --dump=FILE\tDump the transmissions to FILE\n"
	    "\t-p, --port=PORT\tSet the listening port to PORT\n"
	    "\t-w, --wait=COUNT\tWait for COUNT federates to join\n" );
      exit( 0 );
    }
  }

  msi_server = net_async_new_server( port, msi_server_func, msi_conn_func, NULL,
				     4096 );
  if( !msi_server ) {
    fprintf( stderr, "MSI: could not start server, exitting...\n" );
    exit( 1 );
  }
  net_async_make_async( msi_server );
#ifdef SIGIO
  signal( SIGIO, msi_sig_io );
#endif
  signal( SIGINT, msi_sig_int );
#ifdef SIGIO
  signal( SIGPIPE, msi_sig_io );
#endif

  while( 1 ) {
    net_async_block_for( msi_server, 0 );
    net_async_iteration( msi_server );
  }

  return( 0 );
}
