/*
 * WMMail - Window Maker Mail
 *
 * Copyright (c) 1996, 1997, 1998  Per Liden
 * Copyright (c) 1997, 1998  Bryan Chan
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * wmmail.c: essential routines
 *
 * $Id: wmmail.c,v 1.1 2000/07/02 20:38:01 bryan.chan Exp $
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <X11/IntrinsicP.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/extensions/shape.h>

#include "wmmail.h"
#include "wmutil.h"
#include "properties.h"
#include "std_icons.h"

int end_of_cycle = False;

#ifdef MBOX_SUPPORT
#  include "mbox.h"
#endif

#ifdef MH_SUPPORT
#  include "mh.h"
#endif

#ifdef MAILDIR_SUPPORT
#  include "maildir.h"
#endif

#ifdef POP3_SUPPORT
#  include "pop3.h"
#endif

#ifdef IMAP_SUPPORT
#  include "imap.h"
#endif


/* function prototypes */
int   main(int, char **);
void  help(void);
void  read_configuration_data(void);
void  initialize(int, char **);
void  create_appicon(void);
void  redraw_appicon(void);
void  animate(XtPointer, XtIntervalId *);
Pixel get_pixel_by_color(char *);
void  update_status(XtPointer, XtIntervalId *);
void  handle_expose(Widget, XtPointer, XEvent *);
void  handle_button(Widget, XtPointer, XEvent *);


int main(int argc, char **argv)
{
  initialize(argc, argv);

  create_appicon();

  update_status(NULL, NULL);
  animate(NULL, NULL);

  XtAppMainLoop(wmmail_context);

  return 0;
}


void help(void)
{
  fprintf(stderr, 
    "WMMail version %s%s by Bryan Chan (bryan.chan@utoronto.ca)\n"
    "based on asmail version 0.50, by Per Liden (per@rsn.hk-r.se)\n"
    "\n"
    "usage: \t%s [options ...] [<pathname>]\n"
    "\n"
    "<pathname>:\tfull path of defaults domain to use\n"
    "\n"
    "options:\t-help, -h           display this message\n"
    "        \t-quiet, -q          suppress all error messages\n"
    "        \t-swallowed, -s      show window as well as appicon;\n"
    "        \t                    good for swallowing with AfterStep\n"
    "\n",
    VERSION,
#ifdef DEBUG
    "(with debug support),",
#else
    ",",
#endif
    app_name);

  exit(1);
}


void read_configuration_data(void)
{
  char *domain;

  if (user_specified_domain)
  {
    if (!parse_gnustep_domain(user_specified_domain))
      exit(-1);
  }
  else if (domain = find_resource(WMMAIL_CLASS, "Defaults"))
  {
    if (!parse_gnustep_domain(domain))
      exit(-1);
  }
  else
    croak("cannot find any configuration data, using default values...");
}


/* SIGCHLD handler */
void sig_chld(int signo)
{
  waitpid((pid_t) -1, NULL, WNOHANG);
  signal(SIGCHLD, sig_chld);
}


/* handle command line options, open the configuration file for reading */
void initialize(int argc, char **argv)
{
  /* determine how this program was invoked */
  if ((char *) rindex(argv[0], '/') != NULL)
    app_name = (char *) rindex(argv[0], '/') + 1;
  else
    app_name = argv[0];

  wmmail_widget = XtAppInitialize(&wmmail_context, WMMAIL_CLASS, 
                                  (XrmOptionDescRec *) NULL, 0, &argc, argv,
                                  (String *) NULL, (ArgList) NULL, 0);

  disp = XtDisplay(wmmail_widget);

  root = RootWindow(disp, DefaultScreen(disp));

  wmmail_font = XLoadFont(disp, DEFAULT_FONT);

  if (argc > 1)
  {
    int i;

    for (i = 1; i < argc; i++)
      if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "-h"))
      {
        help();
        exit(0);
      }
      else if (!strcmp(argv[i], "-quiet") || !strcmp(argv[i], "-q"))
      {
        keep_quiet = True;
      }
      else if (!strcmp(argv[i], "-swallowed") || !strcmp(argv[i], "-s"))
      {
        use_appicon_only = False;
      }
      else if (argv[i][0] == '-')
      {
        croak("no such option '%s'", argv[i]);
        exit(-1);
      }
      else if (user_specified_domain != NULL)
      {
        croak("cannot use more than one defaults domain");
        exit(-1);
      }
      else
        user_specified_domain = wstrdup(argv[i]);
  }

  read_configuration_data();

  /* if we're cycling text, set up for animation of mailbox names */
  if (display_names) {
    int i;

    for (i = 0; i < 3; i++) {
        animations[i]->text_cycle = 0;
    }
  }

  /* if no mailbox is specified, assume $MAIL is a UNIX mbox */
#ifdef MBOX_SUPPORT
  if (mailbox_list == NULL && getenv("MAIL") != NULL)
  {
    proplist_t   key, path;
    Mailbox     *mailbox;
    char        *mailbox_path;
    struct stat  t;

    mailbox_path = getenv("MAIL");

    if (mailbox_path && !stat(mailbox_path, &t))
    {
      if (S_ISREG(t.st_mode))
      {
        mailbox = (Mailbox *) wmalloc(sizeof(Mailbox));

        mailbox->name             = wstrdup("Inbox");
        mailbox->type             = TYPE_MBOX;
        mailbox->status           = NO_MAIL;
        mailbox->total_mail_count = 0;
        mailbox->new_mail_count   = 0;
        mailbox->update_interval  = DEFAULT_INTERVAL;
        mailbox->last_update      = 0;
        mailbox->size             = t.st_size;

        key = PLMakeString("Path");
        path = PLMakeString(mailbox_path);
        mailbox->options = PLMakeDictionaryFromEntries(key, path, NULL);
        PLRelease(key);
        PLRelease(path);

        mailbox_list = list_cons(mailbox, mailbox_list);
      }
      else
      {
        croak("default mailbox %s not a UNIX mbox; aborting", mailbox_path);
        exit(-1);
      }
    }
    else
    {
      croak("cannot stat default mailbox %s", mailbox_path ? mailbox_path : "");
      exit(-1);
    }

  }
  else
#endif
  if (mailbox_list == NULL)
  {
    croak("no usable mailbox (already tried $MAIL)");
    exit(-1);
  }

  /* zombie children are dealt with in the SIG_CHLD handler */
  signal(SIGCHLD, sig_chld);
}


void create_appicon(void)
{
  Arg            args[4];
  XGCValues      gc_values;

  load_std_icons();

  wmmail_appicon = XtCreateWidget("AppIcon", topLevelShellWidgetClass,
                                  wmmail_widget, NULL, 0);

  XtAddEventHandler(wmmail_appicon, ExposureMask, False, 
                    (XtEventHandler) handle_expose, NULL);

  XtAddEventHandler(wmmail_appicon, ButtonPressMask, False,
                    (XtEventHandler) handle_button, NULL);

  XtResizeWidget(wmmail_appicon, animations[wmmail_status]->attributes.width, 
                 animations[wmmail_status]->attributes.height, 1);

  XtRealizeWidget(wmmail_appicon);

  XtSetArg(args[0], XtNiconName, app_name);
  XtSetArg(args[1], XtNiconWindow, XtWindow(wmmail_appicon));

  if (!use_appicon_only)
    XtSetArg(args[2], XtNinitialState, NormalState);
  else
    XtSetArg(args[2], XtNinitialState, WithdrawnState);
  
  XtSetValues(wmmail_widget, args, 3); 

  if (!use_appicon_only)
  {
    XtAddEventHandler(wmmail_widget, ExposureMask, False, 
                      (XtEventHandler) handle_expose, NULL);

    XtAddEventHandler(wmmail_widget, ButtonPressMask, False,
                      (XtEventHandler) handle_button, NULL);
  }

  XtResizeWidget(wmmail_widget, animations[wmmail_status]->attributes.width, 
                 animations[wmmail_status]->attributes.height, 1);

  XtRealizeWidget(wmmail_widget);

  XtMapWidget(wmmail_widget);

  shape_mask = XCreatePixmap(disp, XtWindow(wmmail_widget),
                               animations[wmmail_status]->attributes.width,
                               animations[wmmail_status]->attributes.height, 1);

  shape_mask_gc = XCreateGC(disp, shape_mask, 0, &gc_values);
  XSetGraphicsExposures(disp, shape_mask_gc, False);
  XSetBackground(disp, shape_mask_gc, 0);
  XSetForeground(disp, shape_mask_gc, 1);

  wmmail_gc = XCreateGC(disp, root, 0, &gc_values);
  XSetGraphicsExposures(disp, wmmail_gc, False);
  XSetBackground(disp, wmmail_gc, get_pixel_by_color("black"));
  XSetForeground(disp, wmmail_gc, get_pixel_by_color(wmmail_color));

  redraw_appicon();
}


void redraw_appicon(void)
{
  char          buf[30];
  XTextItem     num_of_msg;

  /* create shape mask from the mask of the XPM animation frame */
  XFillRectangle(disp, shape_mask, shape_mask_gc, 0, 0,
                 animations[wmmail_status]->attributes.width,
                 animations[wmmail_status]->attributes.height);
                 
  XCopyArea(disp, animations[wmmail_status]->mask, shape_mask, 
            shape_mask_gc, 0, 0, 
            animations[wmmail_status]->attributes.width,
            animations[wmmail_status]->attributes.height,
            0, 0);

  /* generate NumOfMsg if NumOfMsgMode is used */
  if (num_of_msg_mode != SHOW_NONE || display_names)
  {
    Mailbox *mailbox;
    int      i, j, sum_new_mail, sum_total_mail;
    static char *box_name = NULL;
    static int box_new = 0;
    static int box_total = 0;

    j = animations[wmmail_status]->text_cycle;
    if (j != -1) {
      box_name = NULL;
    }
 
    for (i = 0, sum_new_mail = 0, sum_total_mail = 0; i < mailbox_count; i++)
    {
      mailbox = list_nth(i, mailbox_list);
      sum_new_mail += mailbox->new_mail_count;
      sum_total_mail += mailbox->total_mail_count;

      if (j > -1 && mailbox->status == NEW_MAIL) {
        j--;
        if (j <= 0) {
          box_name = mailbox->name;
          box_new = mailbox->new_mail_count;
          box_total = mailbox->total_mail_count;
#ifdef DEBUG
          croak("box_name: %s box_new: %d box_total: %d",
                box_name, box_new, box_total);
#endif
        }
      }
    }

    if (j >= 0) {
      end_of_cycle = True;
      box_name = NULL;
      box_new = box_total = 0;
    }

    if (display_names && box_name) {
      switch(num_of_msg_mode) {
        case SHOW_NEW_OVER_TOTAL:
          sprintf(buf, "%s %d/%d", box_name, box_new, box_total);
          break;
        case SHOW_NEW_ONLY:
          sprintf(buf, "%s %d", box_name, box_new);
          break;
        case SHOW_TOTAL_ONLY:
          sprintf(buf, "%s %d", box_name, box_total);
          break;
        case SHOW_NONE:
          sprintf(buf, "%s", box_name);
          break;
      }
    } else {
      switch(num_of_msg_mode) {
        case SHOW_NEW_OVER_TOTAL:
          sprintf(buf, "%d/%d", sum_new_mail, sum_total_mail);
          break;
        case SHOW_NEW_ONLY:
          sprintf(buf, "%d", sum_new_mail);
          break;
        case SHOW_TOTAL_ONLY:
          sprintf(buf, "%d", sum_total_mail);
          break;
      }
    }

    num_of_msg.chars  = buf;
    num_of_msg.nchars = strlen(buf);            
    num_of_msg.delta  = 1;       
    num_of_msg.font   = wmmail_font;

    /* add the text to the shape mask */
    XDrawText(disp, shape_mask, shape_mask_gc,
              num_of_msg_x, num_of_msg_y, &num_of_msg, 1);

    XDrawText(disp, shape_mask, shape_mask_gc,
              num_of_msg_x, num_of_msg_y, &num_of_msg, 1);
  }
   
  /* apply the shape mask */
  XShapeCombineMask(disp, XtWindow(wmmail_appicon), ShapeBounding, 0, 0,
                    shape_mask, ShapeSet);

  if (!use_appicon_only)
    XShapeCombineMask(XtDisplay(wmmail_widget), XtWindow(wmmail_widget), ShapeBounding, 0, 0,
                      shape_mask, ShapeSet);

  /* draw XPM animation frame */
  XCopyArea(disp, animations[wmmail_status]->pixmap, XtWindow(wmmail_appicon),
            wmmail_gc, 0, 0, animations[wmmail_status]->attributes.width,
            animations[wmmail_status]->attributes.height, 0, 0);            

  if (!use_appicon_only)
    XCopyArea(XtDisplay(wmmail_widget), animations[wmmail_status]->pixmap, XtWindow(wmmail_widget),
              wmmail_gc, 0, 0, animations[wmmail_status]->attributes.width,
              animations[wmmail_status]->attributes.height, 0, 0);            

  /* draw NumOfMsg */
  if (num_of_msg_mode != SHOW_NONE)
  {
    XDrawText(disp, XtWindow(wmmail_appicon), wmmail_gc, 
              num_of_msg_x, num_of_msg_y, &num_of_msg, 1);

    if (!use_appicon_only)
      XDrawText(disp, XtWindow(wmmail_widget), wmmail_gc,
                num_of_msg_x, num_of_msg_y, &num_of_msg, 1);
  }

  XFlush(disp);
}


void animate(XtPointer XtP, XtIntervalId *XtI)
{
  animations[wmmail_status]  = animations[wmmail_status]->next;

  if (animations[wmmail_status]->text_cycle != -1) {
    if (end_of_cycle) {
      animations[wmmail_status]->text_cycle = 0;
      end_of_cycle = False;
    } else {
      animations[wmmail_status]->text_cycle++;
    }
  }

  redraw_appicon();

  XtAppAddTimeOut(wmmail_context, anim_speed[wmmail_status], animate, NULL);
}


Pixel get_pixel_by_color(char *color_name)
{
  XColor            color;
  XWindowAttributes attributes;

  XGetWindowAttributes(disp, root, &attributes);
  color.pixel = 0;

  if (!XParseColor(disp, attributes.colormap, color_name, &color))
    croak("cannot parse color '%s', using black", color_name);
  else if (!XAllocColor(disp, attributes.colormap, &color))
    croak("cannot allocate color '%s', using black", color_name);

  return color.pixel;
}


void update_status(XtPointer XtP, XtIntervalId * XtI)
{
  Mailbox    *mailbox;
  static int update_in_progress = False;
  int        i, ret; 
  int        new_status         = NO_MAIL;
  int        beep               = False,
             redraw             = False,
             run                = False,
             next_update        = -1;     /* time to next update in seconds */

  if (!update_in_progress)
  {
    update_in_progress = True;
    mailbox_count = list_length(mailbox_list);

    for (i = 0; i < mailbox_count; i++)
    {
      mailbox = list_nth(i, mailbox_list);

      if (mailbox->last_update + mailbox->update_interval > time(NULL))
      {
        /* don't check this mailbox if it isn't time yet */
        ret = True;
      }
      else
      {
#ifdef DEBUG
        croak("checking mailbox %s", mailbox->name);
#endif

        if (mailbox->execute_on_update)
          exec_command(mailbox->execute_on_update);

#ifdef MBOX_SUPPORT
        if (IS_MBOX(mailbox))
          ret = MBOX_check(mailbox, &beep, &redraw, &run);
        else
#endif
#ifdef MH_SUPPORT
        if (IS_MH(mailbox))
	  ret = MH_check(mailbox, &beep, &redraw, &run);
        else
#endif
#ifdef MAILDIR_SUPPORT
        if (IS_MAILDIR(mailbox))
	  ret = MAILDIR_check(mailbox, &beep, &redraw, &run);
        else
#endif
#ifdef POP3_SUPPORT
        if (IS_POP3(mailbox))
          ret = POP3_check(mailbox, &beep, &redraw, &run);
        else
#endif
#ifdef IMAP_SUPPORT
        if (IS_IMAP(mailbox))
          ret = IMAP_check(mailbox, &beep, &redraw, &run);
        else
#endif
          /* ignore anything that we cannot handle; bug in properties.c? */
          continue;
      }
  
      /* delete mailbox if it has become rotten */
      if (!ret)
      {
        mailbox_list = list_remove_elem(mailbox_list, mailbox);
        wfree(mailbox->execute_on_update);
        wfree(mailbox->name);
        wfree(mailbox);
        mailbox_count--;
        i--;
#ifdef DEBUG
        croak("deleting mailbox %d", i + 1);
        croak("mailbox count: %d", mailbox_count);
#endif
      }
      else
      {
        int tmp = mailbox->last_update + mailbox->update_interval;

        if (mailbox->status > new_status)
          new_status = mailbox->status;

        if ( (next_update == -1) ||
             (time(NULL) < tmp && time(NULL) + next_update > tmp) )
          next_update = tmp - time(NULL);
      }
    }

    if (mailbox_count == 0)
    {
      croak("no functional mailbox left to monitor");
      exit(-1);
    }

    wmmail_status = new_status;

    update_in_progress = False;
  }

  if (run && exec_on_new != NULL)
    exec_command(exec_on_new);

  if (beep && use_beep)
    XBell(disp, 0);

  if (redraw)
    redraw_appicon();

#ifdef DEBUG
  croak("next update: %d", next_update);
#endif

  if (next_update < 0)
    next_update = DEFAULT_INTERVAL;

  XtAppAddTimeOut(wmmail_context, next_update * 1000, update_status, NULL);
}


void handle_expose(Widget w, XtPointer p, XEvent *e)
{
  if (e->xexpose.count == 0)
    redraw_appicon();
}


void handle_button(Widget w, XtPointer p, XEvent *e)
{
  static Time last_left_mouse_click   = -1;
  static Time last_middle_mouse_click = -1;

  if (e->xbutton.button == LEFT_BUTTON && exec_on_click != NULL)
  {
    if (last_left_mouse_click > 0 &&
        e->xbutton.time - last_left_mouse_click <= double_click_time)

    {
      last_left_mouse_click = -1;
      exec_command(exec_on_click);
    }

    last_left_mouse_click = e->xbutton.time;
  }
  else if (e->xbutton.button == MIDDLE_BUTTON)
  {
    if (last_middle_mouse_click > 0 && 
        e->xbutton.time - last_middle_mouse_click <= double_click_time)
    {
      /* double-clicking with the middle button overrides mailbox status */

      Mailbox *mailbox;
      int      i;
      int      new_status = NO_MAIL;

      for (i = 0; i < list_length(mailbox_list); i++)
      {
        mailbox = list_nth(i, mailbox_list);

        if (mailbox->status == NEW_MAIL)
          mailbox->status = OLD_MAIL;

        if (mailbox->status > new_status)
          new_status = mailbox->status;
      }

      if (wmmail_status != new_status)
      {
        wmmail_status = new_status;
        redraw_appicon();
      }
      else
        wmmail_status = new_status;
    }

    last_middle_mouse_click = e->xbutton.time;
  }
}


syntax highlighted by Code2HTML, v. 0.9.1