/*
 *    wmmaiload - A dockapp to monitor mails numbers
 *    Copyright (C) 2002  Thomas Nemeth <tnemeth@free.fr>
 *
 *    Based on work by Seiichi SATO <ssato@sh.rim.or.jp>
 *    Copyright (C) 2001,2002  Seiichi SATO <ssato@sh.rim.or.jp>
 *    and on work by Mark Staggs <me@markstaggs.net>
 *    Copyright (C) 2002  Mark Staggs <me@markstaggs.net>
 
 *    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.
 *
 */

#include "config.h"
#include "main.h"
#include "options.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <signal.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <time.h>
#include <math.h>
#include <pthread.h>
#include <string.h>
#ifdef HAVE_POP3
# include "pop3client.h"
#endif /* HAVE_POP3 */
#ifdef HAVE_IMAP
# include "imapclient.h"
#endif /* HAVE_IMAP */
#include "dockapp.h"

#ifdef HAVE_MH
# define MH_PROFILE ".mh_profile"
# define FCLOSE(data) {if (data) fclose(data);   data = NULL;}
# define DCLOSE(data) {if (data) closedir(data); data = NULL;}
# define DIRTRIM(data) dirtrim(data)

typedef enum { CWD = 1, HOME } BaseDir;
#endif /* HAVE_MH */


extern Bool             test_size;
extern time_t           check_delay;
extern MailBox         *mboxes;
extern char            *notif_cmd;
extern Bool             run_once;
extern int              boxnum;
extern Bool             use_threads;
extern pthread_mutex_t  mutex_list;



#ifdef HAVE_MBOX
static Bool check_mbox(MailBox *box, time_t now);
#endif /* HAVE_MBOX */
#ifdef HAVE_MAILDIR
static Bool list_dir(const char *dirname, int *seen, int *total);
static Bool check_maildir(MailBox *box, time_t now);
#endif /* HAVE_MAILDIR */
#ifdef HAVE_POP3
static Bool check_pop3(MailBox *box, time_t now);
#endif /* HAVE_POP3 */
#ifdef HAVE_IMAP
static Bool check_imap(MailBox *box, time_t now);
#endif /* HAVE_IMAP */
#ifdef HAVE_MH
static Bool check_mh(MailBox *box, time_t now);
static Bool isnumber(const char*);
static Bool isdir(const char*);
static Bool isfile(const char *);
static char *absname(const char*, int);
static char *dirfileconcat(const char *dir, const char *file);
static char *get_homedir(void);
static void dirtrim(char *s);
static char *mh_get_profile(void);
static char *mh_context_get(const char *);
static int mh_getseqcnt(const char*);
static int mh_getnmcnt(const char*);
static int mh_getnewmails(const char* );
static int mh_getmails(const char *mh_box);
#endif /* HAVE_MH */


#ifndef _GNU_SOURCE
int isblank(int c)
{
        return (c == ' ' || c == '\t');
}
#endif


#ifdef HAVE_MBOX
static Bool check_mbox(MailBox *box, time_t now)
{
        FILE *file;
        struct utimbuf u;
        struct stat s;
        char line[MAXSTRLEN + 1];
        int nbmails = 0;
        int nbreadmails = 0;
        int longline = 0;
        int inheaders = 0;
        int force = 0;

        force = ((now - box->time > 10) && (!box->updated));
        if (force)
        {
                box->time = 0;
                box->size = 0;
        }
        if (stat(box->entry, &s) == -1) return False;
        u.actime  = s.st_atime;
        u.modtime = s.st_mtime;
        if (!test_size && !filetime(box->entry, &(box->time), CTIME)) return False;
        if (test_size && !filesize(box->entry, &(box->size))) return False;
        if ((file = fopen(box->entry, "r")) == NULL) return False;
        while (! feof(file) )
        {
                fgets(line, MAXSTRLEN, file);
                if (! longline)
                {
                        if (strncmp(line, "From ", 5) == 0)
                        {
                                nbmails++;
                                inheaders = 1;
                        }
                        else if (inheaders == 1)
                        {
                                if (strcmp(line, "\n") == 0)
                                {
                                        inheaders = 0;
                                }
                                else if ( (strncmp(line, "Status:", 7) == 0) &&
                                          (strchr(line, 'R') != NULL) )
                                {
                                        nbreadmails++;
                                }
                        }
                }
                longline = (strchr(line, '\n') == NULL);
        }
        fclose (file);
        box->new = nbmails - nbreadmails;
        box->total = nbmails;
        box->updated = force;
        utime(box->entry, &u);

        return True;
}
#endif /* HAVE_MBOX */


#ifdef HAVE_MAILDIR
static Bool list_dir(const char *dirname, int *seen, int *total)
{
        DIR *dir;
        struct dirent *dir_ent;

        if ((dir = opendir(dirname)) == NULL) return False;
        while ((dir_ent = readdir(dir)) != NULL)
        {
                if (dir_ent->d_name[0] != '.')
                {
                        char * lastpart;
                        (*total)++;
                        if ((lastpart = strstr(dir_ent->d_name, ":2")) != NULL)
                        {
                                if (strstr(lastpart, "S") != NULL) (*seen)++;
                        }
                }
        }
        closedir(dir);
        return True;
}


static Bool check_maildir(MailBox *box, time_t now)
{
        char cur[MAXSTRLEN + 1], new[MAXSTRLEN + 1];
        int nbmails = 0;
        int nbreadmails = 0;
        int force = 0;
        time_t btime1, btime2;

        force = ((now - box->time > 10) && (!box->updated));
        if (force)
        {
                box->time = 0;
                box->size = 0;
        }
        btime1 = box->time;
        btime2 = box->time;
        memset(cur, 0, MAXSTRLEN + 1);
        memset(new, 0, MAXSTRLEN + 1);
        strcpy(cur, box->entry);
        strcpy(new, box->entry);
        strcat(cur, "/cur");
        strcat(new, "/new");
        if ((!fexist(cur)) || (!fexist(new))) return False;
        if ((!filetime(cur, &btime1, CTIME)) &&
            (!filetime(new, &btime2, CTIME))) return False;
        if (!list_dir(cur, &nbreadmails, &nbmails)) return False;
        if (!list_dir(new, &nbreadmails, &nbmails)) return False;
        box->new = nbmails - nbreadmails;
        box->total = nbmails;
        box->updated = force;
        if (btime1 != box->time)
                box->time = btime1;
        else
                box->time = btime2;

        return True;
}
#endif /* HAVE_MAILDIR */


#ifdef HAVE_POP3
static Bool check_pop3(MailBox *box, time_t now)
{
        Pop3 p3 = NULL;

        if ((now - box->time) > check_delay)
        {
                p3 = initPop3();
                box->time = time(0);

                if ( ((pop3Connect(p3, box->entry, box->port)) == -1) ||
                     ((pop3Login(p3, box->username, box->password)) == -1) ||
                     ((pop3CheckMail(p3)) == -1) )
                {
                        free_pop3(&p3);
                        return False;
                }
                pop3Quit(p3);
                box->new = pop3GetUnreadMess(p3);
                box->total = pop3GetTotalMess(p3);
                box->updated = True;
                box->time = time(0);
                free_pop3(&p3);
                return True;
        }
        return False;
}
#endif /* HAVE_POP3 */


#ifdef HAVE_IMAP
static Bool check_imap(MailBox *box, time_t now)
{
        Imap imap;

        if ((now - box->time) > check_delay)
        {
                imap = initImap();
                box->time = time(0);
                if ((imapConnect(imap, box->entry, box->port)) == -1)
                {
                        free(imap);
                        return False;
                }
                if ((imapLogin(imap, box->username, box->password)) == -1)
                {
                        imapQuit(imap);
                        free(imap);
                        return False;
                }
                if ((imapCheckMail(imap, box->folder)) == -1)
                {
                        imapQuit(imap);
                        free(imap);
                        return False;
                }
                imapQuit(imap);
                box->new	= imapGetUnreadMess(imap);
                box->total	= imapGetTotalMess(imap);
                box->updated	= True;
                box->time	= time(0);
                free(imap);
                return True;
        }
        return False;
}
#endif /* HAVE_IMAP */


#ifdef HAVE_MH
static Bool isnumber(const char *s)
{
        while (*s != '\0')
        {
                if (!isdigit(*s)) return False;
                s++;
        }

        return True;
}


static Bool isdir(const char *d)
{
        struct stat dir_stats;

        if ( (stat(d, &dir_stats) != 0) || !S_ISDIR(dir_stats.st_mode))
                return False;
        return True;
}


static Bool isfile(const char *f)
{
        struct stat file_stats;

        if ((stat(f, &file_stats) != 0) || !S_ISREG(file_stats.st_mode))
                return False;
        return True;
}


static char* absname(const char *name, int rt)
{
        char *retval = NULL;
        char tmp1[MAXSTRLEN];
        char *tmp2 = NULL;
        Bool rc = False;

        if (!name || !strlen(name)) goto bailout;

        if (name[0] == '/')
        {
                char *cp = NULL;

                retval = xstrdup(name);
                cp = retval + strlen(retval) - 1;
                if (*cp == '/') *cp = 0;
                rc = True;
                goto bailout;
        }

        switch (rt)
        {
                case CWD:
                        if (!getcwd(tmp1, MAXSTRLEN)) goto bailout;
                        if (!(retval = dirfileconcat(tmp1, name))) goto bailout;
                        break;
                case HOME:
                        if (!(tmp2 = get_homedir())) goto bailout;
                        retval = dirfileconcat(tmp2, name);
                        FREE(tmp2);
                        if (!retval) goto bailout;
                        break;
                default:
                        goto bailout;
                        break;
        }

        DIRTRIM(retval);

        rc = True;
bailout:
        if (!rc) FREE(retval);
        return retval;
}


static char *dirfileconcat(const char *dir, const char *file)
{
        char *tmp = NULL;

        if (!dir || !file) goto bailout;

        tmp = (char*)xmalloc(strlen(dir) + strlen(file) + 2);
        if (!tmp) goto bailout;

        strcpy(tmp, dir);
        DIRTRIM(tmp);
        strcat(tmp, "/");
        strcat(tmp, file);

bailout:
        return tmp;
}


static char *get_homedir(void)
{
        char *mypath = NULL;

        /* XXX is it usefull ??? */
        mypath = xstrdup(robust_home());
        if (mypath[0] != '\0') DIRTRIM(mypath);
        return mypath;
}


static char *mh_get_profile(void)
{
        char *cp = NULL;
        Bool rc = False;
        char *profile = NULL;

        if ((cp = getenv("MH")))
        {
                if (*cp |= '/')
                        profile = absname(cp, CWD);
                else
                        profile = xstrdup(cp);
        }
        else
                profile = absname(MH_PROFILE, HOME);

        if (!profile) goto bailout;

        if (!isfile(profile)) goto bailout;

        rc = True;
bailout:
        FREE(cp);
        if (!rc) FREE(profile);
        return profile;
}


static char *mh_context_get(const char *str)
{
        char *home;
        char *retval = NULL;
        char *profile = NULL;
        char line[MAXSTRLEN];
        FILE *fp = NULL;
        char *cp = NULL;

        if (!(home = get_homedir())) goto bailout;

        if (!(profile = mh_get_profile())) goto bailout;

        if (!(fp = fopen(profile, "r"))) goto bailout;

        while (fgets(line, MAXSTRLEN, fp))
        {
                cp = line;
                while (isblank(*cp)) cp++;
                if (strncasecmp(str, cp, strlen(str))) continue;

                cp += strlen(str);
                while (isblank(*cp)) cp++;
                if (*cp != ':') continue;

                cp++;
                while (isblank(*cp)) cp++;
                if (!(retval = xstrdup(cp))) continue;

                DIRTRIM(retval);
                break;
        }

        FCLOSE(fp);
bailout:

        FREE(home);
        FREE(profile);
        FCLOSE(fp);

        return retval;
}


static int mh_getseqcnt(const char *word)
{
        int retval = 0;

        if (isnumber(word))
                retval = 1;
        else
        {
                int start, end;

                if ((sscanf(word, "%d-%d", &start, &end) == 2) && (end > start))
                        retval = end - start + 1;
        }

        return retval;
}


static int mh_getnmcnt(const char *cp)
{
        char word[MAXSTRLEN];
        int i = 0;
        int retval = 0;

        while (*cp != '\0')
        {
                if (i == MAXSTRLEN) goto bailout;

                if (isspace(*cp))
                {
                        if (i)
                        {
                                word[i] = '\0';
                                retval += mh_getseqcnt(word);
                                i = 0;
                        }
                }
                else
                        word[i++] = *cp;

                cp++;
        }

        if (i)
        {
                word[i] = '\0';
                retval += mh_getseqcnt(word);
                i = 0;
        }

bailout:
        return retval;
}


static int mh_getnewmails(const char *mh_box)
{
        int nbunread = 0;
        char *tmp = NULL;
        char line[MAXSTRLEN];
        char *seqfile = NULL;
        char *unseen = NULL;
        FILE *fp;

        if (!mh_box) goto bailout;

        if (!isdir(mh_box)) goto bailout;

        if (!(tmp = mh_context_get("mh-sequences")) &&
            !(tmp = xstrdup(".mh_sequences")))
                goto bailout;

        seqfile = dirfileconcat(mh_box, tmp);
        FREE(tmp);
        if (!seqfile) goto bailout;

        if (!isfile(seqfile)) goto bailout;

        if (!(unseen = mh_context_get("unseen-sequence")) &&
            !(unseen = xstrdup("unseen")))
                goto bailout;

        if (!(fp = fopen(seqfile, "r"))) goto bailout;

        while (fgets(line, MAXSTRLEN, fp))
        {
                char *cp = line;

                while (isblank(*cp)) cp++;

                if (strncasecmp(unseen, cp, strlen(unseen))) continue;
                cp += strlen(unseen);

                while (isblank(*cp)) cp++;
                if (*cp != ':') continue;

                cp++;
                while (isblank(*cp)) cp++;
                nbunread = mh_getnmcnt(cp);
                break;
        }

        FCLOSE(fp);

bailout:
        FREE(unseen);
        FREE(seqfile);
        return nbunread;
}


static int mh_getmails(const char *mh_box)
{
        int nbmails = 0;
        DIR *dir = NULL;
        struct dirent *d_entry;

        if (!mh_box) goto bailout;
        if (!isdir(mh_box)) goto bailout;
        if (!(dir = opendir(mh_box))) goto bailout;

        while ((d_entry = readdir(dir)))
        {
                if (isnumber(d_entry->d_name))
                {
                        char * tmp = NULL;

                        if (!(tmp = dirfileconcat(mh_box, d_entry-> d_name)))
                                goto bailout;

                        DIRTRIM(tmp);

                        if (!isfile(tmp)) continue;

                        nbmails++;
                        FREE(tmp);
                }
        }

bailout:
        DCLOSE(dir);

        return nbmails;
}


static Bool check_mh(MailBox *box, time_t now)
{
        Bool retval = False;
        int force = 0;
        char *tmp = NULL;
        char *mh_box = NULL;
        int nbmails = 0;
        int nbunread = 0;
        char *path = NULL;

        if (!box) goto bailout;

        force = ((now - box->time > 10) && (!box->updated));
        if (force)
        {
                box->time = 0;
                box->size = 0;
        }

        if (!(tmp = mh_context_get("path"))) goto bailout;

        path = absname(tmp, HOME);
        FREE(tmp);
        if (!path) goto bailout;
        if (!isdir(path)) goto bailout;
        if (!box->entry) goto bailout;

        DIRTRIM(box->entry);

        if (!(mh_box = dirfileconcat(path, box->entry))) goto bailout;
        if (!isdir(mh_box)) goto bailout;

        nbmails = mh_getmails(mh_box);
        nbunread = mh_getnewmails(mh_box);

        box->new = nbunread;
        box->total = nbmails;
        box->updated = force;
        retval = True;
bailout:
        FREE(mh_box);
        FREE(path);

        return retval;
}


static void dirtrim(char *s)
{
        char *cp;

        cp = s + strlen(s) - 1;
        while (isspace(*cp)) cp--;
        if (*cp == '/')
                *cp = '\0';
        else
                *(cp + 1) = '\0';

        return ;
}
#endif /* HAVE_MH */


static int get_globnew()
{
        int globnew = 0;
        MailBox *box = mboxes;

        while (box)
        {
                globnew += box->new;
                box = box->next;
        }

        return globnew;
}


static Bool update_mailbox(MailBox *mailbox)
{
        time_t now;
        Bool   changed = False;

        time(&now);
        switch (mailbox->type)
        {
#ifdef HAVE_MBOX
                case MBOX:
                        changed = check_mbox(mailbox, now);
                        break;
#else
                case MBOX:
                        break;
#endif
#ifdef HAVE_MAILDIR
                case MAILDIR:
                        changed = check_maildir(mailbox, now);
                        break;
#else
                case MAILDIR:
                        break;
#endif
#ifdef HAVE_MH
                case MH:
                        changed = check_mh(mailbox, now);
                        break;
#else
                case MH:
                        break;
#endif
#ifdef HAVE_POP3
                case POP3:
                        changed = check_pop3(mailbox, now);
                        break;
                case HOTMAIL:
                        changed = check_pop3(mailbox, now);
                        break;
#else
                case POP3:
                        break;
                case HOTMAIL:
                        break;
#endif
#ifdef HAVE_IMAP
                case IMAP:
                        changed = check_imap(mailbox, now);
                        break;
#else
                case IMAP:
                        break;
#endif
                default:
                        break;
        }

        return changed;
}


static Bool update_mailboxes()
{
        int      globnew, prevnew;
        Bool     changed = False;
        MailBox *mailbox;
        time_t   now;

        mailbox = mboxes;
        prevnew = get_globnew();
        time(&now);
        while (mailbox)
        {
                if (update_mailbox(mailbox))
                {
                        changed = True;
                }
                mailbox = mailbox->next;
        }
        globnew = get_globnew();

        if ( (changed) && (notif_cmd) &&
             (
                     ( (globnew > prevnew) && (!run_once) ) ||
                     ( (globnew > 0) && (prevnew == 0) && (run_once) )
             )
           )
        {
                my_system(notif_cmd, NULL);
        }

        return changed;
}


static void check_mailbox(MailBox * box)
{
        box->in_thread = True;
        box->changed   = False;
        box->changed   = update_mailbox(box);
        box->in_thread = False;
}


static void check_mailbox_thread(void *data)
{
        MailBox *box   = get_mailbox((int) data);
        pthread_t       tid = pthread_self();

        pthread_detach(tid);
#if DEBUG_LEVEL>0
        printf("check_mailbox launched : mailbox = %s\n", box->entry);
#endif
        check_mailbox(box);
#if DEBUG_LEVEL>0
        printf("box %s checked\n", box->entry);
#endif
}


void check_boxes()
{
        MailBox *box = mboxes;

        while (box)
        {
                check_mailbox(box);
                box = box->next;
        }
}


static Bool check_changes()
{
        MailBox *box;
        Bool     ret = False;

        box = mboxes;
        while (box)
        {
                if (box->changed)
                {
                        ret = True;
                        break;
                }
                box = box->next;
        }
        return ret;
}


void mail_getnumbers(int* new, int* total, MailAlarm* bell)
{
        static int bnum = 0;
        int        newmails = 0, nbmails = 0, globnew = 0, globtot = 0, i;
        Bool       changed = False;
        MailAlarm  alrm;
        MailBox*   box;

#ifdef HAVE_THREADS
        if (use_threads)
        {
                pthread_mutex_lock(&mutex_list);
                changed = check_changes();
        }
        else
        {
                changed = update_mailboxes();
        }
#else
        changed = update_mailboxes();
#endif
        if (changed || (bnum != boxnum))
        {
                bnum = boxnum;
                box = mboxes;
                for (i = 0 ; box ; i++)
                {
                        globnew += box->new;
                        globtot += box->total;
                        if (i + 1 == boxnum)
                        {
                                newmails = box->new;
                                nbmails = box->total;
                        }
                        box = box->next;
                }
                if (boxnum != 0)
                {
                        alrm = NOALARM;
                        if (globnew > 0)
                        {
                                alrm =
                                    (globnew - newmails == globnew) ? NEWGLOB :
                                    (globnew - newmails  > 0)       ? NEWBOTH :
                                    (globnew - newmails == 0)       ? NEWHERE :
                                                                      NOALARM;
                        }
                }
                else
                {
                        newmails = globnew;
                        nbmails = globtot;
                        alrm = (globnew > 0) ? NEWGLOB : NOALARM;
                }
                *new = newmails;
                *total = nbmails;
                *bell = alrm;
        }
#ifdef HAVE_THREADS
        if (use_threads)
        {
                pthread_mutex_unlock(&mutex_list);
        }
#endif
}


#ifdef HAVE_THREADS
static void check_thread(void)
{
        pthread_t       tid = pthread_self();
        struct timespec tv  = {check_delay, 0};

        pthread_detach(tid);
#if DEBUG_LEVEL>0
        printf("check_thread launched.\n");
#endif
        while (use_threads)
        {
                MailBox *mb;
                int      bn = 0;
#if DEBUG_LEVEL>0
                printf("locking mutex.\n");
#endif
                pthread_mutex_lock(&mutex_list);
                mb = mboxes;
                while (mb)
                {
                        bn++;
                        if (! mb->in_thread)
                        {
                                pthread_t pthread_id;

#if DEBUG_LEVEL>0
                                printf("launching thread for mailbox %s\n",
                                                mb->entry);
#endif
                                if (pthread_create(&pthread_id,
                                                   NULL,
                                                   (void*)check_mailbox_thread,
                                                   (void*)bn) != 0)
                                {
                                        use_threads = False;
                                        break;
                                }
                        }
                        mb = mb->next;
                }
                pthread_mutex_unlock(&mutex_list);
#if DEBUG_LEVEL>0
                printf("mutex unlocked.\n");
#endif
                load_cfgfile(True);
#if DEBUG_LEVEL>0
                printf("configuration's changes checked.\n");
#endif
                nanosleep(&tv, NULL);
        }
}


void launch_threads()
{
        pthread_t           pthread_id;
        pthread_mutexattr_t mutex_attr;

        pthread_mutexattr_init(&mutex_attr);
        pthread_mutex_init(&mutex_list, &mutex_attr);
#if DEBUG_LEVEL>0
        printf("mutexe initialized.\n");
#endif

        if (pthread_create(&pthread_id, NULL, (void*)check_thread, NULL) != 0)
        {
                use_threads = False;
        }
}


void wait_for_threads()
{
        int ok = 0;

        while (! ok)
        {
                MailBox* p = mboxes;

                ok = 1;
                while (p)
                {
                        if (p->in_thread) ok = 0;
                        p = p->next;
                }
        }
}
#endif

