/*
 * Program name:  equake
 * Version:	  1.3.8.2
 * Source file:	  equake.c (main source file)
 * Description:	  Equake pulls data about earthquakes and displays it in interesting ways
 *
 * Copyright (C) 2016-2018 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 <curl/curl.h>
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>

#include <string.h>
#include <mate-panel-applet.h>
#include <gtk/gtk.h>
#include <glib-object.h>

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

int equake_period=HOURLY;

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"));

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

  /* get the urls for hourly, daily and weekly reports */
  snprintf(equakedata->httprequest, HTTPREQ, "%s://%s/%s", equakedata->portname, equakedata->quakeserver, equakedata->quakefile_hourly);
  snprintf(equakedata->httprequestdaily, HTTPREQ, "%s://%s/%s", equakedata->portname, equakedata->quakeserver, equakedata->quakefile_daily);
  snprintf(equakedata->httprequestweekly, HTTPREQ, "%s://%s/%s", equakedata->portname, equakedata->quakeserver, equakedata->quakefile_weekly);
  
  equake_setprefs(equakedata);

  /* initialise libcurl, must use SSL */
  curl_global_init(CURL_GLOBAL_SSL);

  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;
}


/* callback used to store data we get from server */
size_t wdata(char *curlbuf, size_t size, size_t mb, char *buf)
{
  size_t len=size*mb;

  /*
   * we use strncat() instead of strncpy()
   * the reason is that libcurl does not add a \0 itself, strncat() will add it for us
   * note we have chosen the size of buf to be much larger than the typical size of data to be retrieved
   */
  switch (equake_period)
  {
    case DAILY:
      strncat(buf, curlbuf, BUFFERSIZEDAILY-strlen(buf)-1);
      break;
    case WEEKLY:
      strncat(buf, curlbuf, BUFFERSIZEWEEKLY-strlen(buf)-1);
      break;
    default:
      strncat(buf, curlbuf, BUFFERSIZE-strlen(buf)-1);
      break;
  }
  /*
   * we don't care as much if all data has been read or not, it is ok if buffer has incomplete data
   * the processdata() function knows how to deal with incomplete data
   * we return the size of data given to us so libcurl won't throw an error
   */
  return len;
} 


/* get the earthquake data using libcurl to allow https support */
int getdata(char *url, char *buf, int period)
{
  CURL *c;
  /* default failure */
  int result=0,ret=0;

  /* initialise curl library */
  c=curl_easy_init();
  /*
   * returns NULL if failed, in that case we should not use any more curl functions, thus no curl_easy_cleanup()
   * https://curl.haxx.se/libcurl/c/curl_easy_init.html
   */
  if (c==NULL)
    return result;
  /* set to 1L for more verbosity */
  curl_easy_setopt(c, CURLOPT_VERBOSE, 0L);
  /* the url to use */
  curl_easy_setopt(c, CURLOPT_URL, url);
  /* use http get */
  curl_easy_setopt(c, CURLOPT_HTTPGET, 1L);
  /*
   * set minimum speed in bytes/s for connection to timeout, useful alternative to forcing hard timeout 
   * we use 1 KB for hourly data, 10 KB for daily data and 20 KB for weekly data
   */
  switch (period)
  { 
    case DAILY:
      equake_period=DAILY;
      curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 10000L);
      break;
    case WEEKLY:
      equake_period=WEEKLY;
      curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 20000L);
      break;
    default:
      equake_period=HOURLY;
      curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 1000L);
      break;
  }
  /*
   * amount of seconds to timeout when speeds falls below speed set with CURLOPT_LOW_SPEED_LIMIT
   * we set 5 seconds here, anything longer and equake will become unresponsive for too long
   */
  curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 5L);
  
  curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, wdata);
  curl_easy_setopt(c, CURLOPT_WRITEDATA, buf);

  ret=curl_easy_perform(c);
  if (ret!=CURLE_OK)
  {
    perror(curl_easy_strerror(ret));
    result=0;
  }
  else
  {
    result=1;
  }
  curl_easy_cleanup(c);

  return result;
}


/*
 * pull earthquake data from website
 * the data is in CSV format (Comma separated ASCII text)
 */
int getquakedata(struct Equake_Data *equakedata, int period)
{
  int rv=0;

  switch (period)
  {
    case DAILY:
      memset(equakedata->bufferdaily, 0, BUFFERSIZEDAILY);

      if (getdata(equakedata->httprequestdaily, equakedata->bufferdaily, period)==0)
      {
        perror("Error sending request"); /* non fatal error */
        return 0;
      }
      rv=1;
      break;

    case WEEKLY:
      memset(equakedata->bufferweekly, 0, BUFFERSIZEWEEKLY);

      if (getdata(equakedata->httprequestweekly, equakedata->bufferweekly, period)==0)
      {
        perror("Error sending request"); /* non fatal error */
        return 0;
      }
      rv=1;
      break;

    default:
      /* re-initialize buffer */
      memset(equakedata->buffer, 0, BUFFERSIZE);

      if (getdata(equakedata->httprequest, equakedata->buffer, period)==0)
      {
        perror("Error sending request"); /* non fatal error */
        return 0;
      }
      rv=1;
      break;
  }

  return rv;
}


static void equake_applet_change_background(MatePanelApplet *applet, MatePanelAppletBackgroundType type, GdkColor  *colour,
#if GTK_CHECK_VERSION (3, 0, 0)
  cairo_pattern_t *pattern
#else
  GdkPixmap *pixmap
#endif
)
{
  GtkRcStyle *rc_style;
  GtkStyle *style;

  /* reset style */
  gtk_widget_set_style (GTK_WIDGET (applet), NULL);
  rc_style = gtk_rc_style_new ();
  gtk_widget_modify_style (GTK_WIDGET (applet), rc_style);
#if GTK_CHECK_VERSION (3, 0, 0)
  /*gtk_rc_style_unref (rc_style);*/
#else
  gtk_rc_style_unref (rc_style);
#endif

  switch (type)
  {
    case PANEL_NO_BACKGROUND:
      break;

    case PANEL_COLOR_BACKGROUND:
      gtk_widget_modify_bg(GTK_WIDGET (applet), GTK_STATE_NORMAL, colour);
      break;

    case PANEL_PIXMAP_BACKGROUND:
#if GTK_CHECK_VERSION (3, 0, 0)
      break;
#else
      style = gtk_style_copy (GTK_WIDGET (applet)->style);

      if (style->bg_pixmap[GTK_STATE_NORMAL])
        g_object_unref(style->bg_pixmap[GTK_STATE_NORMAL]);

      style->bg_pixmap[GTK_STATE_NORMAL]=g_object_ref(pixmap);
      gtk_widget_set_style(GTK_WIDGET (applet), style);
      g_object_unref(style);
      break;
#endif
  }
}


/*
 *  ********************** HERE BE DRAGONS **********************
 */
int startequake(MatePanelApplet *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;
}
