/*
 * Multi-Simulation Interface (MSI)
 * $Id: msi_client.c,v 1.4 2006/10/24 20:32:29 arubin Exp $
 * Copyright (C) 2002-2004 Lockheed Martin Advanced Technology Labs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Lesser 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include "soc_lib2.h"
#include "general_utils.h"

typedef void (*MSITagHandler)( char **names, char **values, void *ud );
typedef void (*MSIUpdateHandler)( char **names, char **values, void *ud );
typedef void (*MSIMessageHandler)( char **names, char **values, void *ud );

typedef struct MSINode {
  char *name;
  unsigned long id;
} MSINode;
typedef struct _MSIContext {
  NetAsync *conn;
  int wait_for_time_advance;
  double real_tone;

  char *pc;
  uint pc_len;
  HashTreeNode *node_by_name;
  HashTreeNode *node_by_id;
  void *update_ud;
  MSITagHandler update_handler;
  void *message_ud;
  MSITagHandler message_handler;
} MSIContext;

// Group: Opaque Structure Handles
typedef void *MSIH; // MSIContext
typedef void *MSIObjectH; // MSIObject
typedef void *MSIObjectTypeH; // MSIObject
typedef void *MSIInstanceH; // MSIObject
typedef void *MSIMessageH; // MSIMessageType
typedef void *MSIAttributeH; // MSIAttribute
typedef void *MSISubscriptionH; // MSISubscription

MSIContext *msi_context_internal = NULL;
#ifdef CSIM_CODE
SYNCHRON *msi_ready = NULL;
#endif

// Group: Functions to Overide Builtins

void msi_memorize_nodes( char **names, char **values, void *ud ) {
  
}

void msi_set_update_handler( MSITagHandler update_handler, void *ud ) {
#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif

  msi_context_internal->update_handler = update_handler;
  msi_context_internal->update_ud = ud;
}

void msi_set_message_handler( MSITagHandler message_handler, void *ud ) {
#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif
  
  msi_context_internal->message_handler = message_handler;
  msi_context_internal->message_ud = ud;
}

// Group: Event Reception Functions

MSIObjectTypeH msi_object_type_by_id( MSIH _msi, void *type_id ) {
  return( NULL );
}
MSIAttributeH msi_attribute_by_id( MSIObjectH obj, void *type_id ) {
  return( NULL );
}
MSIMessageH msi_message_by_id( MSIH _msi, void *type_id ) {
  return( NULL );
}

MSISubscriptionH msi_call_on_object_event( MSIH _msi, char *name,
					   MSIUpdateHandler func, void *arg ) {
  return( NULL );
}
MSISubscriptionH msi_call_on_attr_event( MSIH _msi, char *obj_name, char *name,
					 MSIUpdateHandler func, void *arg ) {
  return( NULL );
}
MSISubscriptionH msi_call_on_message( MSIH _msi, char *name, MSIMessageHandler func,
				      void *arg ) {
  return( NULL );
}

#ifdef CSIM_CODE
MSISubscriptionH msi_resume_on_object_event( MSIH _msi, char *name,
					 SYNCHRON **synchron );
MSISubscriptionH msi_resume_on_attr_event( MSIH _msi, char *obj_name, char *name,
				       SYNCHRON **synchron );
MSISubscriptionH msi_resume_on_interaction( MSIH _msi, char *name,
					SYNCHRON **syncron );
#endif

// Group: Event Publishing functions

// func: msi_object_add_attributes
void msi_object_add_attributes( MSIH _msi, char *obj_name, ... );
// func: msi_register_instance
MSIInstanceH msi_register_instance( MSIH _msi, void *_object );
// func: msi_get_current_time
double msi_get_current_time( MSIH _msi );

void msi_send_update( const int id, char **names, char **values ) {
  char *spool, buf[128];
  uint len;
  int i;

#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif
  
  spool = memspool( NULL, &len, "<update id=\"", 12 );
  snprintf( buf, 128, "%d", id );
  spool = memspool( spool, &len, buf, strlen( buf ) );
  spool = memspool( spool, &len, "\"", 1 );
  for( i = 0; names[i]; ++i ) {
    spool = memspool( spool, &len, " ", 1 );
    spool = sxml_spool_escaped( spool, &len, names[i] );
    spool = memspool( spool, &len, "=\"", 2 );
    spool = sxml_spool_escaped(spool, &len, values[i] );
    spool = memspool( spool, &len, "\"", 1 );
  }
  spool = memspool( spool, &len, " />\n", 4 );

  net_async_send( msi_context_internal->conn, spool, len );

  free( spool );
}

void msi_send_register( char *name, char *type, char **names, char **values ) {
  char *spool;
  uint len;
  int i;

#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif
  
  spool = memspool( NULL, &len, "<register name=\"", 16 );
  spool = sxml_spool_escaped( spool, &len, name );
  spool = memspool( spool, &len, "\"", 1 );
  if( type ) {
    spool = memspool( spool, &len, " type=\"", 7 );
    spool = sxml_spool_escaped( spool, &len, type );
    spool = memspool( spool, &len, "\"", 1 );
  }
  for( i = 0; names[i]; ++i ) {
    spool = memspool( spool, &len, " ", 1 );
    spool = sxml_spool_escaped( spool, &len, names[i] );
    spool = memspool( spool, &len, "=\"", 2 );
    spool = sxml_spool_escaped( spool, &len, values[i] );
    spool = memspool( spool, &len, "\"", 1 );
  }
  spool = memspool( spool, &len, " />\n", 4 );

  net_async_send( msi_context_internal->conn, spool, len );

  free( spool );
}

void msi_send_message( char *type, char **names, char **values ) {
  char *spool;
  uint len;
  int i;

#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif
  
  spool = memspool( NULL, &len, "<message type=\"", 15 );
  spool = sxml_spool_escaped( spool, &len, type );
  spool = memspool( spool, &len, "\"", 1 );
  for( i = 0; names[i]; ++i ) {
    spool = memspool( spool, &len, " ", 1 );
    spool = sxml_spool_escaped( spool, &len, names[i] );
    spool = memspool( spool, &len, "=\"", 2 );
    spool = sxml_spool_escaped( spool, &len, values[i] );
    spool = memspool( spool, &len, "\"", 1 );
  }
  spool = memspool( spool, &len, " />\n", 4 );

  net_async_send( msi_context_internal->conn, spool, len );

  free( spool );
}

void msi_send_subscribe( char *name, char *type, char **names ) {
  char *spool;
  uint len;
  int i;

  // this will not work without either a name or a type
  if( !(name || type) )
    return;

#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif

  spool = memspool( NULL, &len, "<subscribe", 10 );
  if( name ) {
    spool = memspool( spool, &len, " name=\"", 7 );
    spool = sxml_spool_escaped( spool, &len, name );
    spool = memspool( spool, &len, "\"", 1 );
  }
  if( type ) {
    spool = memspool( spool, &len, " type=\"", 7 );
    spool = sxml_spool_escaped( spool, &len, type );
    spool = memspool( spool, &len, "\"", 1 );
  }
  if( names[0] ) {
    spool = memspool( spool, &len, " attributes=\"", 13 );
    for( i = 0; names[i]; ++i ) {
      spool = sxml_spool_escaped( spool, &len, names[i] );
      if( names[i+1] ) spool = memspool( spool, &len, ", ", 2 );
    }
    spool = memspool( spool, &len, "\"", 1 );
  }
  spool = memspool( spool, &len, " />\n", 4 );

  net_async_send( msi_context_internal->conn, spool, len );

  free( spool );
}

void msi_send_direct( char *str ) {
#ifdef CSIM_CODE
  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
#endif
  
  net_async_send( msi_context_internal->conn, str, strlen( str ) );
}

void msi_send_tone( double mytone ) {
  char buf[128];

  msi_context_internal->wait_for_time_advance = 1;
  snprintf( buf, 128, "<tone t=\"%.28g\" />\n", mytone );
  msi_send_direct( buf );
}

int msi_received_tone( double *rtone ) {
  if( !msi_context_internal->wait_for_time_advance )
    *rtone = msi_context_internal->real_tone;
  return( !msi_context_internal->wait_for_time_advance );
}

#ifdef CSIM_CODE
void msi_time_lock_loop( MSIContext *msi ) {
  double mytone, delay_time;
  char buf[128];

  if( !msi_context_internal )
    WAIT( &msi_ready, NONQUEUABLE );
  
  while( 1 ) {
    // round local tone for fuzzyness
    mytone = ceil( CSIM_TONE()*16.0 )/16.0;

    // report my tone and await an answer
    if( msi->conn->socket ) {
      snprintf( buf, 128, "<tone t=\"%.28g\" />\n", mytone );
      net_async_send( msi->conn, buf, strlen( buf ) );
      //printf( "MSISocket: sent tone=\"%.28g\"\n", mytone );

      // productive spin lock
      msi->wait_for_time_advance = 1;
      while( msi->wait_for_time_advance )
	DELAY( 0.0 ); // hand control back to kernel to update the gui

      // reliquish control
      delay_time = msi->real_tone - CSIM_TIME;
      if( delay_time > 0.0 )
	DELAY( delay_time );
    }
    // ensure that this is the last event of the current time
    DELTA_DELAY();
  }
}
#endif

void msi_process_tone_command( MSIContext *msi, char **names, char **values ) {
  double rtone = 0.0;
  int got_time = 0, i;

  for( i = 0; names[i]; ++i ) {
    if( !strcmp( names[i], "t=" ) ) {
      rtone = atof( values[i] );
      got_time = 1;
    } else if( strcmp( names[i], "/" ) ) {
      fprintf( stderr, "MSISocket: I was not programmed to understand"
	               " 'tone %s', ignoring...\n", names[i] );
    }
  }

  if( !got_time ) {
    fprintf( stderr, "MSISocket: tone command without time, ignoring...\n" );
    return;
  }

  //printf( "MSISocket: rtone set to %g\n", rtone );
  msi->real_tone = rtone;
  msi->wait_for_time_advance = 0;
}
      
void msi_start_element( char *name, char **attrs, void *ud ) {
  MSIContext *msi = (MSIContext *)ud;
  char **names = NULL, **values = NULL, *blank = "";
  uint len = 0, len2 = 0, slen;
  int i;

  for( i = 0; attrs[i]; i++ ) {
    if( i > 127 ) {
      fprintf( stderr, "MSISocket: got too many attributes on tag" );
      exit( 2 );
    }

    slen = strlen( attrs[i] );
    names = (char **)memspool( (char *)names, &len, &(attrs[i]), sizeof(char *) );
    if( attrs[i][slen - 1] == '=' ) {
      if( attrs[i + 1] ) {
	i++;
	values = (char **)memspool( (char *)values, &len2, &(attrs[i]),
				    sizeof(char *) );
      } else {
	values = (char **)memspool( (char *)values, &len2, &blank,
				    sizeof(char *) );
      }
    } else {
      values = (char **)memspool( (char *)values, &len2, &(attrs[i]),
				  sizeof(char *) );
    }
  }
  names = (char **)memspool( (char *)names, &len, &(attrs[i]), sizeof(char *) );

  if( !strcmp( name, "tone" ) ) {
    msi_process_tone_command( msi, names, values );
  } else
  if( !strcmp( name, "update" ) ) {
    if( msi->update_handler )
      msi->update_handler( names, values, msi->update_ud );
  } else
  if( !strcmp( name, "message" ) ||
      !strcmp( name, "interaction" ) ) {
    if( msi->message_handler )
      msi->message_handler( names, values, msi->message_ud );
  }

  if( names )
    free( names );
  if( values )
    free( values );
}

void msi_conn_func( char *buf, uint len, void *ud ) {
  MSIContext *msi = (MSIContext *)ud;

  if( !len ) {
    printf( "MSISocket: recv'ed empty buffer\n" );
    return;
  }

  if( msi->conn->state < 0 ) {
    fprintf( stderr, "MSISocket: Connection to MSI failed\n" );
    return;
  }

  /*
  printf( "MSISocket: recv'ed: " );
  fwrite( buf, 1, len, stdout );
  puts( "" );
  */
  msi->pc = sxml_parse( msi->pc, &(msi->pc_len), buf, len, msi_start_element,
			NULL, NULL, msi );
  if( 0 ) {
    fprintf( stderr, "MSISocket: read: %s\n", "error" );
    exit( 1 );
  }

}

void msi_iteration( int sig ) {
  if( !msi_context_internal->conn->socket ) {
    net_async_iteration( msi_context_internal->conn );
  }

  // prevent spinning
  if( !msi_context_internal->conn->outbuf )
    net_async_block_for( msi_context_internal->conn, 10 );

  // do work on socket
  net_async_iteration( msi_context_internal->conn );
}

void msi_sig_io( int sig ) {
  //  if( msi_debug_level > 10 )
  //    puts( "got io" );
  net_async_iteration( msi_context_internal->conn );
}

int msi_client_init( int use_socket, int time_lock, char *name, int port ) {
  MSIContext *msi;
  FILE *fp;
  char *buf;

  if( !msi_context_internal ) {
    msi_context_internal = c_new0( MSIContext, 1 );
    msi = msi_context_internal;
#ifdef CSIM_CODE
    RESUME( &msi_ready, NONQUEUABLE );
#endif
  } else {
    return( 0 );
  }

  if( use_socket ) {
    printf( "MSISocket: using socket connection\n" );
    
    msi->conn = net_async_new_client( name, port, msi_conn_func, msi, 4096 );
    if( !msi->conn ) {
      fprintf( stderr, "MSISocket: Error could not establish MSI socket\n" );
      return( 0 );
    }
#ifndef CSIM_CODE
    msi->conn->use_signals = 1;

    msi_iteration( 0 );
#endif

    if( !time_lock ) {
      buf = "<tone constrained=\"no\" />\n";
      net_async_send( msi->conn, buf, strlen( buf ) );
    }

#if !defined(CSIM_CODE) && defined(SIGIO)
    signal( SIGIO, msi_sig_io );
    signal( SIGPIPE, msi_sig_io );
#endif

#ifdef CSIM_CODE
    CSIM_AddEventLoopFunc( msi_iteration, NULL );

    if( time_lock > 0 )
      msi_time_lock_loop( msi );
#endif
  } else {
    if( !(fp = fopen( name, "r" )) ) {
      fprintf( stderr, "MSISocket: Could not read socket script \"%s\"\n",
	       name );
      return( 0 );
    }

    buf = malloc( 4096 );
    while( fgets( buf, 4096, fp ) ) {
      msi->pc = sxml_parse( msi->pc, &(msi->pc_len), buf, strlen( buf ),
			    msi_start_element, NULL, NULL, msi );
      if( 0 ) {
	fprintf( stderr, "MSISocket: parse: %s\n", "error" );
	exit( 1 );
      }
    }
    free( buf );
  }

#ifndef CSIM_CODE
  msi_iteration( 0 );
#endif

  return( 1 );
}

