#include "process.h"
#include "lock_maildrop.h"
#include "rw.h"
#include "md5.h"
#include <stdio.h>
#include <syslog.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

static struct mail * mails;
static int number_mails;


void get_stats(long * msg, long * size) {
  long s = 0, m = 0;
  int i;
  for (i=0;i<number_mails;i++) {
    if (!mails[i].deleted) {
      m++;
      s+= mails[i].adjsize;
    }
  }
  *msg = m;
  *size = s;
}

static void list_all(int fd) {
  char line[MAXLINE+1];
  int i;
  write_line(fd,"+OK\r\n");
  for (i=0;i<number_mails;i++) {
    if (!mails[i].deleted) {
      snprintf(line,MAXLINE,"%d %ld\r\n",i+1,mails[i].adjsize);
      write_line(fd,line);
    }
  }
  write_line(fd,".\r\n");
}

static void list_msg(int fd, int num) {
  char line[MAXLINE+1];
  if (num<1 || (num-1) >= number_mails || mails[num-1].deleted==1) {
    write_line(fd,"-ERR no such message\r\n");
    return;
  }
  snprintf(line,MAXLINE,"+OK %d %ld\r\n",num,mails[num-1].adjsize);
  write_line(fd,line);
}

void do_list(int fd, char * line) {
  int msg_num = -1;
  if (sscanf(line+5,"%d",&msg_num)!=1) {
    list_all(fd);
  } else {
    list_msg(fd,msg_num);
  }
}

static void print_msg(int fd, int msg_num) {
  char line[MAXLINE+1];
  int done = 0;
  FILE * f = mails[msg_num].fh;
  char * ptr;

  rewind(f);
  line[0] = 0;
  fgets(line,MAXLINE,f);

  while (!feof(f)) {
    if ((line[0]=='\n' || line[0]=='\r') && !done) {
      write_line(fd,"\r\n");
      done = 1;
    } else {
      ptr = strchr(line, '\r');
      if (ptr == NULL) ptr = strchr(line, '\n');
      if (ptr != NULL) ptr[0] = 0;
      if (line[0] == '.') write_line(fd, ".");
      write_line(fd, line);
      write_line(fd, "\r\n");
    }
    fgets(line,MAXLINE,f);
  }
  rewind(f);
}

void retrieve_msg(int fd, char * line) {
  int msg_num;
  if (sscanf(line+5,"%d",&msg_num)!=1) {
    write_line(fd,"-ERR invalid arguments\r\n");
    return;
  }
  if (msg_num<1 || (msg_num-1) >= number_mails || mails[msg_num-1].deleted==1) {
    write_line(fd,"-ERR no such message\r\n");
    return;
  }
  write_line(fd,"+OK\r\n");
  print_msg(fd,msg_num-1);
  write_line(fd,".\r\n");
}

void show_top_msg(int fd, char * l) {
  int msg_num, lines;
  int i;
  char line[MAXLINE+1];
  char * ptr;

  if (sscanf(l+4,"%d %d",&msg_num,&lines)!=2) {
    write_line(fd,"-ERR invalid arguments\r\n");
    return;
  }
  if (msg_num<1 || 
      (msg_num-1) >= number_mails || 
      mails[msg_num-1].deleted==1) {
    write_line(fd,"-ERR no such message\r\n");
    return;
  }
  write_line(fd,"+OK\r\n");
  rewind(mails[msg_num-1].fh);
  line[0] = 0;
  fgets(line,MAXLINE,mails[msg_num-1].fh);

  while ((line[0]!='\n' && line[0]!='\r') && !feof(mails[msg_num-1].fh)) {
    ptr = strchr(line, '\r');
    if (ptr == NULL) ptr = strchr(line, '\n');
    if (ptr != NULL) ptr[0] = 0;
    if (line[0] == '.') write_line(fd, ".");
    write_line(fd, line);
    write_line(fd, "\r\n");
    fgets(line,MAXLINE,mails[msg_num-1].fh);
  }
  if (!feof(mails[msg_num-1].fh)) {
    write_line(fd,"\r\n");
    fgets(line,MAXLINE,mails[msg_num-1].fh);
    for (i=0;i<lines && !feof(mails[msg_num-1].fh);i++) {
      ptr = strchr(line, '\r');
      if (ptr == NULL) ptr = strchr(line, '\n');
      if (ptr != NULL) ptr[0] = 0;
      if (line[0] == '.') write_line(fd, ".");
      write_line(fd, line);
      write_line(fd, "\r\n");
      fgets(line,MAXLINE,mails[msg_num-1].fh);
    }
  }
  write_line(fd,".\r\n");
  rewind(mails[msg_num-1].fh);
}


void delete_msg(int fd, char * line) {
  int msg_num;
  if (sscanf(line+5,"%d",&msg_num)!=1) {
    write_line(fd,"-ERR invalid arguments\r\n");
    return;
  }
  if (msg_num<1 || (msg_num-1) >= number_mails) {
    write_line(fd,"-ERR no such message\r\n");
    return;
  }
  mails[msg_num-1].deleted = 1;
  write_line(fd,"+OK\r\n");
}

void reset_msg(void) {
  int i;
  for (i=0;i<number_mails;i++) {
    mails[i].deleted = 0;
  }
}

int process_mails(char * maildrop) {
  FILE * f;
  char line[MAXLINE+1];
  int had_cr_or_nl;
  long adj;

  f = fopen(maildrop,"r");
  if (f==NULL)
    return 0;
  lock_fd(fileno(f));
  mails = NULL;
  number_mails = 0;
  fgets(line,MAXLINE,f);
  if (strncmp(line,"From ",5)!=0) {
    if (feof(f))
      return 1;
    else
      return 0;
  }
  had_cr_or_nl = 1;
  adj = 0;
  while (!feof(f)) {
    if (strncmp(line,"From ",5)==0 && had_cr_or_nl) {
      if (number_mails>0) {
        mails[number_mails-1].size = ftell(mails[number_mails-1].fh);
        mails[number_mails-1].adjsize = mails[number_mails-1].size + adj;
        rewind(mails[number_mails-1].fh);
      }
      adj = 0;
      number_mails++;
      mails = realloc(mails,number_mails*sizeof(struct mail));
      if (mails==NULL) {
        return 0;
      }
      mails[number_mails-1].fh = tmpfile();
      if (mails[number_mails-1].fh == NULL) {
        return 0;
      }
      mails[number_mails-1].deleted = 0;
    }
    fputs(line,mails[number_mails-1].fh);
    fgets(line,MAXLINE,f);
    if (strchr(line, '\r') != NULL) {
      had_cr_or_nl = 1;
    } else if (strchr(line, '\n') != NULL) {
      had_cr_or_nl = 1;
      adj++;
    } else {
      had_cr_or_nl = 0;
    }
  }
  unlock_fd(fileno(f));
  fclose(f);
  fputs(line,mails[number_mails-1].fh);
  mails[number_mails-1].size = ftell(mails[number_mails-1].fh);
  mails[number_mails-1].adjsize = mails[number_mails-1].size + adj;
  rewind(mails[number_mails-1].fh);

  return 1;
}

static void fcopy(FILE * f1, FILE * f2) {
  char line[MAXLINE+1];
  fgets(line,MAXLINE,f2);
  while (!feof(f2)) {
    fputs(line,f1);
    fgets(line,MAXLINE,f2);
  }
}

void do_update(char * maildrop) {
  int i;
  FILE * f = fopen(maildrop,"w");
  if (f==NULL) {
    syslog(LOG_ERR,"%s: %s: %s","failed to write to maildrop",maildrop,strerror(errno));
    return;
  }
  lock_fd(fileno(f));
  for (i=0;i<number_mails;i++) {
    if (!mails[i].deleted) {
      fcopy(f,mails[i].fh);
    }
    fclose(mails[i].fh);
  }
  unlock_fd(fileno(f));
  fclose(f);
  free(mails);
}

void output_uidl(int fd, int msgnum) {
  char linebuf[128];
  unsigned char h[17];
  FILE * f;
  char buf[256];

  f = mails[msgnum].fh;
  rewind(f);
  fread(buf,255,1,f);
  rewind(f);
  md5_buffer(buf, 255, h);

  sprintf(linebuf, "%d %08lx%08lx%08lx%08lx\r\n", msgnum+1,
    (unsigned long) h[0] + (h[1]<<8) + (h[2]<<16) + (h[3]<<24),
    (unsigned long) h[4] + (h[5]<<8) + (h[6]<<16) + (h[7]<<24),
    (unsigned long) h[8] + (h[9]<<8) + (h[10]<<16) + (h[11]<<24),
    (unsigned long) h[12] + (h[13]<<8) + (h[14]<<16) + (h[15]<<24)
  );

  write_line(fd,linebuf);
}

void show_uidl(int fd, char * line) {
  int msgnum;

  while (line[0] == ' ') line ++;
  if (isdigit(line[0])) {
    msgnum = atoi(line);
    if ((msgnum < 1) || (msgnum > number_mails) || (mails[msgnum-1].deleted)) {
      write_line(fd, "-ERR no such message\r\n");
      return;
    }
    write_line(fd, "+OK ");
    output_uidl(fd, msgnum-1);
  } else {
    write_line(fd, "+OK\r\n");
    for (msgnum=0;msgnum<number_mails;msgnum++) {
      if (mails[msgnum].deleted) continue;
      output_uidl(fd, msgnum);
    }
    write_line(fd, ".\r\n");
  }
}
