/***** midi.osc.c - (c) rohan drape, 2004-2007 *****/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "common/byte-order.h"
#include "common/client.h"
#include "common/failure.h"
#include "common/memory.h"
#include "common/network.h"
#include "common/observe-signal.h"
#include "common/print.h"
#include "common/osc.h"

#ifdef HOST_CORE_MIDI
#include "host/core-midi.h"
#endif

#ifdef HOST_ALSA_SEQUENCER
#include "host/alsa.h"
#endif

/* Server data structure. */

typedef struct
{
  midi_host_t *host ;
  int fd ;
  client_register_t *cr ;
  pthread_t thread ;
} 
midi_osc_t ;

/* Macro to declare a byte vector at 'osc_pkt' and build an OSC
   message at that vector. */

#define OSC_MAKE_PACKET(...)					\
  const int osc_pkt_extent = 8192 ;				\
  uint8_t osc_pkt[osc_pkt_extent] ;				\
  int osc_pkt_sz ;						\
  osc_pkt_sz = osc_build_message ( osc_pkt ,			\
				   osc_pkt_extent ,		\
				   __VA_ARGS__ ) ;		\
  osc_debug_print ( osc_pkt , osc_pkt_sz ) ;

/* Allocate structure storage. */

midi_osc_t *
midi_osc_alloc ( void ) 
{
  midi_osc_t *m = malloc ( sizeof(midi_osc_t) ) ;
  m->host = midi_host_alloc () ;
  return m ;
}

/* Procedure to send an OSC byte string containng the <integer> uid
   and the <byte-vector> pkt.  This procedure is passed to the host
   sub-system.  */

void 
midi_osc_send_packet ( void *PTR , 
		       int uid , 
		       const uint8_t *pkt , int pkt_sz ,
		       int mask )
{
  midi_osc_t *m ;
  m = (midi_osc_t *) PTR ; 

  osc_blob_t b ;
  b.size = pkt_sz ;
  b.data = pkt ;

  OSC_MAKE_PACKET ( "/midi" , ",ib" , uid , b ) ;
  sendto_client_register ( m->fd , m->cr , osc_pkt , osc_pkt_sz , mask ) ; 
}

/* Receive notification of system status changes.  This is sent to the
   host system.  */

void 
midi_osc_notify ( void *PTR , int id )
{
  midi_osc_t *m ;
  m = (midi_osc_t *) PTR ;

  OSC_MAKE_PACKET ( "/change" , ",i" , id ) ;
  sendto_client_register ( m->fd , m->cr , osc_pkt , osc_pkt_sz , RECEIVE_META ) ; 
}

/* Initialize all fields at `m'. */

void
midi_osc_init ( midi_osc_t *m ) 
{
  midi_host_init ( m->host , m , midi_osc_send_packet , midi_osc_notify ) ;
  m->fd = 0 ;
  m->cr = NULL ;
  m->thread = 0 ;

}

/* Setup network socket and client register. */

void
midi_osc_setup ( midi_osc_t *m , int port_number ) 
{
  const int register_extent = 16 ;
  m->cr = alloc_client_register ( register_extent ) ;
  m->fd = socket_udp ( 0 ) ;
  bind_inet ( m->fd , NULL , port_number ) ;
}

/* Handle a status request message.  */

void
midi_osc_status (  midi_osc_t *m , struct sockaddr_in address ) 
{
  OSC_MAKE_PACKET ( "/status.reply" , 
		    ",ii" , 
		    midi_host_number_of_sources ( m->host ) ,
		    midi_host_number_of_destinations ( m->host ) ) ;
  sendto_exactly ( m->fd , osc_pkt , osc_pkt_sz , address ) ; 
}

/* Handle a reset request message. */

void
midi_osc_reset (  midi_osc_t *m , struct sockaddr_in *address ) 
{
  midi_host_reset ( m->host ) ;
  if ( address ) {
    OSC_MAKE_PACKET ( "/reset.reply" , "," ) ;
    sendto_exactly ( m->fd , osc_pkt , osc_pkt_sz , *address ) ; 
  }
  midi_osc_notify ( m , 0 ) ;
  eprintf ( "%s: There are %d sources and %d destinations.\n" , 
	    __func__ ,
	    midi_host_number_of_sources ( m->host ) ,
	    midi_host_number_of_destinations ( m->host ) ) ;
}

/* Reply to address with an /name message giving the device and
   endpoint names. The direction <string> should be either "source" or
   "destination".  */

void
midi_osc_port_name ( midi_osc_t *m , const char *direction , int uid ,
		     struct sockaddr_in address )
{
  const int c_name_extent = 1024 ;
  char c_device_name[c_name_extent] ;
  char c_end_name[c_name_extent] ;
  midi_host_port_name ( m->host , direction[0] == 's' ,  uid , 
			c_device_name , c_end_name , c_name_extent ) ;

  OSC_MAKE_PACKET ( "/name.reply" , ",ss" , c_device_name , c_end_name ) ;
  sendto_exactly ( m->fd , osc_pkt , osc_pkt_sz , address ) ; 
}

/* Open a MIDI client.  */

void
midi_osc_open ( midi_osc_t *m , const char *client_name )
{
  midi_host_open ( m->host , client_name ) ;
}

/* Close a MIDI client.  */

void
midi_osc_close ( midi_osc_t *m )
{
  midi_host_close ( m->host ) ;
}

/*  */

void
midi_osc_send_midi ( midi_osc_t *m , int id , osc_blob_t data )
{
  dprintf ( "%s: %d, %d, %8s\n" , 
	    __func__ , id , data.size , data.data ) ;

  midi_host_send_midi ( m->host , id , data.data , data.size ) ;
}

#define OSC_PARSE_MSG(command,types)				\
  osc_parse_message ( command , types , msg , msg_len , o )

/* Thread to listen for incoming OSC packets. */

static void *
midi_osc_listener ( void *PTR )
{
  midi_osc_t *m ;
  m = (midi_osc_t *) PTR;
  while ( ! observe_end_of_process () ) {
    if ( fd_wait ( m->fd , 500000 ) ) {
      struct sockaddr_in addr ;
      socklen_t addr_len ;
      addr_len = sizeof(addr) ;
      const int msg_extent = 64 ;
      uint8_t msg[msg_extent] ;
      int msg_len ;
      msg_len = xrecvfrom ( m->fd , msg , msg_extent , 0 , 
			    (struct sockaddr *)&addr , &addr_len ) ;
      osc_debug_print ( msg , msg_len ) ;
      dprintf ( "%s: recv: %s , %d\n" , __func__ , msg , msg_len ) ;
      const int o_extent = 4 ;
      osc_data_t o[o_extent] ;
      if ( OSC_PARSE_MSG ( "/receive" , ",i" ) ) {
	edit_client_register ( m->cr , addr , o[0].i ) ;
      } else if ( OSC_PARSE_MSG ( "/receive_at" , ",iis" ) ) {
	struct sockaddr_in ra_addr ;
	init_sockaddr_in ( &ra_addr , o[2].s , (int16_t)o[1].i ) ;
	edit_client_register ( m->cr , ra_addr , o[0].i ) ;
      } else if ( OSC_PARSE_MSG ( "/reset" , "," ) ) {
	midi_osc_reset ( m , &addr ) ;
      } else if ( OSC_PARSE_MSG ( "/status" , "," ) ) {
	midi_osc_status ( m , addr ) ;
      } else if ( OSC_PARSE_MSG ( "/name" , ",si" ) ) {
	midi_osc_port_name ( m , o[0].s , o[1].i , addr ) ;
      } else if ( OSC_PARSE_MSG ( "/midi" , ",ib" ) ) {
	midi_osc_send_midi ( m , o[0].i , o[1].b ) ;
      } else {
	eprintf ( "%s: Dropped packet: %8s\n" , __func__ , msg ) ;
      }
    }
  }
  return NULL ;
}

void
midi_osc_usage (void)
{
  printf ( "Usage: midi.osc [ options ]\n" ) ;
  printf ( "   -p  Set the port number (default=57150).\n" ) ;
  FAILURE ;
}

int 
main ( int argc , char **argv )
{
  byte_order_confirm () ;
  observe_signals () ;
  int port_number = 57150 ;
  int c ;
  while ( ( c = getopt ( argc , argv , "hp:" ) ) != -1 ) {
    switch ( c ) {
    case 'h':
      midi_osc_usage () ;
      break;
    case 'p':
      port_number = (int) strtol ( optarg , NULL , 10 ) ;
      break;
    default:
      midi_osc_usage () ;
      break;
    }
  }
  midi_osc_t *m ;  
  m = midi_osc_alloc () ;
  midi_osc_init ( m ) ;
  midi_osc_setup ( m , port_number ) ;
  midi_osc_open ( m , "midi.osc" ) ;
  midi_osc_reset ( m , NULL ) ;
  pthread_create ( &(m->thread) , NULL , midi_osc_listener , m ) ; 
  pthread_join ( m->thread , NULL ) ;
  midi_osc_close ( m ) ;
  free_client_register ( m->cr ) ;
  return 0 ;
}
