/* **********************************************************************
 * *                                                                    *
 * *      Interface to Read Satellite images into IRIS                  *
 * *           Only for use on the SeaLaunch system                     *
 * **********************************************************************
 * File: utils/custom/iris_sat_interface.c
 *
 *             COPYRIGHT (c) 1998, 1999, 2000, 2001, 2002  BY
 *          SIGMET INCORPORATED, 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <math.h>

#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netdb.h>
#include <signal.h>
#include <unistd.h>

#include "sigtypes.h"
#include "headers.h"
#include "output.h"
#include "product.h"
#include "dsp.h"
#include "dsp_lib.h"
#include "user_lib.h"
#include "misc_lib.h"

static void ProcessFile
(const char *tmp_str );

static void GetTimeFromDirBlock
(struct full_time_spec *pDataTime_a,
 const int dir_block_a[64] );
static void sighup_handler (int signal_a);
static void sigio_handler (int signal_a);

static int iris_s;
static int connected_iris = 0, pending_iris = 0;
void Lprintf(const char *log_str);
static int logging = 0;
static char log_str[200];
static struct sigaction action;
static FILE *log_file;
static char   iris_name[200], iris_dir[PATHNAME_SIZE];
static char   iris_temp_dir[PATHNAME_SIZE];

/*
  The config file is organized as follows and must be in this order:
  LOG     <0 or 1>
  IRIS    <iris_server_NAME> <iris_server_PORT> <iris_server_DIR>
	      (NAME and PORT where we notify, NAME and DIR where we send file to)
  SAT <directory of where to find satellite files>
*/

/*
  Here are some limitation placed upon the input file:
  1) The file must be Mercator projection
  2) The file can have some blank data on the bottom and on the right.
     It has been observed that this is an outcome of converting the
     normal satellite AREA file to a mercator projection.
  3) The data area (after the blanks are stripped) must not exceed
     1100 pixels in either dimention.
*/

int main (int argc, char ** argv)
{
  short  iris_port_num;
  int    answer;
  int    iflag, iaddrsize;
  time_t last_time;
  struct timeval tv0, tv1;
  fd_set afds, bfds;
  char   *pEnv, iris_port[20];
  char   tmp_str[200], sat_name[FILENAME_SIZE], sat_dir[100];
  struct sockaddr_in servaddr_in ;
  struct sockaddr *pservaddr;
  struct hostent *hostptr;
  struct in_addr *paddress;
  FILE   *config_file, *sat_file;
  struct stat stat_buf;

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

  last_time = 0;  

  /*****
    Process the configuration file
  *****/
  if (argc != 2)
  {
    printf("usage: iris_interface <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') 
  {
    if ((log_file = fopen("/tmp/iris_interface.dat", "w")) == NULL)
    {
      printf("Can not open log file: /tmp/iris_interface.dat!\n");
      exit(-2);
    }
    else 
    {
      logging = 1;
      printf("Logging to file: /tmp/iris_interface.dat.\n");
    }
  }

  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.\n");
    exit(-2);
  }

  answer = fscanf(config_file, "%s%s", tmp_str, sat_dir);
  if (answer <= 0 || strcmp(tmp_str, "SAT"))
  {
    printf("No valid satellite dir listed.\n");
    Lprintf("No valid satellite dir listed.\n");
    exit(-2);
  }

  hostptr = gethostbyname((char *)(&iris_name[0]));
  if (hostptr != NULL)
  {
    memset( &servaddr_in, 0, sizeof(servaddr_in));
    servaddr_in.sin_family = AF_INET;
    paddress = (struct in_addr *)hostptr->h_addr_list[0];
    servaddr_in.sin_addr = *paddress;
    iris_port_num = atoi(iris_port);
    servaddr_in.sin_port = htons(iris_port_num);
    pservaddr = (struct sockaddr *)&servaddr_in;
    iaddrsize = sizeof( servaddr_in );
  }
  else
  {
    printf("Error getting hostname: %s\n", iris_name);
    sprintf(log_str, "Error getting hostname: %s\n", iris_name);
    Lprintf(log_str);
    exit(1);
  }
  sprintf(log_str, "Using IRIS server and port: %s %s at dir: %s\n", 
	  iris_name, iris_port, iris_dir);
  Lprintf(log_str);

  if ((pEnv = getenv("IRIS_TEMP")) == NULL)
  {
    strncpy( iris_temp_dir, pEnv, PATHNAME_SIZE-FILENAME_SIZE );
    iris_temp_dir[(PATHNAME_SIZE-FILENAME_SIZE)-1] = 0;
    printf("Error getting env var IRIS_TEMP\n");
    sprintf(log_str, "Error getting env var IRIS_TEMP\n");
    Lprintf(log_str);
    exit(1);
  }

  tv0.tv_sec = 0;
  tv0.tv_usec = 0;
  tv1.tv_sec = 1;
  tv1.tv_usec = 0;

  sprintf(tmp_str, "rm -rf %s/*", sat_dir);
  system(tmp_str);

  /****
     Enter a while forever loop.  This loop runs at a frequency of once per second.
     In this loop we first try to connect to an IRIS server if we are not already
     connected.  Then we check to see if any new satellite files have been posted.
     If so, we read it, turn it into an IRIS user product and send it to the IRIS
     server.
  ****/

  while (1) 
  {
    /****
       One second pause to pace the loop
    ****/
    answer = select(32, NULL, NULL, NULL, &tv1);

    /****
       If we are not connected to the IRIS server and the connection is not
       in the pending state, then try to establish the connection.  Declare 
       the connection to be pending.  We will later check to see if it is
       connected, failed or blocking.
    ****/
    if (!connected_iris && ! pending_iris)
    {
      iris_s = socket(AF_INET, SOCK_STREAM, 0);
      if (iris_s != -1)
      {
        /* Set up so that i/o is nonblocking */
        iflag = 1;
        answer = ioctl( iris_s, FIONBIO, &iflag );
        answer = connect(iris_s, pservaddr, iaddrsize);
        pending_iris = 1;
        sprintf(log_str, "Trying to connect to IRIS server %s\n", iris_name);
        Lprintf(log_str);
      }
      else
      {
	Lprintf("Socket open failed to IRIS server.\n");
      }
    }

    /****
       If the pending connection has been blocked for more than 30 seconds, 
       then disconnect it.
    ****/
    if (pending_iris >= 30)
    {
      sprintf(log_str, "Connection to IRIS server %s failed after several attempts.  Will try fresh.\n", iris_name);
      pending_iris = 0;
      connected_iris = 0;
      close(iris_s);
      iris_s = 0;
    }

    /****
       If the connection is in the pending state, then call "connect".  If connect either
       succeeds or fails with reason already connected, then the connection is good.  At 
       this point, change state to good, and setup the signal handler.  If the connection fails
       but reports in progress (currently block), then stay in state.  If it reports failed,
       then changed state from pending to down.
    ****/
    if (pending_iris)
    {
      FD_ZERO(&afds);
      FD_ZERO(&bfds);
      FD_SET(iris_s, &bfds);
      answer = select(32, NULL, &bfds, NULL, &tv0);
      switch (answer)
      {
        case -1: /* error */	
          sprintf(log_str, "select() returned error (%d)\n", errno);
          Lprintf(log_str);
        break;
        default: /* got something */
          if (FD_ISSET(iris_s, &bfds))
          {
            answer = connect(iris_s, pservaddr, iaddrsize);
            if ((answer >= 0) || (answer < 0 && errno == EISCONN))
            {
              /* Set up a signal handler for the SIGIO signal */
              action.sa_handler = sigio_handler;
              sigemptyset( &action.sa_mask );
              action.sa_flags = SA_RESTART;
              answer = sigaction( SIGIO, &action, NULL );
              /* Set up so that i/o causes the SIGIO signal */
	      {
		pid_t iPid = getpid();
		answer  = fcntl( iris_s, F_SETOWN, iPid );
	      }
#ifdef LINUX
              answer = fcntl( iris_s, F_SETFL, FASYNC );
#else
              iflag = 1;
              answer = ioctl( iris_s, FIOASYNC, &iflag );
#endif
              iflag = 1;
              answer = ioctl( iris_s, FIONBIO, &iflag );
	      connected_iris = 1;
              pending_iris = 0;
	      sprintf(log_str, "Connected successfully to IRIS server %s.\n", iris_name);
              Lprintf(log_str);
            }
	    else if (answer <= 0 && errno == EINPROGRESS)
	    {
              sprintf(log_str, "Still trying connection to IRIS server %s\n", iris_name);
              Lprintf(log_str);
	      pending_iris++;
	    }
            else
            {
              sprintf(log_str, "Connection failed to IRIS server %s with errno = %d\n", iris_name, errno); 
              Lprintf(log_str);
              pending_iris = 0;
              close(iris_s);
              iris_s = 0;
            }
          }
	  else
	  {
            sprintf(log_str, "Still trying connection to IRIS server %s\n", iris_name);
            Lprintf(log_str);
	    pending_iris++;
	  }
        break;
      }
    } /* end if pending_iris */

    /****
       Do a "stat" on the satellite file directory.  The stat will tell us when the
       directory was last accessed.  If it was accessed since we last accessed it
       here, then do an "ls" on the directory to see what file is there.  Open the
       satellite file that is there and process it by turning it into an IRIS user
       product.  Then send the IRIS user product to the IRIS server.  Delete the
       satellite file and the local version of the IRIS user file.
    ****/

    answer = stat(sat_dir, &stat_buf);
    if (stat_buf.st_mtime > last_time)
      {
	last_time = stat_buf.st_mtime;
	sprintf(tmp_str, "ls -1 %s > /tmp/sat.dat", sat_dir);
	system(tmp_str);
	if ((sat_file = fopen("/tmp/sat.dat", "r")) != NULL)
	  {
	    while (fscanf(sat_file, "%s", sat_name) != EOF)
	      {
		sprintf(tmp_str, "%s/%s", sat_dir, sat_name);
		
		/**** wait 1 second to make sure entire file is here ****/
		select(32, NULL, NULL, NULL, &tv1);
		ProcessFile( tmp_str );
	      } /* end while (fscanf(sat_file, "%s", sat_name) != EOF) */
		
	    fclose(sat_file);
	    sprintf(tmp_str, "rm -rf %s/*", sat_dir);
	    system(tmp_str);
	  } /* end if ((sat_file = fopen("/tmp/sat.dat", "r")) != NULL) */
      } /* end if (stat_buf.st_mtime > last_time) */
  } /* end while(1) */
}

/* ==================================
 * Process one input file.
 */
static void ProcessFile
(const char *sPath_a )
{
  int    i, dir_block[64], nav_block[8];
  int    row, col, dat_block_size;
  char   dat_block[2000000], tmp_block[10000];
  char   tmp_str[200];
  char   iris_file_name[FILENAME_SIZE];
  FILE   *area_file, *iris_file;
  struct product_hdr phdr;
  UINT2  iDay, iHour, iMinute, iSecond;
  float  tmp_lon, tmp_lat, pix_per_deg, lat_pix_ratio;
  int    lon_pix_dif, lat_pix_dif;
  struct full_time_spec DataTime;

  if ((area_file = fopen(sPath_a, "r+b")) == NULL)
    {
      Lprintf("Unable to open received satellite file\n");
      return;
    }
 
/* fopen succeeded */
  /****
       The satellite file is structured as a directory block, then a navigation
       block, then a calibration block, then a supplemental block, then a data
       block.  The dir block and nav block has stuff in them that give us
       the charactoristics of the satellite data.  So read them in first.  Then
       we read in, but trash the cal block and the supp block.  Then based on
       what the dir block and nav block tell us, we read in the data block.
  ****/
      
  fread(dir_block, sizeof(int), 64, area_file);
  fread(nav_block, sizeof(int),  8, area_file);
  fclose(area_file);
  
  if (dir_block[33] > 10000)
    {
      Lprintf("Error in area file - dir / nav / cal / supp blocks too large.\n");
      return;
    }
  
  row = dir_block[8];
  col = dir_block[9];
  dat_block_size = row * col;
      
  if (row > 1100 || col > 1100)
    {
      sprintf(log_str, "Error - input dimensions too big. (max is 1100) row=%d  col=%d\n",
	      row, col);
      Lprintf(log_str);
      return;
    }
      
  /**** 
	Re-open the area file to reset it to beginning.  Then skip over
	the cal block and supp block and read in the data block.  Note that 
	dat block words 8 and 9 are the x and y number of pixels in file.
  ****/
  area_file = fopen(sPath_a, "r+b");
  fread(tmp_block, sizeof(char), dir_block[33], area_file);
  fread(dat_block, sizeof(char), dat_block_size, area_file); 
  fclose(area_file);
	  
  /****
       If we have a good area file, then we must now convert it into an IRIS
       user product.  So we take the steps to do that.
  ****/
  
  sprintf(log_str, "Processing satellite file: %s\n", sPath_a);
  Lprintf(log_str);
  
  
  GetTimeFromDirBlock( &DataTime, dir_block );

  unpacktime( DataTime.Ymds.isec, &iDay, &iHour, &iMinute, &iSecond );
  sprintf(iris_file_name, "WX1%04d%02d%02d%02d%02d%02d.USE0000",
	  DataTime.Ymds.iyear, DataTime.Ymds.imon, DataTime.Ymds.iday,
	  iHour, iMinute, iSecond );
  sprintf(tmp_str, "%s%s", iris_temp_dir, iris_file_name);
  iris_file = fopen(tmp_str, "w+b");
  
  HeaderFromScratch( &phdr, &DataTime, "SL, wxradar1    ", "SL, wxradar1    ",
		     "Satellite" ); 

  phdr.pcf.iptype = PROD_USER;


  /****
       The pixel scale is determined by the row and column resolution in the
       dir block times the resolution in the nav block.
  ****/
  phdr.pcf.ixscale = dir_block[12] * nav_block[4] * 100;
  phdr.pcf.iyscale = dir_block[11] * nav_block[4] * 100;
  phdr.pcf.izscale = 0;
  phdr.pcf.ixsize = col;
  phdr.pcf.iysize = row;
  phdr.pcf.izsize = 0;
  phdr.pcf.ixrad = 0;
  phdr.pcf.iyrad = 0;
  phdr.pcf.izrad = 0;
  phdr.pcf.irange_last_v20 = 0;
  phdr.pcf.idata_out = DB_USER;
  phdr.pcf.idata_in  = DB_USER;
  phdr.pcf.iprojection_type = 1;
  phdr.pcf.colors.iflags = (COLOR_SCALE_TOP_SAT | COLOR_SCALE_BOT_SAT);
  phdr.pcf.colors.istart = 500;
  phdr.pcf.colors.istep  = 100;
  phdr.pcf.colors.icolcnt = 16;
  phdr.pcf.colors.iset_and_scale = 0x0101;
  
  /****
       The lat and lon come from the info in the nav block.  Convert these
       to IRIS format
  ****/
  
  /****
       The difference from little image center to center long point is: 
       center long point - (horz offset into big picture + (little image pixels * little image res / 2))
       \_________ center of little image __________/
  ****/
  
  lon_pix_dif = nav_block[2] - (dir_block[6] + (dir_block[9] * dir_block[12] / 2));
  
  /* pix_per_deg: (circumference of earth in km / pixels per km) / degrees in earth */ 
  pix_per_deg = ( ( ((float)nav_block[6] / 1000.0) * 2.0 * FPI_P) / ((float)nav_block[4] / 1000.0) ) / 360.0;
  
  tmp_lon = (float)(nav_block[5] / 10000) +
    (float)((float)((nav_block[5] % 10000) / 100) / 60.0) +
    (float)((float)(nav_block[5] % 100) / 3600.0);
  
  /* lon = center lon in degrees + (lon_pix_dif / pixels per degree) */
  tmp_lon = tmp_lon + ((float)lon_pix_dif / pix_per_deg);
  
  /* now scale lon for IRIS */
  tmp_lon = 360.0 - tmp_lon;
  tmp_lon = tmp_lon / 360.0;
  tmp_lon = tmp_lon * 0xFFFFFFFF;
  tmp_lon += 0.5;
  phdr.end.ilon_center = (BIN4)tmp_lon; 
  
  /* lat_pix_dif: center lat point - (vert offset into big picture + (little image pixels * little image res / 2)) */
  lat_pix_dif = nav_block[1] - (dir_block[5] + (dir_block[8] * dir_block[11] / 2));
  
  /* lat_pix_ratio: pixel offset from equator to vert cent of little image in pixels / radius of earth in km*/
  lat_pix_ratio = fabs((float)lat_pix_dif / ((float)nav_block[6] / 1000.0));
  
  /* given the pixel ratio to the center of the little image, find its latitude */
  tmp_lat = 2.0 * (atan(exp(lat_pix_ratio)) - (FPI_P/4.0));
  tmp_lat = tmp_lat * 180.0 / FPI_P;
  
  /* we are south latitude when lat_pix_dif is less than 0) */
  if (lat_pix_dif < 0.0) tmp_lat = 360.0 - tmp_lat;
  tmp_lat = tmp_lat / 360.0;
  tmp_lat = tmp_lat * 0xFFFFFFFF;
  tmp_lat += 0.5;
  phdr.end.ilat_center = (BIN4)tmp_lat;
  
  
  /****
       Now we write the data of the user file to disk.
  ****/
  
  fwrite(&phdr, sizeof(struct product_hdr), 1, iris_file);
  
  for(i=row-1; i>=0; i--)
    {
      fwrite(&dat_block[i*col], 1, col, iris_file);  
    }
  fclose(iris_file);
  
  /****
       Now that we have the user file, send it to IRIS!
  ****/
  if (connected_iris)
    {
      sprintf(tmp_str, "rcp %s%s operator@%s:%s", iris_temp_dir,
	      iris_file_name, iris_name, iris_dir);
      system(tmp_str);
      sprintf(tmp_str, "FILE %s", iris_file_name);
      send(iris_s, tmp_str, strlen(tmp_str)+1, 0);
    }
  sprintf(tmp_str, "rm -f %s%s", iris_temp_dir, iris_file_name);
  system(tmp_str);
  
}

/* ======================================================
 * Get the time information.
 */
static void GetTimeFromDirBlock
(struct full_time_spec *pDataTime_a,
 const int dir_block_a[64] )
{
  int    imon, imday, buf_yday, break_flag;
  struct tm in_time, out_time;

  buf_yday = dir_block_a[3] % 1000 - 1;
  in_time.tm_year = dir_block_a[3] / 1000;
  in_time.tm_sec = dir_block_a[4] % 100;
  in_time.tm_min = (dir_block_a[4] % 10000) / 100;
  in_time.tm_hour = dir_block_a[4] / 10000;
  
  /****
       We know the julian day of the year (buf_yday), but we must
       convert this into a month and day.  This algorithm does that.
  ****/
  break_flag = 0;
  for (imon = 0; imon < 12; imon++)
    {
      in_time.tm_mon = imon;
      for (imday = 1; imday < 32; imday++)
	{
	  time_t tmp_time;
	  in_time.tm_mday = imday;
	  tmp_time = mktime(&in_time);
	  gmtime_r(&tmp_time, &out_time );
	  if (out_time.tm_yday == buf_yday)
	    {
	      break_flag = 1;
	      break;
	    }
	}
      if (break_flag) break;
    }
  
  /****
       We must come up with a date and time for the internal structures.  
       The date and time come from the 
       dir block of the area file
  ****/
  if (out_time.tm_year >= 100) out_time.tm_year -= 100;

  memset( pDataTime_a, 0, sizeof(*pDataTime_a) );

  pDataTime_a->Ymds.iyear = in_time.tm_year + 1900;
  pDataTime_a->Ymds.imon  = out_time.tm_mon + 1;
  pDataTime_a->Ymds.iday  = out_time.tm_mday;
  pDataTime_a->Ymds.isec  = (in_time.tm_hour * 3600) + (in_time.tm_min * 60) + 
    in_time.tm_sec;
  /* Recorded UTC */
  pDataTime_a->Ymds.imills |= YMDS_FLG_UTC; 
  pDataTime_a->iMinutesWest = 0;

  /* No local time knowledge, so assume UTC */
  pDataTime_a->iLocalWest = 0;
  strcpy( pDataTime_a->sLocalTZName, "UTC" );
  return;
} 

static void sighup_handler(int signal_a)
{
  action.sa_handler = sighup_handler;
  sigemptyset( &action.sa_mask );
  action.sa_flags = SA_RESTART;
  sigaction( SIGHUP, &action, NULL );
}

static void sigio_handler( int signal_a )
{
  struct sigaction action;
  char data[100];
  int isize;

  isize = recv( iris_s, data, 99, 0 );
  if (isize > 0)
  {
    /* We got some valid data here.  Add terminating NULL, then process */
    data[isize] = 0;
    if (0 == strcmp( data, "NOP" ))
    {
    }
    else if (0 == strcmp( data, "EXIT" ))
    {
    }
    else
    {
      sprintf(log_str, "recv read unknown message: '%s' from IRIS server.\n", data );
      Lprintf(log_str);
    }
  }
  else if (isize < 0)
  {
    if (errno != EWOULDBLOCK)
    {
      sprintf(log_str, "recv error errno = %d from IRIS server.\n", errno);
      Lprintf(log_str);
    }
  }
  else
  {
    /* Zero size indicates that the socket has been closed at the other end */
    Lprintf ("Link broken to IRIS server - closing connection.\n");
    action.sa_handler = SIG_IGN;
    sigemptyset( &action.sa_mask );
    action.sa_flags = SA_RESTART;
    sigaction( SIGIO, &action, NULL );
    connected_iris = 0;
    pending_iris = 0;
    close(iris_s);
    iris_s = 0;
  }
  return;
}

void Lprintf(const char *log_str)
{
  if (!logging) return;
  fprintf(log_file, "%s", log_str);
  fflush(log_file);
  return;
}
