/*
 * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
 * 
 *     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.
 */ 

/*
 * Rather crude POP3 support.
 */

#include "../config.h"
#include "mutt.h"
#include "mailbox.h"
#include "mx.h"

#ifdef BALSA_USE_THREADS
#include "thread_msgs.h"
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>

static int getLine (int fd, char *s, int len)
{
  char ch;
  int bytes = 0;

  while (read (fd, &ch, 1) > 0)
  {
    *s++ = ch;
    bytes++;
    if (ch == '\n')
    {
      *s = 0;
      return (bytes);
    }
    /* make sure not to overwrite the buffer */
    if (bytes == len - 1)
    {
      *s = 0;
      return bytes;
    }
  }
  *s = 0;
  return (-1);
}

#ifndef LIBMUTT
static int getPass (void)
{
  if (!PopPass)
  {
    char tmp[SHORT_STRING];
    tmp[0] = '\0';
    if (mutt_get_password ("POP Password: ", tmp, sizeof (tmp)) != 0
	|| *tmp == '\0')
      return 0;
    PopPass = safe_strdup (tmp);
  }
  return 1;
}
#endif
void mutt_fetchPopMail (void)
{
  struct sockaddr_in sin;
#if SIZEOF_LONG == 4
  long n;
#else
  int n;
#endif
  struct hostent *he;
  char buffer[2048];
  char msgbuf[SHORT_STRING];
#ifdef BALSA_USE_THREADS
  MailThreadMessage *threadmsg;
  char threadbuf[160];
#endif
  char uid[80], last_uid[80];
  int s, i, last = 0, tmp, total, msgs, bytes, err = 0;
  int num_bytes, tot_bytes;
  int first_msg;

  CONTEXT ctx;
  MESSAGE *msg = NULL;

  if (!PopHost)
  {
    mutt_error ("POP host is not defined.");
    return;
  }

  if (!PopUser)
  {
    mutt_error ("No POP username is defined.");
    return;
  }
#ifndef LIBMUTT   
  if (!getPass ()) return;
#endif
  s = socket (AF_INET, SOCK_STREAM, IPPROTO_IP);

  memset ((char *) &sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons (PopPort);

  if ((n = inet_addr (NONULL(PopHost))) == -1)
  {
    /* Must be a DNS name */
    if ((he = gethostbyname (NONULL(PopHost))) == NULL)
    {
      mutt_error ("Could not find address for host %s.", PopHost);
      return;
    }
    memcpy ((void *)&sin.sin_addr, *(he->h_addr_list), he->h_length);
  }
  else
    memcpy ((void *)&sin.sin_addr, (void *)&n, sizeof(n));
  
  mutt_message ("Connecting to %s", inet_ntoa (sin.sin_addr));

  if (connect (s, (struct sockaddr *) &sin, sizeof (struct sockaddr_in)) == -1)
  {
    mutt_perror ("connect");
    return;
  }
  
  if (getLine (s, buffer, sizeof (buffer)) == -1)
    goto fail;

  if (mutt_strncmp (buffer, "+OK", 3) != 0)
  {
    mutt_remove_trailing_ws (buffer);
    mutt_error (buffer);
    goto finish;
  }

  snprintf (buffer, sizeof(buffer), "user %s\r\n", PopUser);
  write (s, buffer, mutt_strlen (buffer));

  if (getLine (s, buffer, sizeof (buffer)) == -1)
    goto fail;

  if (mutt_strncmp (buffer, "+OK", 3) != 0)
  {
    mutt_remove_trailing_ws (buffer);
    mutt_error (buffer);
    goto finish;
  }
  
  snprintf (buffer, sizeof(buffer), "pass %s\r\n", NONULL(PopPass));
  write (s, buffer, mutt_strlen (buffer));
  
  if (getLine (s, buffer, sizeof (buffer)) == -1)
    goto fail;

  if (mutt_strncmp (buffer, "+OK", 3) != 0)
  {
    if(PopPass)
      memset(PopPass, 0, mutt_strlen(PopPass));
    
    safe_free((void **) &PopPass); /* void the given password */
    mutt_remove_trailing_ws (buffer);
    mutt_error (buffer[0] ? buffer : "Server closed connection!");
    goto finish;
  }
  
  /* find out how many messages are in the mailbox. */
  write (s, "stat\r\n", 6);
  
  if (getLine (s, buffer, sizeof (buffer)) == -1)
    goto fail;

  if (mutt_strncmp (buffer, "+OK", 3) != 0)
  {
    mutt_remove_trailing_ws (buffer);
    mutt_error (buffer);
    goto finish;
  }
  
  sscanf (buffer, "+OK %d %d", &msgs, &bytes);

  if (msgs == 0)
  {
    mutt_message ("No new mail in POP mailbox.");
    goto finish;
  }

  if (mx_open_mailbox (NONULL(Spoolfile), M_APPEND, &ctx) == NULL)
    goto finish;

 first_msg = 1;


/*  
 *  If Messages are left on the server, be sure that we don't retrieve
 *  them a second time.  Do this by checking Unique IDs backward from the
 *  last message, until a previously downloaded message is found.
 */
 
 if ( !option (OPTPOPDELETE) )
 {

  for( i = msgs; i > 0 ; i--) 
  {
  	snprintf( buffer, sizeof(buffer), "uidl %d\r\n", i);
  	write( s, buffer, strlen(buffer) );

  	getLine (s, buffer, sizeof (buffer));
  	
  	if (strncmp (buffer, "+OK", 3) != 0)
  	{
  		mutt_remove_trailing_ws( buffer);
  		mutt_error (buffer);
  		goto finish; 
          /* I'm following the convention of libmutt, not my coding style */
  	}
  	sscanf( buffer, "+OK %d %s", &tmp, uid );

	if( i == msgs )  
	{
	   strcpy( last_uid, uid ); // save uid of the last message for exit
	   if( PopUID[0] == 0 )
	     break;  /* no previous UID set */
	 }

    if( *PopUID && strcmp( uid, PopUID ) == 0 )  
	{
	  /* 
	   * this message seen, so start w/ next in queue           *
	   * This will be larger than 'msgs' if no new messages     *
	   * forcing the for loop below to skip retrieving messages *
	   */
	    
      first_msg = i + 1; 
      break;
    }

   }
  } 
    	 

  total = msgs - first_msg + 1; /* will be used for display later */
 

  /*  Check for the total amount of bytes mail to be received */
  tot_bytes=0;
  for (i = first_msg ; i <= msgs ; i++)
  {
    snprintf(buffer, sizeof(buffer), "list %d\r\n", i);
    write (s, buffer, strlen(buffer));
    if ( getLine (s, buffer, sizeof(buffer)) == -1) 
    {
      mx_fastclose_mailbox (&ctx);
      goto fail;
    }
    
    if (sscanf (buffer,"+OK %d %d",&i,&num_bytes) != 2) 
    {
      tot_bytes=-1;
      break;
    }
    tot_bytes += num_bytes;
  }


  num_bytes=0;  
  for (i = first_msg ; i <= msgs ; i++)
  {
    snprintf (buffer, sizeof(buffer), "retr %d\r\n", i);
    write (s, buffer, strlen (buffer));

#ifdef BALSA_USE_THREADS
    sprintf( threadbuf, "Retrieving Message %d of %d", 
	     i - first_msg + 1, total );
    MSGMAILTHREAD( threadmsg, MSGMAILTHREAD_MSGINFO, threadbuf,0,0 );
#endif

    if (getLine (s, buffer, sizeof (buffer)) == -1)
    {
      mx_fastclose_mailbox (&ctx);
      goto fail;
    }

    if (mutt_strncmp (buffer, "+OK", 3) != 0)
    {
      mutt_remove_trailing_ws (buffer);
      mutt_error (buffer);
      break;
    }

    if ((msg = mx_open_new_message (&ctx, NULL, M_ADD_FROM)) == NULL)
    {
      err = 1;
      break;
    }

    /* Now read the actual message. */
    FOREVER
    {
      char *p;
      int chunk;

      if ((chunk = getLine (s, buffer, sizeof (buffer))) == -1)
      {
	mutt_error ("Error reading message!");
	err = 1;
	break;
      }
      
#ifdef BALSA_USE_THREADS
      sprintf( threadbuf,"Received %d bytes of %d",num_bytes,tot_bytes);
      MSGMAILTHREAD(threadmsg, MSGMAILTHREAD_PROGRESS, threadbuf, num_bytes,tot_bytes); 
#endif
      /* check to see if we got a full line */
      if (buffer[chunk-2] == '\r' && buffer[chunk-1] == '\n')
      {
	if (mutt_strcmp(".\r\n", buffer) == 0)
	{
	  /* end of message */
	  break;
	}

	num_bytes += chunk;

	/* change CRLF to just LF */
	buffer[chunk-2] = '\n';
	buffer[chunk-1] = 0;
	chunk--;

	/* see if the line was byte-stuffed */
	if (buffer[0] == '.')
	{
	  p = buffer + 1;
	  chunk--;
	}
	else
	  p = buffer;
      }
      else
	p = buffer;
      
      fwrite (p, 1, chunk, msg->fp);
    }

    if (mx_commit_message (msg, &ctx) != 0)
    {
      mutt_error ("Error while writing mailbox!");
      err = 1;
    }

    mx_close_message (&msg);

    if (err)
      break;

    if (option (OPTPOPDELETE))
    {
      /* delete the message on the server */
      snprintf (buffer, sizeof(buffer), "dele %d\r\n", i);
      write (s, buffer, mutt_strlen (buffer));

      /* eat the server response */
      getLine (s, buffer, sizeof (buffer));
      if (mutt_strncmp (buffer, "+OK", 3) != 0)
      {
	err = 1;
        mutt_remove_trailing_ws (buffer);
	mutt_error (buffer);
	break;
      }
    }

    mutt_message ("%s [%d messages read]", msgbuf, i);
  }

  if (msg)
  {
    mx_commit_message (msg, &ctx);
    mx_close_message (&msg);
  }
  mx_close_mailbox (&ctx);

  if (err)
  {
    /* make sure no messages get deleted */
    write (s, "rset\r\n", 6);
    getLine (s, buffer, sizeof (buffer)); /* snarf the response */
  }

finish:

  /* exit gracefully */
  write (s, "quit\r\n", 6);
  getLine (s, buffer, sizeof (buffer)); /* snarf the response */
  close (s);
  strcpy( PopUID, last_uid );
  return;

  /* not reached */

fail:

  mutt_error ("Server closed connection!");
  close (s);
}
