#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "popfetch.h"
#include "vauth.h"

char *domain = NULL;
struct vqpasswd *pw = NULL;
int status = S_NONE, n_tries = 0;
struct msg_t *msglist = NULL, *msg_t = NULL;

int pop_init(struct vqpasswd *lpw, char *ldomain)
{
  if (lpw == NULL)
     return 0;

  if (ldomain == NULL)
     return 0;

  pw = lpw;
  domain = ldomain;

  if (!(pw->pw_clear_passwd))
     return -1;

  file_init();

  status = S_NONE;
  return 1;
}

int pop_loop(void)
{
  if (status == S_NONE)
     pop_none();

  else if (status == S_CONN)
     pop_conn();

  else if (status & S_AUTH)
     pop_auth();

  else if (status & S_END)
     return 0;

  return 1;
}

int pop_none(void)
{
  int ret = 0;

  ret = socket_init();
  if (!ret) {
     pop_fail();
     return;
  }

  ret = socket_connect();
  if (!ret) {
     pop_fail();
     return;
  }

  status = S_CONN;
}

int pop_conn(void)
{
  int ret = 0;
  char *p = NULL;
  char b[MAX_QUERY_BUFFER] = { 0 };
 
  ret = pop_write(NULL, "+OK");
  if (!ret)
     return 0;

  memset((char *)b, 0, MAX_QUERY_BUFFER);
  snprintf(b, MAX_QUERY_BUFFER, "USER %s@%s\n", pw->pw_name, domain);

  ret = pop_write(b, "+OK");
  if (!ret)
     return 0;

  memset((char *)b, 0, MAX_QUERY_BUFFER);
  snprintf(b, MAX_QUERY_BUFFER, "PASS %s\n", pw->pw_clear_passwd);

  ret = pop_write(b, "+OK");
  if (!ret)
     return 0;

  status |= S_AUTH;
}

int pop_auth(void)
{ 
  int ret = 0;
 
  if (!(status & S_LIST)) {
     status |= S_LIST;

     ret = pop_write("LIST\n", "+OK");
     if (!ret)
        return 0;     
  }
  
  if (status & S_DELE)
     ret = pop_dele();

  else if (status & S_RETR)
     ret = pop_retr();  

  else if (status & S_LIST) {
     ret = pop_list();
     if (ret == 2)
        status |= S_RETR;
  }

  return ret;
}

int pop_fail(void)
{
  n_tries++;

  if (n_tries >= MAX_ATTEMPTS) {
#ifdef POP_DEBUG
     printf("Made %d attempts to fetch mail; but failed\n", n_tries);
#endif
     status = S_END;
  }
  
  else
     status = S_NONE;
}

int pop_write(char *send, char *expect)
{
  int ret = 0;
  char *p = NULL;

  if (send) {
     ret = socket_write(send);
     if (!ret) {
#ifdef POP_DEBUG
        printf("Failed on write [%s] expecting [%s]\n", send, expect);
#endif
        pop_fail();
        return 0;
     }

#ifdef POP_DEBUG
     printf("-> %s", send);
#endif
  }

  ret = socket_read();
  if (!ret) {
     pop_fail();
     return 0;
  }

  for (ret = 0, p = socket_parse(); p; p = socket_parse()) {      
#ifdef POP_DEBUG
      printf("<- [%s]\n", p);
#endif

      if (ret == 0) {
         if (!(strncasecmp(p, expect, strlen(expect))))
            ret = 1;
      }      
  }

  socket_post_parse();

  if (!ret) {
#ifdef POP_DEBUG
     if (send)
        printf("Sent [%s] expecting [%s]; but got something else\n",
   	       send, expect);
     else
        printf("Expected [%s]; but got something else\n");
#endif

     pop_fail();
  }

  return ret;
}

int pop_list(void)
{
  char *p = NULL;
  int ret = 0, eret = 0;

  msglist = (struct msg_t *)malloc(sizeof(struct msg_t));
  if (msglist == NULL) {
     pop_fail();
     return 0;
  }

  msglist->next = NULL;
  msg_t = msglist;

  eret = 0;

  while(1) {
     ret = socket_read();
     if (ret == -1) {
        pop_fail();
        return 0;
     }
     
     if (ret == 0)
        break;

     for (p = socket_parse(); p; p = socket_parse()) {
         eret = pop_parse_list(p);
         if (!eret)
            break;
     }

     ret = eret;
  }

  if (!eret)
     pop_fail();

  return eret;
}

int pop_parse_list(char *data)
{
  struct msg_t *m = NULL;
  char *h = NULL, *t = NULL;

  if (*data == '.')
     return 2;    

  for (h = t = data; *h; h++) {
      if (*h == ' ')
         break;
  }

  if (!(*h))
     return 0;

  *h++ = '\0';

  if (!(*h))
     return 0;

  m = (struct msg_t *)malloc(sizeof(struct msg_t));
  if (m == NULL) {
     pop_fail();
     return 0;
  }

  memset((struct msg_t *)m, 0, sizeof(struct msg_t));

  m->id = strdup(t);
  m->status = F_NONE;
  m->bytes = atol(h);
  m->next = NULL;

  msg_t->next = m;
  msg_t = m;

#ifdef POP_DEBUG
  printf("Added message %s: %lu byte(s)\n", m->id, m->bytes);
#endif

  return 1;
}

int pop_retr(void)
{
  int ret = 0;
  struct msg_t *m = NULL;

  if (msglist->next == NULL) {
#ifdef POP_DEBUG
     printf("No messages to fetch!\n");
#endif

     pop_write("QUIT\n", "+OK");

     status = S_END;
     return 1;
  }

  for (m = msglist; m->next; m = m->next) {
      ret = pop_retr_fetch(m->next);  
      if (!ret)
         break;
  }

  if (!ret)
     return 0;

  status |= S_DELE;

  return 1;  
}

int pop_retr_fetch(struct msg_t *m)
{
  int ret = 0;
  char *p = NULL, b[MAX_QUERY_BUFFER] = { 0 }, stat = 0;

  memset((char *)b, 0, MAX_QUERY_BUFFER);
  snprintf(b, MAX_QUERY_BUFFER, "RETR %s\n", m->id);

  ret = socket_write(b);
  if (!ret) {
     pop_fail();
     return 0;
  }

#ifdef POP_DEBUG
  printf("-> %s", b);
#endif

  ret = socket_read();
  if (!ret) {
     pop_fail();
     return 0;
  }

  p = socket_parse();
  if (!p) {
     pop_fail();
     return 0;
  }

  if (strncasecmp(p, "+OK", 3)) {
     pop_fail();  
     return 0;
  }

#ifdef POP_DEBUG
  printf("<- [%s]\n", p);
#endif

  ret = file_open();
  if (!ret) {
     pop_fail();
     return 0;
  }

  stat = 0;

  while(1) { 
     p = socket_parse();

     if (p) {
        if (!(*p))
           stat = 1;

        else if ((stat == 1) && (!(strcmp(p, ".")))) {
	   m->status = F_WROTE;
           break;
        }

        else {
           if (stat == 1)
              file_write(NULL);

           stat = 0;
        }

        file_write(p);
     }
    
     else {
        ret = socket_read();
        if (ret == -1) {
           pop_fail();
           return 0;
        }

        if (ret == 0)
           break;
     }
  }

  file_close();

  socket_post_parse();

  return 1;
}

int pop_dele(void)
{
  int ret = 0;
  struct msg_t *m = NULL;
  char b[MAX_QUERY_BUFFER] = { 0 };

  for (m = msglist; m->next; m = m->next) {
      if (!(m->next->status == F_WROTE)) {
#ifdef POP_DEBUG
         printf("Message %s not written to disk?\n", m->next->id);
#endif
      }
      else {
         memset((char *)b, 0, MAX_QUERY_BUFFER);
         snprintf(b, MAX_QUERY_BUFFER, "DELE %s\n", m->next->id);

         ret = pop_write(b, "+OK");
         if (!ret)
            return 0;
      }
  }

  ret = pop_write("QUIT\n", "+OK");
  
  status = S_END;

  return 1;
}
