/*
 * Program name:  equake
 * Version:	  1.3.8.2
 * Source file:	  equake_processdata.c
 * 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 processdata_daily(struct Equake_Data *equakedata)
{
  const char delimiters[] = ",\"";
  const char delimiterquote[] = "\""; /* some fields are between quotes, with commas */
  const char delimiterdate[]="TZ"; /* new date format has strings T and Z, we use it to separate date and time */
  char *token, *dt1, *dt2, buf[URLSIZE];
  int n;
  
  char *running=alloca(sizeof(equakedata->bufferdaily)+1);
  if (running==NULL)
    return -1;
 
  equakedata->quake_history_countdaily=-1;
 
  snprintf(running, strlen(equakedata->bufferdaily), "%s", equakedata->bufferdaily);

  running=strstr(running, equakedata->formatstring); /* find line with field names */
  if (running==NULL)
    return -1; /* non fatal error, formatstring couldn't be found, no big deal */

  running=strchr(running,'\n'); /* find newline */
  if (running==NULL)
    return -1; /* again non fatal error */

  running=running+1;
  if (running=='\0') /* if we reached the end of the text, means no data available, but this is not always the case, see below */
    return -1; /* again non fatal error */

  /* 
   * sometimes there's more text but no actual valid data
   * one can't blindly rely upon data provided by this (or any) webservice to be in a specific fixed format
   * in case formatstring is larger than whatever text is left
   * then it's a sure bet we should not do further processing and risk a crash 
   * the old data is thus prerserved and re-used
   */
  if ((strlen(running)) < (strlen(equakedata->formatstring)))
    return -1;

  /* 
   * we will load the data into the array up to a maxmimum of MAXQUAKES_DAY
   * although it's unlikely that maximum would ever be reached
   */
  for(n=0;n<MAXQUAKES_DAY;n++)
  {
    token=strsep(&running, delimiters);
    if (token==NULL)
      break;

    /* lets make date more readable by filtering out T and Z strings and adding some clarification */
    dt1=strsep(&token, delimiterdate);
    if (dt1==NULL)
      break;
    dt2=strsep(&token, delimiterdate);
    if (dt2==NULL)
      break;
    snprintf(buf, URLSIZE, "%s%s%s%s%s", dt1, "  ", dt2, " ", TIMEZONE);

    equakedata->equakeformatday[n].datetime=strndup(buf, strlen(buf));

    if (equakedata->equakeformatday[n].datetime!=NULL)
    {
      /* now that we have datetime lets convert it to local time */
      if (convert_localtime(&equakedata->equakeformatday[n])==-1)
        equakedata->equakeformatday[n].datetime_local=strndup(buf, strlen(buf));

      token=strsep(&running, delimiters);
      if (token==NULL)
	break;

      equakedata->equakeformatday[n].lat=strndup(token, strlen(token));
      if (equakedata->equakeformatday[n].lat!=NULL)
      {
	token=strsep(&running, delimiters);
	if (token==NULL)
	  break;

	equakedata->equakeformatday[n].lon=strndup(token, strlen(token));
	if (equakedata->equakeformatday[n].lon!=NULL)
	{
	  token=strsep(&running, delimiters); /* eliminate whitespace */ 
	  if (token==NULL)
	    break;

	  equakedata->equakeformatday[n].depth=strndup(token, strlen(token));
	  if (equakedata->equakeformatday[n].depth!=NULL)
	  {
	    token=strsep(&running, delimiters); /* eliminate whitespace */
	    if (token==NULL)
	      break;

	    equakedata->equakeformatday[n].magnitude=strndup(token, strlen(token));
	    if (equakedata->equakeformatday[n].magnitude!=NULL)
	    {
	      token=strsep(&running, delimiters);
	      if (token==NULL)
		break;

	      equakedata->equakeformatday[n].magnitude_type=strndup(token, strlen(token));
	      if (equakedata->equakeformatday[n].magnitude_type!=NULL)
	      {
		token=strsep(&running, delimiters);
		if (token==NULL)
		  break;

		equakedata->equakeformatday[n].nst=strndup(token, strlen(token));
		if (equakedata->equakeformatday[n].nst!=NULL)
		{
		  token=strsep(&running, delimiters);
		  if (token==NULL)
		    break;

		  equakedata->equakeformatday[n].gap=strndup(token, strlen(token));
		  if (equakedata->equakeformatday[n].gap!=NULL)
		  {
		    token=strsep(&running, delimiters); /* eliminate whitespace */
		    if (token==NULL)
		      break;

		    equakedata->equakeformatday[n].dmin=strndup(token, strlen(token));
		    if (equakedata->equakeformatday[n].dmin!=NULL)
		    {
		      token=strsep(&running, delimiters); /* eliminate whitespace */
		      if (token==NULL)
			break;

		      equakedata->equakeformatday[n].rms=strndup(token, strlen(token));
		      if (equakedata->equakeformatday[n].rms!=NULL)
		      {
			token=strsep(&running, delimiters); /* eliminate whitespace */
			if (token==NULL)
			  break;

			equakedata->equakeformatday[n].src=strndup(token, strlen(token));
			if (equakedata->equakeformatday[n].src!=NULL)
			{
			  token=strsep(&running, delimiters); /* eliminate whitespace */
			  if (token==NULL)
			    break;

			  equakedata->equakeformatday[n].eqid=strndup(token, strlen(token));
			  if (equakedata->equakeformatday[n].eqid!=NULL)
			  {
			    token=strsep(&running, delimiters); /* eliminate whitespace */
			    if (token==NULL)
			      break;

			    equakedata->equakeformatday[n].updated=strndup(token, strlen(token));
			    if (equakedata->equakeformatday[n].updated!=NULL)
			    {
			      token=strsep(&running, delimiters); /* eliminate whitespace */
			      if (token==NULL)
				break;

			      token=strsep(&running, delimiterquote); /* this will save what's in between quotes */
			      if (token==NULL)
				break;

			      equakedata->equakeformatday[n].region=strndup(token, strlen(token));
			      if (equakedata->equakeformatday[n].region==NULL)
				return -1;

			      /*
			       * we test whether running is NULL to fix a rare and random bug
			       * occasionally, especially with limited bandwidth and/or congested network connectivity, 
			       * the last bit of the data would be cut off and the last quotation mark of the place CSV field 
			       * could be missing, running would as result be NULL and if we don't test for it here it will crash strchr()
			       */
                              if(running==NULL)
                                break;

			      /* 
			       * we find a newline and then try to go one step after that which is the start of the
			       * next line, or if that fails we reached the end of the buffer
			       */
			      running=strchr(running,'\n'); /* find newline */
			      if (running==NULL)
				break; /* again non fatal error */

			      running=running+1;
			      if (running=='\0') /* if we reached the end of the text, means no data available, but this is not always the case, see below */
				break;
			    }
			  }	
			}
		      }
		    }
		  }
		}
	      }
	    }
	  }
	}
      }
    }
    /* we count amount of commas left in the text, if this is less than 20 we assume whatever is left is not valid data */
    if(count_characters(running, ",")<COMMAS) break;
  }
  equakedata->quake_history_countdaily=n;

  return equakedata->quake_history_countdaily;
}

int count_characters(const char *str, char *character)
{
  const char *p=str;
  int count=0;

  do 
  {
    if(strcmp(p, character))
      count++;
    /* we break if count is enough to expect at least one line of valid data */
    if(count>COMMAS)
      break;
  }
  while (*(p++));

  return count;
}

int processdata_weekly(struct Equake_Data *equakedata)
{
  const char delimiters[] = ",\"";
  const char delimiterquote[] = "\""; /* some fields are between quotes, with commas */
  const char delimiterdate[]="TZ"; /* new date format has strings T and Z, we use it to separate date and time */
  char *token, *dt1, *dt2, buf[URLSIZE];
  int n,c;
  
  char *running=alloca(sizeof(equakedata->bufferweekly)+1);
  if (running==NULL)
    return -1;
 
  equakedata->quake_history_countweekly=-1;
 
  snprintf(running, strlen(equakedata->bufferweekly), "%s", equakedata->bufferweekly);

  running=strstr(running, equakedata->formatstring); /* find line with field names */
  if (running==NULL)
    return -1; /* non fatal error, formatstring couldn't be found, no big deal */

  running=strchr(running,'\n'); /* find newline */
  if (running==NULL)
    return -1; /* again non fatal error */

  running=running+1;
  if (running=='\0') /* if we reached the end of the text, means no data available, but this is not always the case, see below */
    return -1; /* again non fatal error */

  /* 
   * sometimes there's more text but no actual valid data
   * one can't blindly rely upon data provided by this (or any) webservice to be in a specific fixed format
   * in case formatstring is larger than whatever text is left
   * then it's a sure bet we should not do further processing and risk a crash 
   * the old data is thus prerserved and re-used
   */
  if ((strlen(running)) < (strlen(equakedata->formatstring)))
    return -1;

  /* 
   * we will load the data into the array up to a maxmimum of MAXQUAKES_WEEK
   * although it's unlikely that maximum would ever be reached
   */
  for(n=0;n<MAXQUAKES_WEEK;n++)
  {
    token=strsep(&running, delimiters);
    if (token==NULL)
      break;

    /* lets make date more readable by filtering out T and Z strings and adding some clarification */
    dt1=strsep(&token, delimiterdate);
    if (dt1==NULL)
      break;
    dt2=strsep(&token, delimiterdate);
    if (dt2==NULL)
      break;
    snprintf(buf, URLSIZE, "%s%s%s%s%s", dt1, "  ", dt2, " ", TIMEZONE);

    equakedata->equakeformatweek[n].datetime=strndup(buf, strlen(buf));

    if (equakedata->equakeformatweek[n].datetime!=NULL)
    {
      /* now that we have datetime lets convert it to local time */
      if (convert_localtime(&equakedata->equakeformatweek[n])==-1)
        equakedata->equakeformatweek[n].datetime_local=strndup(buf, strlen(buf));

      token=strsep(&running, delimiters);
      if (token==NULL)
	break;

      equakedata->equakeformatweek[n].lat=strndup(token, strlen(token));
      if (equakedata->equakeformatweek[n].lat!=NULL)
      {
	token=strsep(&running, delimiters);
	if (token==NULL)
	  break;

	equakedata->equakeformatweek[n].lon=strndup(token, strlen(token));
	if (equakedata->equakeformatweek[n].lon!=NULL)
	{
	  token=strsep(&running, delimiters); /* eliminate whitespace */ 
	  if (token==NULL)
	    break;

	  equakedata->equakeformatweek[n].depth=strndup(token, strlen(token));
	  if (equakedata->equakeformatweek[n].depth!=NULL)
	  {
	    token=strsep(&running, delimiters); /* eliminate whitespace */
	    if (token==NULL)
	      break;

	    equakedata->equakeformatweek[n].magnitude=strndup(token, strlen(token));
	    if (equakedata->equakeformatweek[n].magnitude!=NULL)
	    {
	      token=strsep(&running, delimiters);
	      if (token==NULL)
		break;

	      equakedata->equakeformatweek[n].magnitude_type=strndup(token, strlen(token));
	      if (equakedata->equakeformatweek[n].magnitude_type!=NULL)
	      {
		token=strsep(&running, delimiters);
		if (token==NULL)
		  break;

		equakedata->equakeformatweek[n].nst=strndup(token, strlen(token));
		if (equakedata->equakeformatweek[n].nst!=NULL)
		{
		  token=strsep(&running, delimiters);
		  if (token==NULL)
		    break;

		  equakedata->equakeformatweek[n].gap=strndup(token, strlen(token));
		  if (equakedata->equakeformatweek[n].gap!=NULL)
		  {
		    token=strsep(&running, delimiters); /* eliminate whitespace */
		    if (token==NULL)
		      break;

		    equakedata->equakeformatweek[n].dmin=strndup(token, strlen(token));
		    if (equakedata->equakeformatweek[n].dmin!=NULL)
		    {
		      token=strsep(&running, delimiters); /* eliminate whitespace */
		      if (token==NULL)
			break;

		      equakedata->equakeformatweek[n].rms=strndup(token, strlen(token));
		      if (equakedata->equakeformatweek[n].rms!=NULL)
		      {
			token=strsep(&running, delimiters); /* eliminate whitespace */
			if (token==NULL)
			  break;

			equakedata->equakeformatweek[n].src=strndup(token, strlen(token));
			if (equakedata->equakeformatweek[n].src!=NULL)
			{
			  token=strsep(&running, delimiters); /* eliminate whitespace */
			  if (token==NULL)
			    break;

			  equakedata->equakeformatweek[n].eqid=strndup(token, strlen(token));
			  if (equakedata->equakeformatweek[n].eqid!=NULL)
			  {
			    token=strsep(&running, delimiters); /* eliminate whitespace */
			    if (token==NULL)
			      break;

			    equakedata->equakeformatweek[n].updated=strndup(token, strlen(token));
			    if (equakedata->equakeformatweek[n].updated!=NULL)
			    {
			      token=strsep(&running, delimiters); /* eliminate whitespace */
			      if (token==NULL)
				break;

			      token=strsep(&running, delimiterquote); /* this will save what's in between quotes */
			      if (token==NULL)
				break;

			      equakedata->equakeformatweek[n].region=strndup(token, strlen(token));
			      if (equakedata->equakeformatweek[n].region==NULL)
				return -1;

                              /*
                               * we test whether running is NULL to fix a rare and random bug
                               * occasionally, especially with limited bandwidth and/or congested network connectivity, 
                               * the last bit of the data would be cut off and the last quotation mark of the place CSV field 
                               * could be missing, running would as result be NULL and if we don't test for it here it will crash strchr()
                               */
                              if(running==NULL)
                                break;

			      /* 
			       * we find a newline and then try to go one step after that which is the start of the
			       * next line, or if that fails we reached the end of the buffer
			       */
			      running=strchr(running,'\n'); /* find newline */
			      if (running==NULL)
				break; /* again non fatal error */

			      running=running+1;
			      if (running=='\0') /* if we reached the end of the text, means no data available, but this is not always the case, see below */
				break;
			    }
			  }	
			}
		      }
		    }
		  }
		}
	      }
	    }
	  }
	}
      }
    }
    /* we break if n exceeds thet maximum amount of text blocks the GtkLabel will accept before crashing */
    if(n>=MAXWEEK) { break; }
    /* we count amount of commas left in the text, if this is less than 20 we assume whatever is left is not valid data */
    if(count_characters(running, ",")<COMMAS) break;
  }
  equakedata->quake_history_countweekly=n;
  return equakedata->quake_history_countweekly;
}


int render_equake_label(struct Equake_Data *equakedata)
{
  char url[URLSIZE];
  int n,i=equakedata->pos,day=1;
  double mag;

  if (equakedata->quake_history_count>=0)
  {
    /* in case monitormag>1 we find the first earthquake with magnitude>=monitormag */
    if (equakedata->monitormag>1)
    {
      for (n=0;n<=equakedata->quake_history_count;n++)
      {
	/* 
	 * if magnitude==NULL we break loop and return empty handed, else we risk a crash 
	 * well we return equakedata->pos to keep using previous data until next refresh
	 */
	if (equakedata->equakeformat[n].magnitude!=NULL)
	  mag=atof(equakedata->equakeformat[n].magnitude);
	else
	  mag=0;

	if (mag>=equakedata->monitormag)
	{
	  i=n;
	  day=0;
	  break;
	}
      }
      if (day==1)
      {
	/* if we get here it means hourly update had no earthquakes>=monitormag, so we check daily update */
	if (getquakedata(equakedata, DAILY)>0)
	{
	  if (processdata_daily(equakedata)>0)
	  {
            /* if failed to get data let's stick with whatever is the most recent earthquake */
	    for (n=0;n<=equakedata->quake_history_countdaily;n++)
	    {
              /* 
               * if magnitude==NULL we break loop and return empty handed, else we risk a crash 
               * well we return equakedata->pos to keep using previous data until next refresh
               */
	      if (equakedata->equakeformatday[n].magnitude!=NULL)
		mag=atof(equakedata->equakeformatday[n].magnitude);
	      else
		mag=0;

	      if (mag>=equakedata->monitormag)
	      {
		i=n;
		if (equakedata->equakeformatday[i].src!="")
		  snprintf(url, URLSIZE, "%s://%s%s%s%s", equakedata->portname, equakedata->quakeserver, equakedata->quakepath_ww, equakedata->equakeformatday[i].eqid, QUAKEPATH_AF);

		/* html markup only supported in GTK+ 2.18 and up */
		if ((gtk_major_version>=2) && (gtk_minor_version>=18))
		  snprintf(equakedata->lbl, LABELSIZE, "<a href=\"%s\" title=\"%s\">%s</a> %s", url, url, equakedata->equakeformatday[i].magnitude, equakedata->equakeformatday[i].region);
		else
		  snprintf(equakedata->lbl, LABELSIZE, "%s %s", equakedata->equakeformatday[i].magnitude, equakedata->equakeformatday[i].region);

		snprintf(equakedata->tooltiptext_label, URLSIZE, "Earthquake Magnitude: %s\nDate: %s\nDate Local: %s\nLocation: %s\nLatitude: %s; Longitude: %s\nDepth: %s km", equakedata->equakeformatday[i].magnitude, equakedata->equakeformatday[i].datetime, equakedata->equakeformatday[i].datetime_local, equakedata->equakeformatday[i].region, equakedata->equakeformatday[i].lat, equakedata->equakeformatday[i].lon, equakedata->equakeformatday[i].depth);
		equakedata->posperiod=DAILY;

		return i;
	      }
	    }
	  }
	  else
	    return -1;
	}
	else
	  return -1;
      }		
    }

    if (equakedata->equakeformat[i].src!="")
      snprintf(url, URLSIZE, "%s://%s%s%s%s", equakedata->portname, equakedata->quakeserver, equakedata->quakepath_ww, equakedata->equakeformat[i].eqid, QUAKEPATH_AF);

    /* html markup only supported in GTK+ 2.18 and up */
    if ((gtk_major_version>=2) && (gtk_minor_version>=18))
      snprintf(equakedata->lbl, LABELSIZE, "<a href=\"%s\" title=\"%s\">%s</a> %s", url, url, equakedata->equakeformat[i].magnitude, equakedata->equakeformat[i].region);
    else
      snprintf(equakedata->lbl, LABELSIZE, "%s %s", equakedata->equakeformat[i].magnitude, equakedata->equakeformat[i].region);
   
    snprintf(equakedata->tooltiptext_label, URLSIZE, "Earthquake Magnitude: %s\nDate: %s\nDate Local: %s\nLocation: %s\nLatitude: %s; Longitude: %s\nDepth: %s km", equakedata->equakeformat[i].magnitude, equakedata->equakeformat[i].datetime, equakedata->equakeformat[i].datetime_local, equakedata->equakeformat[i].region, equakedata->equakeformat[i].lat, equakedata->equakeformat[i].lon, equakedata->equakeformat[i].depth);
    equakedata->posperiod=HOURLY;
    
    return i;

  }
}


static void set_label_tooltip_text(GtkLabel *label, char *lbltxt, char *tooltiptxt)
{
  /* check if text is not NULL, if it IS NULL then set default text */
  if(lbltxt!=NULL)
    /* update label text */
    gtk_label_set_markup((GtkLabel *)label, lbltxt);
  else
    gtk_label_set_markup((GtkLabel *)label, LABELTXT);

  if(tooltiptxt!=NULL)
    /* update tooltip text */
    gtk_widget_set_tooltip_text((GtkWidget *)label, tooltiptxt);
  else
    gtk_widget_set_tooltip_text((GtkWidget *)label, NODATA);
}


void set_signature_file(struct Equake_Data *equakedata)
{
  FILE *fp;

  /* we try to open each file, the first that succeeds is overwritten and used to save the signature */
  fp=fopen(equakedata->sigfile, "w");
  if (fp==NULL)
    return;

  fprintf(fp, "%s | e-quake.org", equakedata->tooltiptext_label);
  fclose(fp);
}


/*
 * function will process data which we retrieved through getquakedata()
 * if we need data for another period besides HOURLY we call the ap[propriate function
 * i.e. processdata_daily for DAILY and return count of data processed or -1 on failure
 */
int processdata(struct Equake_Data *equakedata, int period)
{
  const char delimiters[] = ",\"";
  const char delimiterquote[] = "\""; /* some fields are between quotes, with commas */
  const char delimiterdate[]="TZ"; /* new date format has strings T and Z, we use it to separate date and time */
  char *token, *dt1, *dt2, buf[URLSIZE];
  int n, pos=0;
  double mag;

  /* 
   * if a daily report was requested in the "last day" menu item 
   * then call that function and return right away, the rest of this function isn't needed 
   */ 
  if (period==DAILY)
  {
    n=processdata_daily(equakedata);
    return n;
  }
  if (period==WEEKLY)
  {
    n=processdata_weekly(equakedata);
    return n;
  }
 
  equakedata->quake_history_count=-1; 
  /*
   * the rest then will generate an hourly report which is used for the "last hour" menu item
   * and also for a regular update, but only the most recent earthquake will be displayed then ofcourse
   */
  char *running=alloca(sizeof(equakedata->buffer)+1);
  if (running==NULL)
    return -1;

  snprintf(running, strlen(equakedata->buffer), "%s", equakedata->buffer);

  running=strstr(running, equakedata->formatstring); // find line with field names
  if (running==NULL)
    return -1; /* non fatal error, formatstring couldn't be found, no big deal */

  running=strchr(running,'\n'); /* find newline */
  if (running==NULL)
    return -1; /* again non fatal error */

  running=running+1;
  if (running=='\0') /* if we reached the end of the text, means no data available, but this is not always the case, see below */
    return -1; /* again non fatal error */

  /* 
   * sometimes there's more text but no actual valid data
   * one can't blindly rely upon data provided by this (or any) webservice to be in a specific fixed format
   * in case formatstring is larger than whatever text is left
   * then it's a sure bet we should not do further processing and risk a crash 
   * the old data is thus prerserved and re-used
   */
  if ((strlen(running)) < (strlen(equakedata->formatstring)))
    return -1;

  /* 
   * preset label and tooltip text if something fails this will be shown 
   * that's better than showing some half broken data due to a failure in the below code
   * parsing NULL as the text causes the default text to be set
   */
  set_label_tooltip_text((GtkLabel *)equakedata->label, NULL, NULL);
  
  /* 
   * we will load the data into the array up to a maxmimum of MAXQUAKES_HR
   * although it's unlikely that maximum would ever be reached
   */
  for(n=0;n<MAXQUAKES_HR;n++)
  {
    token=strsep(&running, delimiters);
    if (token==NULL)
      break;

    /* lets make date more readable by filtering out T and Z strings and adding some clarification */
    dt1=strsep(&token, delimiterdate);
    if (dt1==NULL)
      break;
    dt2=strsep(&token, delimiterdate);
    if (dt2==NULL)
      break;
    snprintf(buf, URLSIZE, "%s%s%s%s%s", dt1, "  ", dt2, " ", TIMEZONE);

    equakedata->equakeformat[n].datetime=strndup(buf, strlen(buf));

    if (equakedata->equakeformat[n].datetime!=NULL)
    {
      /* now that we have datetime lets convert it to local time */
      if (convert_localtime(&equakedata->equakeformat[n])==-1)
        equakedata->equakeformat[n].datetime_local=strndup(buf, strlen(buf));

      token=strsep(&running, delimiters);
      if (token==NULL)
	break;

      equakedata->equakeformat[n].lat=strndup(token, strlen(token));
      if (equakedata->equakeformat[n].lat!=NULL)
      {
	token=strsep(&running, delimiters);
	if (token==NULL)
	  break;

	equakedata->equakeformat[n].lon=strndup(token, strlen(token));
	if (equakedata->equakeformat[n].lon!=NULL)
	{
	  token=strsep(&running, delimiters); /* eliminate whitespace */ 
	  if (token==NULL)
	    break;

	  equakedata->equakeformat[n].depth=strndup(token, strlen(token));
	  if (equakedata->equakeformat[n].depth!=NULL)
	  {
	    token=strsep(&running, delimiters); /* eliminate whitespace */
	    if (token==NULL)
	      break;

	    equakedata->equakeformat[n].magnitude=strndup(token, strlen(token));
	    if (equakedata->equakeformat[n].magnitude!=NULL)
	    {
	      token=strsep(&running, delimiters);
	      if (token==NULL)
		break;

	      equakedata->equakeformat[n].magnitude_type=strndup(token, strlen(token));
	      if (equakedata->equakeformat[n].magnitude_type!=NULL)
	      {
		token=strsep(&running, delimiters);
		if (token==NULL)
		  break;

		equakedata->equakeformat[n].nst=strndup(token, strlen(token));
		if (equakedata->equakeformat[n].nst!=NULL)
		{
		  token=strsep(&running, delimiters);
		  if (token==NULL)
		    break;

		  equakedata->equakeformat[n].gap=strndup(token, strlen(token));
		  if (equakedata->equakeformat[n].gap!=NULL)
		  {
		    token=strsep(&running, delimiters); /* eliminate whitespace */
		    if (token==NULL)
		      break;

		    equakedata->equakeformat[n].dmin=strndup(token, strlen(token));
		    if (equakedata->equakeformat[n].dmin!=NULL)
		    {
		      token=strsep(&running, delimiters); /* eliminate whitespace */
		      if (token==NULL)
			break;

                      equakedata->equakeformat[n].rms=strndup(token, strlen(token));
	              if (equakedata->equakeformat[n].rms!=NULL)
	              {
		        token=strsep(&running, delimiters); /* eliminate whitespace */
		        if (token==NULL)
			  break;

                        equakedata->equakeformat[n].src=strndup(token, strlen(token));
	                if (equakedata->equakeformat[n].src!=NULL)
		        {
			  token=strsep(&running, delimiters); /* eliminate whitespace */
                          if (token==NULL)
	                    break;

	                  equakedata->equakeformat[n].eqid=strndup(token, strlen(token));
		          if (equakedata->equakeformat[n].eqid!=NULL)
			  {
	                    token=strsep(&running, delimiters); /* eliminate whitespace */
		            if (token==NULL)
			      break;

	                    equakedata->equakeformat[n].updated=strndup(token, strlen(token));
		            if (equakedata->equakeformat[n].updated!=NULL)
	                    {
		              token=strsep(&running, delimiters); /* eliminate whitespace */
	                      if (token==NULL)
	                        break;

	  		      token=strsep(&running, delimiterquote); /* this will save what's in between quotes */
			      if (token==NULL)
				break;

			      equakedata->equakeformat[n].region=strndup(token, strlen(token));
			      if (equakedata->equakeformat[n].region==NULL)
				return -1;

                              /*
                               * we test whether running is NULL to fix a rare and random bug
                               * occasionally, especially with limited bandwidth and/or congested network connectivity, 
                               * the last bit of the data would be cut off and the last quotation mark of the place CSV field 
                               * could be missing, running would as result be NULL and if we don't test for it here it will crash strchr()
                               */
                              if(running==NULL)
                                break;

			      /* 
			       * we find a newline and then try to go one step after that which is the start of the
			       * next line, or if that fails we reached the end of the buffer
			       */
			      running=strchr(running,'\n'); /* find newline */
			      if (running==NULL)
				break; /* again non fatal error */

			      running=running+1;
			      if (running=='\0') /* if we reached the end of the text, means no data available, but this is not always the case, see below */
				break;
			    }
			  }	
			}
		      }
		    }
		  }
		}
	      }
	    }
	  }
	}
      }
    }
    /* we count amount of commas left in the text, if this is less than 20 we assume whatever is left is not valid data */
    if(count_characters(running, ",")<COMMAS) break;
  }
  equakedata->quake_history_count=n;

  pos=render_equake_label(equakedata);
  if (pos<0)
    return -1;
 
  set_label_tooltip_text((GtkLabel *)equakedata->label, equakedata->lbl, equakedata->tooltiptext_label);

  /* 
   * since we got the tooltiptext we now save it in a signature file
   * which the user can use to attach to an email for example
   * for added coolness factor
   * check if magnitude is larger than SIGFILE, then check if it's the same earthquake, 
   * if it is don't save anything because we already did 
   */
  if (equakedata->posperiod==HOURLY)
  {
    if (equakedata->equakeformat[pos].magnitude!=NULL)
    {
      mag=atof(equakedata->equakeformat[pos].magnitude);
      if (equakedata->sigfilemag>0)
      {
	if (mag>=equakedata->sigfilemag)
	{
	  if ((equakedata->equakeformat[pos].src!="") && (strncmp(equakedata->sigfile_prev_eqid, equakedata->equakeformat[pos].eqid, strlen(equakedata->equakeformat[pos].eqid))))
	  {
	    set_signature_file(equakedata);
	    /* store new eqid into sigfile_prev_eqid */
	    equakedata->sigfile_prev_eqid=strndup(equakedata->equakeformat[pos].eqid, strlen(equakedata->equakeformat[pos].eqid));
	  }
	}
      }
    }
  }
  else
  {
    if (equakedata->equakeformatday[pos].magnitude!=NULL)
    {
      mag=atof(equakedata->equakeformatday[pos].magnitude);
      if (equakedata->sigfilemag>0)
      {
        if (mag>=equakedata->sigfilemag)
        {
	  if ((equakedata->equakeformatday[pos].src!="") && (strncmp(equakedata->sigfile_prev_eqid, equakedata->equakeformatday[pos].eqid, strlen(equakedata->equakeformatday[pos].eqid))))
	  {
	    set_signature_file(equakedata);
            /* store new eqid into sigfile_prev_eqid */
            equakedata->sigfile_prev_eqid=strndup(equakedata->equakeformatday[pos].eqid, strlen(equakedata->equakeformatday[pos].eqid));
          }
	}
      }
    } 
  }
  /* set the icon depending on the magnitude */
  set_magnitude_icon(equakedata, pos);
  
  if (equakedata->will_alert!=0)
    alert_heavy_quake(equakedata, pos);

  equakedata->pos=pos;
  return 1;
}


void alert_heavy_quake(struct Equake_Data *equakedata, int pos)
{
  double mag;
  char *alert=alloca(sizeof(equakedata->buffer)+1); /* there's not more memory needed than what's in the buffer and normally much less */
  if (alert==NULL)
    return;
  char url[URLSIZE];

  if (equakedata->posperiod==HOURLY)
  {
    if (equakedata->quake_history_count>=0)
    {
      if(equakedata->equakeformat[pos].magnitude==NULL)
	return;
      /* convert magnitude to double */
      mag=atof(equakedata->equakeformat[pos].magnitude);
  
      /* if magnitude is bigger or equal to equakedata->alert_heavy create a special alert */
      if (mag>=equakedata->alert_heavy)
      {
	/* make sure the quake actually exists, shouldn't fail, and make sure we haven't yet reported about it */
	if ((equakedata->equakeformat[pos].src!="") && (strncmp(equakedata->report_prev_eqid, equakedata->equakeformat[pos].eqid, strlen(equakedata->equakeformat[pos].eqid))))
	{
	  snprintf(url, URLSIZE, "%s://%s%s%s%s", equakedata->portname, equakedata->quakeserver, equakedata->quakepath_ww, equakedata->equakeformat[pos].eqid, QUAKEPATH_AF);

	  snprintf(alert, strlen(equakedata->buffer), "Earthquake Magnitude: %s\nDate: %s\nDate Local: %s\nLocation: %s\nLatitude: %s; Longitude: %s\nDepth: %s km\nurl: %s\n\n", equakedata->equakeformat[pos].magnitude, equakedata->equakeformat[pos].datetime, equakedata->equakeformat[pos].datetime_local, equakedata->equakeformat[pos].region, equakedata->equakeformat[pos].lat, equakedata->equakeformat[pos].lon, equakedata->equakeformat[pos].depth, url);
	  equakedata->report_prev_eqid=strndup(equakedata->equakeformat[pos].eqid, strlen(equakedata->equakeformat[pos].eqid));

	  /* this is not user selected, but automatically pops up, so we don't want to block the rest of the app */ 
	  display_nonmodaldialog(equakedata->alert_heavy_dialog, alert);
	}
      }
    }
  }
  else
  {
    if (equakedata->quake_history_countdaily>=0)
    {
      if(equakedata->equakeformatday[pos].magnitude==NULL)
        return;
      /* convert magnitude to double */
      mag=atof(equakedata->equakeformatday[pos].magnitude);

      /* if magnitude is bigger or equal to equakedata->alert_heavy create a special alert */
      if (mag>=equakedata->alert_heavy)
      {
        /* make sure the quake actually exists, shouldn't fail, and make sure we haven't yet reported about it */
        if ((equakedata->equakeformatday[pos].src!="") && (strncmp(equakedata->report_prev_eqid, equakedata->equakeformatday[pos].eqid, strlen(equakedata->equakeformatday[pos].eqid))))
        {
          snprintf(url, URLSIZE, "%s://%s%s%s%s", equakedata->portname, equakedata->quakeserver, equakedata->quakepath_ww, equakedata->equakeformatday[pos].eqid, QUAKEPATH_AF);

          snprintf(alert, strlen(equakedata->buffer), "Earthquake Magnitude: %s\nDate: %s\nDate Local: %s\nLocation: %s\nLatitude: %s; Longitude: %s\nDepth: %s km\nurl: %s\n\n", equakedata->equakeformatday[pos].magnitude, equakedata->equakeformatday[pos].datetime, equakedata->equakeformatday[pos].datetime_local, equakedata->equakeformatday[pos].region, equakedata->equakeformatday[pos].lat, equakedata->equakeformatday[pos].lon, equakedata->equakeformatday[pos].depth, url);
          equakedata->report_prev_eqid=strndup(equakedata->equakeformatday[pos].eqid, strlen(equakedata->equakeformatday[pos].eqid));

          /* this is not user selected, but automatically pops up, so we don't want to block the rest of the app */
          display_nonmodaldialog(equakedata->alert_heavy_dialog, alert);
        }
      }
    }
  }
}


static void display_nonmodaldialog(GtkWidget *dialog, char *t)
{
  if(dialog!=NULL)
  {
    gtk_widget_hide(dialog);
    gtk_widget_destroy(dialog);
  }

  dialog=gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", t);
  if(dialog)
  {
    g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
    gtk_widget_show_all(dialog);
  }
}


void set_magnitude_icon(struct Equake_Data *equakedata, int pos)
{
  double mag;
  GdkPixbuf *quake_logo;

  /* convert magnitude to double */
  if (equakedata->posperiod==HOURLY)
  {
    if (equakedata->equakeformat[pos].magnitude!=NULL)
      mag=atof(equakedata->equakeformat[pos].magnitude);
    else
      mag=0;
  }
  else
  {
    if (equakedata->equakeformatday[pos].magnitude!=NULL)
      mag=atof(equakedata->equakeformatday[pos].magnitude);
    else
      mag=0;
  }

  if (mag<2)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag1, FALSE, NULL);
  else if (mag<3)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag2, FALSE, NULL);
  else if (mag<4)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag3, FALSE, NULL);
  else if (mag<5)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag4, FALSE, NULL);
  else if (mag<6)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag5, FALSE, NULL);
  else if (mag<7)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag6, FALSE, NULL);
  else if (mag<8)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag7, FALSE, NULL);
  else if (mag<9)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag8, FALSE, NULL);
  else if (mag>=9)
    quake_logo=gdk_pixbuf_new_from_inline(-1, mag9, FALSE, NULL);
  else  
    quake_logo=gdk_pixbuf_new_from_inline(-1, equake_icon, FALSE, NULL);

  gtk_image_set_from_pixbuf((GtkImage *) equakedata->logo, quake_logo);
}


/*
 * Fixed, see: http://stackoverflow.com/questions/9076494/how-to-convert-from-utc-to-local-time-in-c
 * this converts UTC time into local time
 */
int convert_localtime(struct Equake_Format *equakeformat)
{
  char *c[8];
  char *token;
  const char delimitercolon[] = "-:. ";
  time_t utc, local;

  struct tm *tp=alloca(sizeof(struct tm));
  if(tp==NULL)
    return -1;

  memset(tp, 0, sizeof(struct tm));

  struct tm *localt=alloca(sizeof(struct tm));
  if(localt==NULL)
    return -1;

  char *datetime=alloca(URLSIZE+1); /* we use URLSIZE since it's also the size used to allocate for equakeformat->datetime */
  if(datetime==NULL)
    return -1;

  char *printtime=alloca(URLSIZE+1);
  if(printtime==NULL)
    return -1;

  char *running2=alloca(URLSIZE+1);
  if (running2==NULL)
    return -1;

  snprintf(running2, URLSIZE, "%s", equakeformat->datetime);

  token=strsep(&running2, delimitercolon); /* year */
  if (token!=NULL)
  {
    c[0]=strndup(token, strlen(token));
    if (c[0]==NULL)
      return -1;
    token=strsep(&running2, delimitercolon); /* month */

    if (token!=NULL)
    {
      c[1]=strndup(token, strlen(token));
      if (c[1]==NULL)
        return -1;
      token=strsep(&running2, delimitercolon); /* day of month */
      if (token!=NULL)
      {
        c[2]=strndup(token, strlen(token));
	if (c[2]==NULL)
          return -1;
        token=strsep(&running2, delimitercolon); /* eliminate whitespace */
        if (token==NULL)
          return -1;
        token=strsep(&running2, delimitercolon); /* hours */
        if (token==NULL)
          return -1;
        if (token!=NULL)
        {
          c[3]=strndup(token, strlen(token));
	  if (c[3]==NULL)
	    return -1;
          token=strsep(&running2, delimitercolon); /* minutes */
          if (token!=NULL)
          {
            c[4]=strndup(token, strlen(token));
	    if (c[4]==NULL)
	      return -1;
            token=strsep(&running2, delimitercolon); /* seconds */
            if (token!=NULL)
            {
              c[5]=strndup(token, strlen(token));
	      if (c[5]==NULL)
		return -1;
              token=strsep(&running2, delimitercolon); /* mseconds */
              if (token!=NULL)
              {
                c[6]=strndup(token, strlen(token));
		if (c[6]==NULL)
	          return -1;
                token=strsep(&running2, delimitercolon); /* timezone */
                if (token!=NULL)
                {
                  c[7]=strndup(token, strlen(token));
                  if (c[7]==NULL)
	            return -1;
		  /* we will not record mseconds here since strptime() doesn't have it */
                  snprintf(datetime, URLSIZE, "%s %s %s %s %s %s %s", c[0], c[1], c[2], c[3], c[4], c[5], c[7]);
		}
              }
            }
          }
        }
      }
    }
  }
  strptime(datetime, "%Y %m %d %H %M %S %z", tp); /* year month day hour minute second */

  /* convert UTC in seconds since EPOCH */
  utc=mktime(tp);

  printtime=ctime(&utc);
  
  char buf[100];
  struct tm e0 = { .tm_year = 70, .tm_mday = 1 }, e1, new;

  /* get time_t EPOCH value for e0 (Jan. 1, 1970) */
  time_t pseudo = mktime(&e0);

  /* get gmtime for this value */
  e1 = *gmtime(&pseudo);
  e0.tm_sec += utc - diff_tm(&e1, &e0);
  local=e0.tm_sec;
  localt=localtime(&local);

  /* get human readable time */
  strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z", localt); 
  equakeformat->datetime_local=strndup(buf, strlen(buf));

  return 0;
}


long long diff_tm(struct tm *a, struct tm *b)
{
  return a->tm_sec - b->tm_sec
    +60LL*(a->tm_min - b->tm_min)
    +3600LL*(a->tm_hour - b->tm_hour)
    +86400LL*(a->tm_yday - b->tm_yday)
    +(a->tm_year-70)*31536000LL
    -(a->tm_year-69)/4*86400LL
    +(a->tm_year-1)/100*86400LL
    -(a->tm_year+299)/400*86400LL
    -(b->tm_year-70)*31536000LL
    +(b->tm_year-69)/4*86400LL
    -(b->tm_year-1)/100*86400LL
    +(b->tm_year+299)/400*86400LL;
}
