/*
 * Program name:  equake
 * Version:	  1.03.8
 * Source file:	  equake.c (main source file)
 * Description:	  Equake pulls data about earthquakes and displays it in interesting ways
 *
 * Copyright (C) 2012-2015 Jeroen van Aart
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the 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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pwd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>

#include <config.h>

#include <gtk/gtk.h>
#include <glib-object.h>
#include <string.h>
#include <libxfce4util/libxfce4util.h>
#include <libxfce4ui/libxfce4ui.h>
#include <libxfce4panel/xfce-panel-plugin.h>
#include <libxfce4panel/xfce-hvbox.h>

/* GdkPixbuf image defines */
#include "equake_dat.h" /* contains all structures */
#include "equake_func.h" /* contains all function prototypes used in this program */
#include "equake_images.h"


void setdefaults(struct Equake_Data *equakedata)
{
  /* get user's home directory */
  strncpy(equakedata->home, getenv("HOME"), strlen(getenv("HOME")));

  strncpy(equakedata->sigfile, equakedata->home, strlen(equakedata->home));
  strncat(equakedata->sigfile, SIGFILE, strlen(SIGFILE));

  strncpy(equakedata->configfile, equakedata->home, strlen(equakedata->home));
  strncat(equakedata->configfile, CONFIGFILE, strlen(CONFIGFILE));

  /* preset defaults */
  equakedata->quake_history_count=-1;
  equakedata->quake_history_countdaily=-1;
  equakedata->poll_time=POLLTIME; /* hourly reports are updated every few minutes for CA, every 30 minutes worldwide */
  equakedata->will_alert=1; /* by default we will alert about heavy earthquakes, so it's set to 1 */
  equakedata->alert_heavy=ALERTHEAVY;
  equakedata->sigfilemag=SIGFILEMAG;
  equakedata->monitormag=MONITORMAG;
  equakedata->pos=0;
  equakedata->posperiod=HOURLY;

  /* preset properties_index according to default preferences */
  equakedata->properties_index[0]=3; /* polltime index, 5 minutes */
  equakedata->properties_index[1]=1; /* willalert index, yes */
  equakedata->properties_index[2]=3; /* alertheavy index, magnitude 6 */
  equakedata->properties_index[3]=3; /* sigfilemag index, magnitude 4 */
  equakedata->properties_index[4]=0; /* monitormag index, magnitude 1 */

  /* preload image for preferences window */
  equakedata->equake_about=gdk_pixbuf_new_from_inline(-1, equakeabout, FALSE, NULL);
   
  strncpy(equakedata->quakeserver, QUAKESERVER, strlen(QUAKESERVER)); /* should never change unless hostname changes */ 
  strncpy(equakedata->quakepath_ww, QUAKEPATH_WW, strlen(QUAKEPATH_WW)); /* path to worldwide earthquakes */
  strncpy(equakedata->quakepath_dyfi_be, QUAKEPATH_DYFI_BE, strlen(QUAKEPATH_DYFI_BE)); /* path to did you feel it maps */
  strncpy(equakedata->quakepath_dyfi_af, QUAKEPATH_DYFI_AF, strlen(QUAKEPATH_DYFI_AF)); /* path to did you feel it maps */
  strncpy(equakedata->quakefile_hourly, QUAKEFILE_HOURLY, strlen(QUAKEFILE_HOURLY)); 
  strncpy(equakedata->quakefile_daily, QUAKEFILE_DAILY, strlen(QUAKEFILE_DAILY));
  strncpy(equakedata->quakefile_weekly, QUAKEFILE_WEEKLY, strlen(QUAKEFILE_WEEKLY));
  strncpy(equakedata->formatstring, FORMATSTRING, strlen(FORMATSTRING));

  equakedata->report_prev_eqid=strndup("000000", strlen("00000000"));
  equakedata->sigfile_prev_eqid=strndup("000000", strlen("00000000"));

  /* get the urls for hourly, daily and weekly reports */
  snprintf(equakedata->httprequest, HTTPREQ, "GET /%s HTTP/1.1\nHost: %s\n\n", equakedata->quakefile_hourly, equakedata->quakeserver);
  snprintf(equakedata->httprequestdaily, HTTPREQ, "GET /%s HTTP/1.1\nHost: %s\n\n", equakedata->quakefile_daily, equakedata->quakeserver);
  snprintf(equakedata->httprequestweekly, HTTPREQ, "GET /%s HTTP/1.1\nHost: %s\n\n", equakedata->quakefile_weekly, equakedata->quakeserver);

  /* we open an http connection */
  equakedata->portno=PORTNO;
  strncpy(equakedata->portname, PORTNAME, strlen(PORTNAME));

  equake_setprefs(equakedata);

  if (getquakedata(equakedata, FIRST)>0)
    if (processdata(equakedata, HOURLY)==-1)
      perror("Failed to process data"); /* non fatal error, hopefully */
}


/* 
 * not really a loop anymore but left it that way for "historical" reasons
 * this function is called every equakedata->poll_time which is declared in 
 * g_timeout_add() in startequake()
 */
int eventloop(struct Equake_Data *equakedata)
{
  /* lets refresh preferences, if user has changed it, we will now use the new values */
  equake_getprefs(equakedata);

  if (getquakedata(equakedata, HOURLY)>0)
    if (processdata(equakedata, HOURLY)==-1)
      perror("Failed to process data"); /* non fatal error, hopefully */

  return 1;
}


/* print error message */
void error(char *msg, struct Equake_Data *equakedata)
{
  perror(msg);
}


int receiveall(int socket, char *buf, int len, int period)
{
  int numread;

  if (period==FIRST)
    numread=recv(socket, buf, len, 0);
  else
  /* MSG_WAITALL should ensure we retrieve all data, or up to the size of buf */
    numread=recv(socket, buf, len, MSG_WAITALL);

  return numread;
}


/* In case of partial send make sure everything is sent or give an error */
int sendall(int socket, char *buf, int *len)
{
  int total=0; 
  int bytesleft=*len; 
  int n;

  while(total<*len)
  {
    n=send(socket, buf+total, bytesleft, 0);
    if (n<=0)
    {
      if ((n==-1) && (errno==EINTR)) /* interrupted, restart send() */
	continue;
      if (n==-1)
	break;
    }
    total+=n;
    bytesleft-=n;
  }

  *len=total; // return number actually sent here

  return n==-1?-1:0; // return -1 on failure, 0 on success
} 


/*
 * pull earthquake data from website
 * the data is in CSV format (Comma separated ASCII text)
 */
int getquakedata(struct Equake_Data *equakedata, int period)
{
  int len, rv;
  struct addrinfo hints, *servinfo, *p;
  struct timeval timeout;
    if (period!=WEEKLY)
      timeout.tv_sec = SOCKTIMEOUT;
    else
      timeout.tv_sec = SOCKTIMEOUTWEEK;

    timeout.tv_usec = 0;

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
  hints.ai_socktype = SOCK_STREAM;

  rv=getaddrinfo(equakedata->quakeserver, equakedata->portname, &hints, &servinfo);
  if (rv!=0)
  {
    perror("ERROR, no such host"); /* non fatal error */
    return 0;
  }
  /* loop through all the results and connect to the first we can */
  for(p=servinfo; p!=NULL; p=p->ai_next) 
  {
    if ((equakedata->sockfd=socket(p->ai_family, p->ai_socktype, p->ai_protocol))==-1)
    {
      perror("socket");
      continue;
    }
    if (connect(equakedata->sockfd, p->ai_addr, p->ai_addrlen)==-1)
    {
      close(equakedata->sockfd);
      perror("connect");
      continue;
    }
    break;
  }

  freeaddrinfo(servinfo);

  if (p==NULL)
  {
    perror("Failed to connect to remote server.");
    close(equakedata->sockfd);
    return 0;
  }

  /* set the receive timeout for the socket */
  if(setsockopt(equakedata->sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,  sizeof(timeout))<0)
  {
    perror("setsockopt failed\n");
    close(equakedata->sockfd);
    return 0;
  }
 
  rv=0;

  switch (period)
  {
    case DAILY:
      len=strlen(equakedata->httprequestdaily);
      if (sendall(equakedata->sockfd, equakedata->httprequestdaily, &len)!=0)
      {
        perror("Error sending request"); /* non fatal error */
        return 0;
      }
      /* re-initialize buffer */
      memset(equakedata->bufferdaily, 0, BUFFERSIZEDAILY);
      rv=receiveall(equakedata->sockfd, equakedata->bufferdaily, sizeof(equakedata->bufferdaily), period);
      break;

    case WEEKLY:
      len=strlen(equakedata->httprequestweekly);
      if (sendall(equakedata->sockfd, equakedata->httprequestweekly, &len)!=0)
      {
        perror("Error sending request"); /* non fatal error */
        return 0;
      }
      /* re-initialize buffer */
      memset(equakedata->bufferweekly, 0, BUFFERSIZEWEEKLY);
      rv=receiveall(equakedata->sockfd, equakedata->bufferweekly, sizeof(equakedata->bufferweekly), period);
      break;

    default:
      len=strlen(equakedata->httprequest); 
      if (sendall(equakedata->sockfd, equakedata->httprequest, &len)!=0)
      {
	perror("Error sending request"); /* non fatal error */
	return 0;
      }

      /* re-initialize buffer */
      memset(equakedata->buffer, 0, BUFFERSIZE);
      rv=receiveall(equakedata->sockfd, equakedata->buffer, sizeof(equakedata->buffer), period);
      break;
  }
  close(equakedata->sockfd);

  switch (rv)
  {
    case -1: /* error receiving data, errno is set but we don't care right now */
      perror("error when receiving data");
      return 0;
      break;

    case 0: /* remote side has closed connection, not an error, but we have received no data so we treat it as error here */
      perror("remote side closed connection");
      return 0;
      break;

    default: /* if > 0 then success, although if we received very little there may not be much to process */
      return rv;
      break;
  }
  
}


/*
 *  ********************** HERE BE DRAGONS **********************
 */
int startequake(XfcePanelPlugin *applet, GtkWidget *label, GtkWidget *logo, struct Equake_Data *equakedata)
{
  int retval;

  equakedata->applet=applet;
  equakedata->label=label;
  equakedata->logo=logo;

  setdefaults(equakedata);

  /* create signal handler to deal with background changes */
  /*g_signal_connect(applet, "change-background", G_CALLBACK (equake_applet_change_background), NULL);*/

  /* set a timeout after which eventloop will be called until we quit the applet */
  retval=g_timeout_add_seconds(equakedata->poll_time, (GSourceFunc)eventloop, equakedata);
  return retval;
}
