/* **********************************************************************
 * *                                                                    *
 * *     Read the TDWR UDP stream and store rays in shared memory       *
 * *                                                                    *
 * **********************************************************************
 * File: utils/tdwr/tdwr_reading_task.C
 *
 *                        COPYRIGHT (c) 2006  BY
 *              VAISALA INC., WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */

#include <arpa/inet.h>
#include <errno.h>
#include <net/if.h>
#include <rpc/rpc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef LINUX
#include <strings.h>
#endif
#include <sys/ioctl.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#ifndef LINUX
#include <arpa/nameser.h>  /* Required for BYTE_ORDER on HP and SGI, must be late */
#endif  /* #ifndef LINUX */


#include "sigtypes.h"
#include "user_lib.h"

#define TDWR_ALLOCATE 1
#include "tdwr_interface.h"
#include "client.h"
#include "udp_pac.h"
#include "frag_pac.h"

#define MAX_TDWR_MSG_BYTES 2008

/* Global declarations */
CMD_ARGS      u_args;
UDP_DI        udp_di;

struct sigaction action;
int    send_msg;
int    Ethernet_qid;
int    pid;
int    answer;
static int msg_bytes = 0;
static unsigned char frag_bfr[1500];
static int seek_flag = FALSE;
static unsigned char *msg_bfr = (unsigned char *)NULL;
char   tdwr_interface[10], tdwr_port[10];
static char   log_str[200];

static int process_Args( int argc, char **argv, CMD_ARGS *c_args );
static int udp_InitIfaceClient( const char *interface, int port, UDP_DI *pUdpDi_a );
static void close_Sockets(void);
static int read_Fragments( int in_socket, int read_byte_max, int *bytes_sent );
static void swap_frag_hdr( struct frag_hdr *pHdr_a );
static int process_data(void );

static void swap_tdwr_hdr( struct tdwr_hdr *pHdr_a );
static void swap_buf_hdr( struct buf_hdr *pHdr_a );
static void swap_norm_data( struct tdwr_buf_norm *pHdr_a );
static void swap_normal_sample( struct normal_sample *pSample_a );

static void sighup_handler(int signal_a);

int main( int argc, char **argv )
{
  int p_stat, udp_stat;
  int msg_bytes_sent;
  int i;

  logging = 0;
  action.sa_handler = sighup_handler;
  sigemptyset( &action.sa_mask );
  action.sa_flags = 0;
  answer = sigaction( SIGHUP, &action, NULL );

  sleep(5);

  /* Process all user arguments */
  p_stat = process_Args( argc, argv, &u_args );
  if(p_stat<0)
  {
    sprintf(log_str, "(udpServer) Bad status processing command arguments, status=%d", p_stat);
    Lprintf(log_str);
    exit(-1);
  }
  else if(p_stat>0) exit(0);

  udp_stat=udp_InitIfaceClient( u_args.iface_str, u_args.port, &udp_di );
  if(udp_stat < 0 )
    {
      sprintf(log_str, "(udpServer) Error while establishing UDP client via interface %s", u_args.iface_str);
      Lprintf(log_str);
      exit(-7);
    }

  sprintf(log_str, "UDP bind succeeded on port %d, via interface %s.", u_args.port, u_args.iface_str);
  Lprintf(log_str);


  /* setup the ipc queue  and shared memory between this task and the raw_create_task */
  Ethernet_qid = msgget(234, (IPC_CREAT|0777));
  global_memory_attach();
  for (i = 0; i < 300; i++) dwell_table->node[i] = 0;

  /* Main loop of the program.  Read a series of packets, condense into a ray of information,
     then call process_data to actually send it to the raw_create_task */

  while( (read_Fragments( udp_di.socket, MAX_TDWR_MSG_BYTES, &msg_bytes_sent)) >= 0 )
    {
      process_data();
    }

  /* Close all sockets */
  close_Sockets();
  exit(0);
}


static int process_Args( int argc, char **argv, CMD_ARGS *c_args )
{
  FILE *config_file;
  int  i, answer;
  char tmp_str[100], scan_count[10], lprf_num[10];
  char iris_name[50], iris_port[10], iris_dir[50];

  /* Initialize user args as defaults */
  c_args->port_str       = DEF_PORT_STR;
  c_args->port           = DEF_PORT;
  c_args->iface_str      = DEF_IFACE_STR;
  c_args->addr_str       = DEF_ADDR_STR;
  c_args->out_str        = DEF_OUT_STR;
  c_args->rcvbuf_str     = DEF_RCVBUF_STR;
  c_args->rcvbuf_size    = DEF_RCVBUF_SIZE;
  c_args->length_str     = DEF_LENGTH_STR;
  c_args->length         = DEF_LENGTH;
  c_args->delay_usec_str = DEF_DELAY_USEC_STR;
  c_args->delay_usec     = DEF_DELAY_USEC;
  c_args->hex_str        = DEF_HEX_STR;
  c_args->hex_limit      = DEF_HEX_LIMIT;
  c_args->hdr_print      = DEF_HDR_PRINT;
  c_args->delta_print    = DEF_DELTA_PRINT;
  c_args->bytes_print    = DEF_BYTES_PRINT;
  c_args->brief_print    = DEF_BRIEF_PRINT;

  /*****
    Process the configuration file
  *****/
  if (argc != 2)
  {
    printf("usage: tdwr_reading_task <config file name>\n");
    exit(0);
  }

  if ((config_file = fopen(argv[1], "r")) == NULL)
  {
    sprintf(log_str, "Can not open config file: %s\n", argv[1]);
    printf(log_str);
    exit(-1);
  }

  answer = fscanf(config_file, "%s%s", tmp_str, log_str);
  if (answer <= 0 || strcmp(tmp_str, "LOG"))
  {
    printf("No valid LOG line!\n");
    exit(-2);
  }
  if (log_str[0] == '1') 
  {
    const char *sPath="/tmp/tdwr_reading_task.log";
    if ((log_file = fopen( sPath, "w")) == NULL)
    {
      fprintf(stderr,"Can not open log file: %s!\n", sPath );
      exit(-2);
    }
    else 
    {
      logging = 1;
      printf("Logging to file: %s\n", sPath );
    }
  }

  answer = fscanf(config_file, "%s%s%s", tmp_str, tdwr_interface, tdwr_port);
  if (answer <= 0 || strcmp(tmp_str, "TDWR"))
  {
    printf("No valid TDWR / interface / port listed.\n");
    Lprintf("No valid TDWR / interface / port listed.");
    exit(-2);
  }
  else
  {
    c_args->iface_str = tdwr_interface;
    c_args->port_str = tdwr_port;
    c_args->port = atoi(tdwr_port);
  }

  answer = fscanf(config_file, "%s%s%s%s", tmp_str, iris_name, iris_port, iris_dir);
  if (answer <= 0 || strcmp(tmp_str, "IRIS"))
  {
    printf("No valid IRIS server / port / dir listed.\n");
    Lprintf("No valid IRIS server / port / dir listed.");
    exit(-2);
  }

  answer = fscanf(config_file, "%s%s%s", tmp_str, scan_count, lprf_num);
  if (answer <= 0 || strcmp(tmp_str, "MONITOR"))
  {
    printf("No valid MONITOR scan.\n");
    Lprintf("No valid MONITOR scan.");
    exit(-2);
  }
  else
  {
    for (i=0; i<atoi(scan_count); i++)
    {
      fscanf(config_file, "%s", tmp_str);
    }
  }

  answer = fscanf(config_file, "%s%s%s", tmp_str, scan_count, lprf_num);
  if (answer <= 0 || strcmp(tmp_str, "HAZARDOUS"))
  {
    printf("No valid HAZARDOUS scan.\n");
    Lprintf("No valid HAZARDOUS scan.");
    exit(-2);
  }
  else
  {
    for (i=0; i<atoi(scan_count); i++)
    {
      fscanf(config_file, "%s", tmp_str);
    }
  }

  fclose(config_file);

  /* Immediately grab the executable name as entered by user */
  c_args->process_name = *argv;

  return( 0 );
}

static void close_Sockets(void)
{
  /* Close all open sockets */
  if(udp_di.socket>0) close( udp_di.socket );
}

/* =============================================================================
 * Read_Fragments is the main subroutine of this task.  Here we read packets one at 
 * a time using the recvfrom system call.  Packets are placed end-to-end into the
 * current active shared memory buffer.  When a complete ray is in, then return.
 */
static int read_Fragments( int in_socket, int read_byte_max, int *bytes_sent )
{
  static FRAG_HDR old_fhdr = { (unsigned long)-1, 0, 0, 0, 0, NULL_OPTION };
  
  struct sockaddr_in from_name;
#ifdef LINUX
  socklen_t fromlen;
#else
  int fromlen;
#endif
  int recv_bytes;
  int bytes_received = 0;
  int ii;
  
  send_msg = 0;
  for (ii = 299; ii >= 0; ii--)
    {
      if (!dwell_table->node[ii]) 
	{
	  msg_bfr = (unsigned char*)(dwell_p+ii);
	  dwell_table->node[ii] = 1;
	  send_msg = ii;
	  break;
	}
    }

  *bytes_sent = 0;

  /* Loop until a message has been constructed */
  while( (recv_bytes=recvfrom( in_socket, frag_bfr, read_byte_max, 0,
         (struct sockaddr *)(void*)&from_name, &fromlen)) >= 0 )
  {
    if( recv_bytes > (int)SIZEOF_FRAG_HDR )
    {
      struct frag_hdr new_fhdr;
      /* Copy in the new fragment header */
      memcpy( (char *)&new_fhdr, frag_bfr, SIZEOF_FRAG_HDR );

      swap_frag_hdr( &new_fhdr );

      /* If still seeking a new message, continue searching for a first fragment */
      if( seek_flag && new_fhdr.frag_nmbr != 1 ) continue;

      /* Reset the seek flag */
      seek_flag = FALSE;

      /* Extract the full message length */
      *bytes_sent = new_fhdr.msg_bytes;
      /* If fragment has been lost midway in a fragment run, start skipping new
        records until a first-fragment appears ... pass back partial message. */

      if( msg_bytes>0 && (new_fhdr.frag_seqn != old_fhdr.frag_seqn+1) )
      {
        seek_flag = TRUE;
        bytes_received = msg_bytes;
        msg_bytes = 0;
        break;
      }

      /* If the first fragment of a new message is lost, also start skipping
         but proceed with reading immediately */
      else if( msg_bytes==0 && new_fhdr.frag_nmbr != 1 )
      {
        seek_flag = TRUE;
        msg_bytes = 0;
        continue;
      }

      /* Process the new fragment */
      else
      {
        /* Copy the new fragment to the message space */
        memcpy( (char *)&msg_bfr[new_fhdr.frag_offset], &frag_bfr[SIZEOF_FRAG_HDR], new_fhdr.data_bytes);

        msg_bytes += new_fhdr.data_bytes;

        /* If this message is complete, pass back message info */
        if( new_fhdr.frag_nmbr == new_fhdr.frag_count )
        {
          bytes_received = msg_bytes;
          msg_bytes = 0;
          break;
        }
        /* Otherwise, save the fragment header */
        else
        {
          old_fhdr = new_fhdr;
        }
      }
    }
  }
  /* If a bad read was encountered, pass back an error status code */
  if( recv_bytes < 0 ) bytes_received = -1;
  return( bytes_received );
}
/* ============================================================
 * Swap the frag header to the native machine 
 */
static void swap_frag_hdr
( struct frag_hdr *pHdr_a )
{
  /* Only swap if required */
  if (BYTE_ORDER==BIG_ENDIAN) return;

  SwapU4( &pHdr_a->frag_seqn  );
  SwapU2( &pHdr_a->msg_bytes  );
  SwapU2( &pHdr_a->frag_count );
  SwapU2( &pHdr_a->frag_nmbr  );
  SwapU2( &pHdr_a->frag_offset);
  SwapU2( &pHdr_a->data_bytes );
  SwapU2( &pHdr_a->options    );

  return;

}

/* =============================================================
 * Initialize the UDP socket for the reception of broadcast data 
 */
static int udp_InitIfaceClient
( const char *interface, int port, UDP_DI *pUdpDi_a )
{
  char local_host[MAXHOSTNAMELEN];
  int loop;
  int found_if;
  struct ifconf ifc;
  struct ifreq *ifr;
  char buf[BUFSIZ];

  /* First, validate the interface name and the port number */
  if( port<MIN_IP_PORT || port>MAX_IP_PORT )
  {
    sprintf(log_str, "(udp_InitIfaceClient) Illegal port number requested = %d", port);
    Lprintf(log_str);
    return( -1 );
  }
  else if( interface == (char *)NULL )
  {
    Lprintf("(udp_InitIfaceClient) Null interface name");
    return( -2 );
  }

  /* Flush the client socket address */
  bzero( &pUdpDi_a->dst_addr, sizeof(struct sockaddr_in) );

  /* Get the name of the local host */
	
  if( (gethostname(local_host, MAXHOSTNAMELEN)) < 0 )
  {
    Lprintf("(udp_InitIfaceClient) Unable to determine local hostname.");
    return( -3 );
  }

  /* The destination "client" name is the host name */
  strcpy( pUdpDi_a->dst_host, local_host );

  /* Open a socket that uses UDP protocol */
  if( (pUdpDi_a->socket=socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
  {
    Lprintf("(udp_InitIfaceClient) Cannot open UDP datagram socket");
    return( -4 );
  }

  /* Checkout the network support of the requested UDP method */
  ifc.ifc_len = sizeof( buf );
  ifc.ifc_buf = buf;
  if( (ioctl(pUdpDi_a->socket, SIOCGIFCONF, &ifc)) != 0 )
  {
    sprintf(log_str, "(udp_InitIfaceClient) Could not acquire interface configuration, errno=%d", errno);
    Lprintf(log_str);
    return( -5 );
  }

  /* Loop through the network list to verify the requested interface */
  for( ifr=ifc.ifc_req, found_if=FALSE, loop=ifc.ifc_len/sizeof(struct ifreq) ; --loop>=0 ; ifr++ )
  {
    if( (ioctl(pUdpDi_a->socket, SIOCGIFFLAGS, (char *)ifr)) != 0 )
    {
      sprintf(log_str, "(udp_InitIfaceClient) Error getting interface (%s) configuration, errno=%d", ifr->ifr_name, errno);
      Lprintf(log_str);
      continue;
    }

    if(strcmp(ifr->ifr_name,interface)==0)
    {
      found_if = TRUE;
      break;
    }
  }

  /* Verify the capabilities of the requested network interface */
  if(found_if)
  {
    if( ! ifr->ifr_flags & IFF_UP )
    {
      sprintf(log_str, "(udp_InitIfaceClient) Network %s is not up.", interface);
             close( pUdpDi_a->socket );
      Lprintf(log_str);
      return( -6 );
    }
    else if( ! ifr->ifr_flags & IFF_RUNNING )
    {
      sprintf(log_str, "(udp_InitIfaceClient) Network %s is not running.", interface);
      Lprintf(log_str);
      close( pUdpDi_a->socket );
      return( -7 );
    }
  }
  else
  {
    sprintf(log_str, "(udp_InitIfaceClient) %s is not connected to %s network.", 
	    local_host, interface);
    Lprintf(log_str);
    close( pUdpDi_a->socket );
    return( -8 );
  }

  /* Fill in the socket name (family, port, internet address) */
  pUdpDi_a->dst_addr.sin_family = AF_INET;
  pUdpDi_a->dst_addr.sin_port = htons(port);
  pUdpDi_a->dst_addr.sin_addr.s_addr = htonl(INADDR_ANY);

  /* Bind the socket, making this address known to the system */
  if( bind(pUdpDi_a->socket, (struct sockaddr *)(void*)&pUdpDi_a->dst_addr,
	   sizeof(struct sockaddr_in) ) < 0 )
  {
    sprintf(log_str, "(udp_InitIfaceClient)Error binding socket {%d, %d, %s}, errno=%d.",
    pUdpDi_a->dst_addr.sin_family, pUdpDi_a->dst_addr.sin_port, inet_ntoa(pUdpDi_a->dst_addr.sin_addr),
    errno);
    Lprintf(log_str);
    return( -9 );
  }

  return( 0 );
}

/* ==============================================================================
 * Once we got a complete ray, if it is a tdwr data ray, and is either a signal dwell
 * or a sector blanked dwell, then inform the raw_create_task via ipc message queue.
 */
static int process_data(void )
{
  struct mail_ptr mail_ptr;
  union  tdwr_buf *msg_bfr;
  static int iRayCount=0;

  if (!send_msg) return 0;

  msg_bfr = dwell_p+send_msg;
  swap_tdwr_hdr( &msg_bfr->tdwr_hdr );

  if ((msg_bfr->tdwr_hdr.msg_id == 0x2B00) ||
      (msg_bfr->tdwr_hdr.msg_id == 0x2B01))
    {
      swap_buf_hdr( &msg_bfr->tdwr_buf_norm.hdr );

      /* Print log for each start of elevation dwell */
      if (msg_bfr->tdwr_buf_norm.hdr.iFlags8&HDR_FLG8_SOE)
	{
	  unsigned long iScanNumber, iSov;
	  iScanNumber = (msg_bfr->tdwr_buf_norm.hdr.iFlags8&HDR_MSK8_SCAN_NUMBER) >>
	    HDR_SHFT8_SCAN_NUMBER;
	  iSov = 0 != (msg_bfr->tdwr_buf_norm.hdr.iFlags2&HDR_FLG2_SOV);

	  sprintf( log_str, "Ray #%d, msg_id=0x%04X, SOV:%d, Scan:%d, El:%3.2f", iRayCount,
		   msg_bfr->tdwr_hdr.msg_id, iSov, iScanNumber,
		   msg_bfr->tdwr_buf_norm.hdr.current_elevation );
	  Lprintf(log_str);
	}

      unsigned short idwell_id =
	(msg_bfr->tdwr_buf_norm.hdr.iFlags22 & HDR_MSK22_DWELL_ID)>>HDR_SHFT22_DWELL_ID;
      if (!(msg_bfr->tdwr_buf_norm.hdr.iFlags6 & HDR_FLG6_DUMMY_RECORD) && 
	  ((idwell_id == 0) || (idwell_id == 2)) &&
	  ((msg_bfr->tdwr_buf_norm.hdr.scan_strategy == 1) || 
	   (msg_bfr->tdwr_buf_norm.hdr.scan_strategy == 2)) )
	
	{
	  int iError;
	  /* Swap normal dwell data */
	  if (msg_bfr->tdwr_hdr.msg_id == 0x2B00)
	    swap_norm_data( &msg_bfr->tdwr_buf_norm );

	  mail_ptr.mtype = 1;
	  mail_ptr.index = send_msg;
	  iError = msgsnd(Ethernet_qid,&mail_ptr,4,1);
	  if (iError == -1) 
	    {
	      dwell_table->node[send_msg] = 0;
	      sprintf(log_str, "msgsnd index %d, failed errno: %d.", send_msg, errno );
	      Lprintf(log_str);
	    }
	  else
	    {
	      /* sprintf(log_str, "msgsnd index %d, sent.", send_msg );
	       * Lprintf(log_str);
	       */
	    }
	}
      else 
	{
	  dwell_table->node[send_msg] = 0;
	}
    } 
  else
    {
      sprintf( log_str, "Ray #%05d, msg_id=0x%04X", iRayCount, msg_bfr->tdwr_hdr.msg_id );
      Lprintf(log_str);

      dwell_table->node[send_msg] = 0;
    }

  iRayCount++;

  return(0);
}
/* ============================================================
 * Swap the tdwr header to the native machine 
 */
static void swap_tdwr_hdr
( struct tdwr_hdr *pHdr_a )
{
  /* Only swap if required */
  if (BYTE_ORDER==BIG_ENDIAN) return;

  SwapU2( &pHdr_a->msg_id  );
  SwapU2( &pHdr_a->msg_len );
  return;
}

/* ============================================================
 * Swap the buf header to the native machine 
 */
static void swap_buf_hdr
( struct buf_hdr *pHdr_a )
{
  /* Only swap if required */
  if (BYTE_ORDER==BIG_ENDIAN) return;

  SwapU2( &pHdr_a->volume_scan_count );
  // swap1( &pHdr_a->iFlags2        );  /* Single byte data does not need swapping */
  // swap1( &pHdr_a->scan_strategy  );
  SwapU2( &pHdr_a->peak_power        );
  SwapU2( &pHdr_a->iFlags6           );
  SwapU4( &pHdr_a->iFlags8           );

  SwapF4( &pHdr_a->current_elevation );
  SwapF4( &pHdr_a->angular_scan_rate );

  SwapU2( &pHdr_a->pri               );
  SwapU2( &pHdr_a->iFlags22          );
  SwapU2( &pHdr_a->final_range_sample);
  SwapU2( &pHdr_a->range_samples_per_dwell);

  SwapF4( &pHdr_a->current_azimuth   );
  SwapF4( &pHdr_a->total_noise_power );

  SwapS4( &pHdr_a->timestamp         );
  SwapU2( &pHdr_a->base_data_type    );
  SwapU2( &pHdr_a->iFlag42           );
  SwapU2( &pHdr_a->integer_azimuth   );
  SwapU2( &pHdr_a->load_short_final_sample);

  return;
}

/* ============================================================
 * Swap the data from a normal ray.  The header is already swapped.
 */
static void swap_norm_data
( struct tdwr_buf_norm *pBuf_a )
{
  /* Only swap if required */
  if (BYTE_ORDER==BIG_ENDIAN) return;
  int iBin;
  for (iBin=0; iBin<600; iBin++)
    {
      swap_normal_sample( &pBuf_a->normal_sample[iBin] );
    }

  return;
}

/* ============================================================
 * Swap the one range bin of normal sample data.
 */
static void swap_normal_sample
( struct normal_sample *pSample_a )
{
  /* Swap conditional already tested */

  SwapU2( &pSample_a->V );
  SwapU2( &pSample_a->VC );

  return;
}

/* =======================================
 * Ignore all sighup signals 
 */
static void sighup_handler(int signal_a)
{
  action.sa_handler = sighup_handler;
  sigemptyset( &action.sa_mask );
  action.sa_flags = 0;
  sigaction( SIGHUP, &action, NULL );
}
