#include "imap4d.h"

static int
filesearch(Msg *m, char *file, char *pat)
{
       char buf[Bufsize + 1];
       int n, nbuf, npat, fd, ok;

       npat = strlen(pat);
       if(npat >= Bufsize/2)
               return 0;

       fd = msgfile(m, file);
       if(fd < 0)
               return 0;
       ok = 0;
       nbuf = 0;
       for(;;){
               n = read(fd, &buf[nbuf], Bufsize - nbuf);
               if(n <= 0)
                       break;
               nbuf += n;
               buf[nbuf] = '\0';
               if(cistrstr(buf, pat) != nil){
                       ok = 1;
                       break;
               }
               if(nbuf > npat){
                       memmove(buf, &buf[nbuf - npat], npat);
                       nbuf = npat;
               }
       }
       close(fd);
       return ok;
}

static int
headersearch(Msg *m, char *hdr, char *pat)
{
       char *s, *t;
       int ok, n;
       Slist hdrs;

       n = m->head.size + 3;
       s = emalloc(n);
       hdrs.next = nil;
       hdrs.s = hdr;
       ok = 0;
       if(selectfields(s, n, m->head.buf, &hdrs, 1) > 0){
               t = strchr(s, ':');
               if(t != nil && cistrstr(t + 1, pat) != nil)
                       ok = 1;
       }
       free(s);
       return ok;
}

static int
addrsearch(Maddr *a, char *s)
{
       char *ok, *addr;

       for(; a != nil; a = a->next){
               addr = maddrstr(a);
               ok = cistrstr(addr, s);
               free(addr);
               if(ok != nil)
                       return 1;
       }
       return 0;
}

static int
datecmp(char *date, Search *s)
{
       Tm tm;

       date2tm(&tm, date);
       if(tm.year < s->year)
               return -1;
       if(tm.year > s->year)
               return 1;
       if(tm.mon < s->mon)
               return -1;
       if(tm.mon > s->mon)
               return 1;
       if(tm.mday < s->mday)
               return -1;
       if(tm.mday > s->mday)
               return 1;
       return 0;
}

enum{
       Simp    = 0,
       Sinfo   = 1<<0,
       Sbody   = 1<<2,
};

int
searchld(Search *s)
{
       int r;

       for(r = 0; (r & Sbody) == 0 && s; s = s->next)
       switch(s->key){
       case SKall:
       case SKanswered:
       case SKdeleted:
       case SKdraft:
       case SKflagged:
       case SKkeyword:
       case SKnew:
       case SKold:
       case SKrecent:
       case SKseen:
       case SKunanswered:
       case SKundeleted:
       case SKundraft:
       case SKunflagged:
       case SKunkeyword:
       case SKunseen:
       case SKuid:
       case SKset:
               break;
       case SKlarger:
       case SKsmaller:
       case SKbcc:
       case SKcc:
       case SKfrom:
       case SKto:
       case SKsubject:
       case SKbefore:
       case SKon:
       case SKsince:
       case SKsentbefore:
       case SKsenton:
       case SKsentsince:
               r = Sinfo;
               break;
       case SKheader:
               if(cistrcmp(s->hdr, "message-id") == 0)
                       r = Sinfo;
               else
                       r = Sbody;
               break;
       case SKbody:
               break;          /* msgstruct doesn't do us any good */
       case SKtext:
       default:
               r = Sbody;
               break;
       case SKnot:
               r = searchld(s->left);
               break;
       case SKor:
               r = searchld(s->left) | searchld(s->right);
               break;
       }
       return 0;
}

/* important speed hack for apple mail */
int
msgidsearch(char *s, char *hdr)
{
       char c;
       int l, r;

       l = strlen(s);
       c = 0;
       if(s[0] == '<' && s[l-1] == '>'){
               l -= 2;
               s += 1;
               c = s[l-1];
       }
       r = hdr && strstr(s, hdr) != nil;
       if(c)
               s[l-1] = c;
       return r;
}

/*
* free to exit, parseerr, since called before starting any client reply
*
* the header and envelope searches should convert mime character set escapes.
*/
int
searchmsg(Msg *m, Search *s, int ld)
{
       uint ok, id;
       Msgset *ms;

       if(m->expunged)
               return 0;
       if(ld & Sbody){
               if(!msgstruct(m, 1))
                       return 0;
       }else if (ld & Sinfo){
               if(!msginfo(m))
                       return 0;
       }
       for(ok = 1; ok && s != nil; s = s->next){
               switch(s->key){
               default:
                       ok = 0;
                       break;
               case SKnot:
                       ok = !searchmsg(m, s->left, ld);
                       break;
               case SKor:
                       ok = searchmsg(m, s->left, ld) || searchmsg(m, s->right, ld);
                       break;
               case SKall:
                       ok = 1;
                       break;
               case SKanswered:
                       ok = (m->flags & Fanswered) == Fanswered;
                       break;
               case SKdeleted:
                       ok = (m->flags & Fdeleted) == Fdeleted;
                       break;
               case SKdraft:
                       ok = (m->flags & Fdraft) == Fdraft;
                       break;
               case SKflagged:
                       ok = (m->flags & Fflagged) == Fflagged;
                       break;
               case SKkeyword:
                       ok = (m->flags & s->num) == s->num;
                       break;
               case SKnew:
                       ok = (m->flags & (Frecent|Fseen)) == Frecent;
                       break;
               case SKold:
                       ok = (m->flags & Frecent) != Frecent;
                       break;
               case SKrecent:
                       ok = (m->flags & Frecent) == Frecent;
                       break;
               case SKseen:
                       ok = (m->flags & Fseen) == Fseen;
                       break;
               case SKunanswered:
                       ok = (m->flags & Fanswered) != Fanswered;
                       break;
               case SKundeleted:
                       ok = (m->flags & Fdeleted) != Fdeleted;
                       break;
               case SKundraft:
                       ok = (m->flags & Fdraft) != Fdraft;
                       break;
               case SKunflagged:
                       ok = (m->flags & Fflagged) != Fflagged;
                       break;
               case SKunkeyword:
                       ok = (m->flags & s->num) != s->num;
                       break;
               case SKunseen:
                       ok = (m->flags & Fseen) != Fseen;
                       break;
               case SKlarger:
                       ok = msgsize(m) > s->num;
                       break;
               case SKsmaller:
                       ok = msgsize(m) < s->num;
                       break;
               case SKbcc:
                       ok = addrsearch(m->bcc, s->s);
                       break;
               case SKcc:
                       ok = addrsearch(m->cc, s->s);
                       break;
               case SKfrom:
                       ok = addrsearch(m->from, s->s);
                       break;
               case SKto:
                       ok = addrsearch(m->to, s->s);
                       break;
               case SKsubject:
                       ok = cistrstr(m->info[Isubject], s->s) != nil;
                       break;
               case SKbefore:
                       ok = datecmp(m->info[Iunixdate], s) < 0;
                       break;
               case SKon:
                       ok = datecmp(m->info[Iunixdate], s) == 0;
                       break;
               case SKsince:
                       ok = datecmp(m->info[Iunixdate], s) > 0;
                       break;
               case SKsentbefore:
                       ok = datecmp(m->info[Idate], s) < 0;
                       break;
               case SKsenton:
                       ok = datecmp(m->info[Idate], s) == 0;
                       break;
               case SKsentsince:
                       ok = datecmp(m->info[Idate], s) > 0;
                       break;
               case SKuid:
                       id = m->uid;
                       goto set;
               case SKset:
                       id = m->seq;
               set:
                       for(ms = s->set; ms != nil; ms = ms->next)
                               if(id >= ms->from && id <= ms->to)
                                       break;
                       ok = ms != nil;
                       break;
               case SKheader:
                       if(cistrcmp(s->hdr, "message-id") == 0)
                               ok = msgidsearch(s->s, m->info[Imessageid]);
                       else
                               ok = headersearch(m, s->hdr, s->s);
                       break;
               case SKbody:
               case SKtext:
                       if(s->key == SKtext && cistrstr(m->head.buf, s->s)){
                               ok = 1;
                               break;
                       }
                       ok = filesearch(m, "body", s->s);
                       break;
               }
       }
       return ok;
}