/** Projekt DR - Date Reminder (Hirnstupser) **/

/* Cop. 2000, 2017 asdala.de */

#if defined(__linux__)
#define LINUX
#elif defined(_WIN32)
#define WINDOWS
#else
#error Unbekanntes System!
#endif

#ifdef WINDOWS
#ifdef __LCC__ /* LCCs ANSIC90-Modus versteht windows.h und time.h nicht */
#define MB_ICONERROR 0x10
#define MB_ICONQUESTION 0x20
#define MB_ICONWARNING 0x30
#define MB_ICONINFORMATION 0x40
int __stdcall MessageBoxA(void*,const char*,const char*,unsigned int);
struct tm { int tm_sec,tm_min,tm_hour,tm_mday,tm_mon,tm_year,tm_wday,tm_yday,tm_isdst; };
typedef unsigned long time_t;
time_t time(time_t *_timer);
struct tm *localtime(const time_t *_timer);
time_t mktime(struct tm *_timeptr);
#else
#include <windows.h>
#endif
#include <io.h>
#ifdef MKRES
#include "res.h"
#endif
#define MSGINF MB_ICONINFORMATION
#define MSGWRN MB_ICONWARNING
#define MSGQST MB_ICONQUESTION
#define MSGERR MB_ICONERROR
#endif

#ifdef LINUX
#include <unistd.h>
#include <gtk/gtk.h>
#include "dr.xpm"
#define SUPPRESS_GTKERROR_NO_TRANSIENT_PARENT
#define MSGINF GTK_MESSAGE_INFO
#define MSGWRN GTK_MESSAGE_WARNING
#define MSGQST GTK_MESSAGE_QUESTION
#define MSGERR GTK_MESSAGE_ERROR
#endif

#ifndef MKRES
#define VER_INTERNALNAME "dr"
#define VER_PRODNAME "Hirnstupser"
#define VER_ABOUT ""
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef __LCC__
#include <time.h>
#endif
#define SECS2DAYS(a) ((a)/86400ul)
#define DATNAME "dr.txt"
#define TOKCOLSEP ","
#define TOKCMT ';'

char t0a[11];
const char *fname = DATNAME; /* Global, da in mehreren Funktionen benoetigt */
int gui;

/** GUI? **/
#ifdef WINDOWS
int initgui(void)
  {
  return !isatty(fileno(stdin));
  }
#endif
#ifdef LINUX
int initgui(int argc, char **argv)
  {
  if (isatty(fileno(stdin)))
    return 0;
  else
    return gtk_init_check(&argc, &argv);
  }
#endif

/** Generische Nachricht **/
void message(const char *msg, const char *title, int msgtype)
  {
  if (gui) {
    #ifdef LINUX
    GtkWidget *winparent, *dialog;
    GdkPixbuf *icon;
    #ifdef SUPPRESS_GTKERROR_NO_TRANSIENT_PARENT
    winparent = gtk_window_new(GTK_WINDOW_TOPLEVEL); /* Elternfenster nur erzeugen, nicht zeigen */
    #else
    winparent = NULL;
    #endif
    dialog = gtk_message_dialog_new(GTK_WINDOW(winparent),
      GTK_DIALOG_DESTROY_WITH_PARENT,msgtype,GTK_BUTTONS_CLOSE,"%s",msg);
    gtk_window_set_title(GTK_WINDOW(dialog),title);
    icon = gdk_pixbuf_new_from_xpm_data(dr_xpm);
    gtk_window_set_icon(GTK_WINDOW(dialog),icon);
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
    g_object_unref(icon);
    #ifdef SUPPRESS_GTKERROR_NO_TRANSIENT_PARENT
    gtk_widget_destroy(winparent);
    #endif
    #endif
    #ifdef WINDOWS
    MessageBoxA(NULL,msg,title,msgtype);
    #endif
    }
  else {
    puts(title);
    puts(msg);
    }
  }

/** Programmhilfe **/
int help(void)
  {
  message(
  VER_ABOUT
  "Aufruf: " VER_INTERNALNAME " [-?] [Kalenderdatei]\n"
  "\n"
  "Ohne Dateiangabe wird " DATNAME " gesucht.\n\n"
  "Beispiel einer Kalenderdatei:\n"
  "(';' = Kommentar, '?' = Platzhalter aktuelles Datum)\n\n"
  "; Datum, Davor, Danach, Nachricht\n"
  "14.08.2020, 1, 0, Auto Werkstatt\n"
  "12.10.????, 2, 1, Eva Geburtstag\n"
  "01.??.????, 2, 1, Abschlag zahlen\n",
  VER_PRODNAME " - Hilfe", MSGINF);
  return 1;
  }

/** Programmfehler **/
int error(const char s[])
  {
  message(s, VER_PRODNAME " - Fehler", MSGERR);
  return 2;
  }

/** Zeilenfehler in Kalenderdatei **/
int lnerror(int lnno)
  {
  char s[120];
  sprintf(s, "Formatfehler in %.80s Zeile %d", fname, lnno);
  return error(s);
  }

/** Jokerfeature: Ersetze '?'-Angaben im Ereignisdatum mit aktuellem Datum **/
int expand_datestr(const char *smp, char *s)
  {
  if (!s || !smp)
    return 0;
  while(*s && *smp) {
    if (*s == '?')
      *s = *smp;
    ++s; ++smp;
    }
  return 1;
  }

/** ASCII to struct dt **/
int atodt(const char *s, struct tm *dt)
  {
  int y, m, d;
  if (sscanf(s,"%2d.%2d.%4d",&d,&m,&y) != 3)
    return 0;
  memset(dt, 0, sizeof(struct tm));
  dt->tm_mday = d; dt->tm_mon = m-1; dt->tm_year = y-1900;
  return 1;
  }

/** struct dt to ASCII **/
void dttoa(const struct tm *dt, char *s)
  {
  if (dt && s)
    sprintf(s,"%02d.%02d.%4d",dt->tm_mday,dt->tm_mon+1,dt->tm_year+1900);
  }

/** Lese Zeile **/
int parseln(char *line, struct tm *txdt, int *txbefore, int *txafter, char **txmsg)
  {
  char *p;

  /* "01.11.????, 10, 1, Werkstatt Auto" als Beispiel zerlegt */

  /* "01.11.????" */
  if (!(p=strtok(line, TOKCOLSEP))) /* Komma als 1. Begrenzer durch strtok() mit \0 belegt */
    return 0;
  if (!expand_datestr(t0a,p))
    return 0;
  if (!atodt(p,txdt))
    return 0;

  /* "10" */
  if (!(p=strtok(NULL, TOKCOLSEP)))
    return 0;
  if (!sscanf(p, "%d", txbefore)) /* atoi() gibt leider bei Fehler und bei 0 je 0 zurueck */
    return 0;

  /* "1" */
  if (!(p=strtok(NULL, TOKCOLSEP)))
    return 0;
  if (!sscanf(p, "%d", txafter))
    return 0;

  /* "Werkstatt Auto" */
  if (!(*txmsg=strtok(NULL, "\n")))
    return 0;
  while (**txmsg==' ' || **txmsg=='\t')
    (*txmsg)++;

  return 1;
  }

/** Verarbeite Kalenderdatei **/
int process(FILE *f)
  { /* t0... = aktuelle Zeit, tx... = Ereigniszeit */
  time_t t0;
  struct tm *t0dt, txdt;
  char line[160], *bufp=NULL, *p, title[40], txa[11], *txmsg;
  int lnno=0, txbefore, txafter, txdays, t0days, buflen=0, bufsize=0;
  const int bufdelta = 1024;

  /* Berechne aktuelle Zeit t0, ausgedrueckt in time_t, struct dt und String */
  t0 = time(NULL); /* Sekunden seit 1970 */
  t0dt = localtime(&t0); /* Achtung, dt wird bei jedem Aufruf ueberschrieben */

  /* Beschraenke t0 auf ganze Tage, um mit tx vergleichbar zu sein */
  t0dt->tm_sec = 0; t0dt->tm_min = 0; t0dt->tm_hour = 0;
  t0 = mktime(t0dt);
  t0days = SECS2DAYS(t0);
  dttoa(t0dt, t0a); /* String fuer Jokerfeature und Titel */

  /* Lies Kalenderdatei */
  while (fgets(line, sizeof line, f)) {
    lnno++;
    /* Ueberlies Kommentare und Leerzeilen */
    if (line[0]==TOKCMT || line[0]=='\r' || line[0]=='\n')
      continue;

    /* Lies Zeile, hier auch tx (Ereigniszeit) */
    if (!parseln(line, &txdt, &txbefore, &txafter, &txmsg))
      return lnerror(lnno);
    txdays = SECS2DAYS(mktime(&txdt));

    /* Uhrenvergleich (und 2038 gibt's time_t mit 128 bit) */
    if (t0days >= txdays-txbefore && t0days <= txdays+txafter) {
      dttoa(&txdt, txa);
      /* Hol Speicher */
      if (buflen + bufdelta > bufsize) {
        bufsize += bufdelta;
        p = (char*)realloc(bufp, bufsize); /* Erster Aufruf mit NULL arbeitet wie malloc() */
        if (p == NULL) {
          if (bufp) {
            free(bufp);
            bufp = NULL;
            }
          return error("Speicher aus!");
          }
        bufp = p;
        }
      buflen += sprintf(bufp+buflen, "%s: %s\n", txa, txmsg);
      }
    } /* while */
  if (bufp) {
    sprintf(title, VER_PRODNAME " - Heute ist der %s", t0a);
    message(bufp, title, MSGINF);
    free(bufp);
    }
  return 0;
  }

/** Main **/
#if defined(__LCC__) || defined(LINUX)
int main(int argc, char **argv)
  {
  FILE *f;
  char s[110];
  int ret;
  gui = initgui(argc,argv);
  if (argc > 1) {
    fname = argv[1];
    if (*fname == '-')
      return help();
    }
  if ((f=fopen(fname,"rt")) == NULL) {
    sprintf(s,"Kann Datei '%.80s' nicht lesen!", fname);
    return error(s);
    }
  ret = process(f);
  fclose(f);
  return ret;
  }
#else
int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow)
  {
  FILE *f;
  char s[110];
  int ret;
  gui = initgui();
  if (*lpCmd) {
    fname = lpCmd;
    if (*fname == '-')
      return help();
    }
  if ((f=fopen(fname,"rt")) == NULL) {
    sprintf(s,"Kann Datei '%.80s' nicht lesen!", fname);
    return error(s);
    }
  ret = process(f);
  fclose(f);
  return ret;
  }
#endif