/*
**  POP3Folder.m
**
**  Copyright (c) 2001, 2002, 2003
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**  
**  This library 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
**  Lesser General Public License for more details.
**  
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <Pantomime/POP3Folder.h>

#include <Pantomime/Connection.h>
#include <Pantomime/Constants.h>
#include <Pantomime/Message.h>
#include <Pantomime/POP3CacheManager.h>
#include <Pantomime/POP3CacheObject.h>
#include <Pantomime/POP3Message.h>
#include <Pantomime/POP3Store.h>
#include <Pantomime/TCPConnection.h>
#include <Pantomime/NSData+Extensions.h>
#include <Pantomime/NSString+Extensions.h>

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>

#include <limits.h>
#include <stdio.h>
#include <string.h>

#if !defined(UINT_MAX)
#define UINT_MAX (unsigned int)~0
#endif

//
//
//
@implementation POP3Folder

- (id) initWithName: (NSString *) theName
{
  self = [super initWithName: theName];

  [self setLeaveOnServer: YES];
  [self setRetainPeriod: 0];

  // We initialize our UID cache
  sizeCache = [[NSMutableDictionary alloc] init];
  UIDCache = [[NSMutableDictionary alloc] init];
  count = -1;
  size = -1;

  return self;
}


//
//
//
- (void) dealloc
{
  RELEASE(sizeCache);
  RELEASE(UIDCache);

  [super dealloc];
}


//
//
//
- (void) deleteMessageAtIndex: (int) theIndex
{
  POP3Store *aStore;
  
  aStore = (POP3Store *)[self store];
  
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"DELE %i", theIndex]];
  
  if ( ![aStore responseFromServerIsValid: NULL] ) 
    {
      NSDebugLog(@"Unable to delete the message on the POP3 server...");
    }
}


//
// This method is used to fetch a message at a specified index
// in the POP3 Inbox folder.
//
- (NSData *) prefetchMessageAtIndex: (int) theIndex
{
  return [self prefetchMessageAtIndex: theIndex  numberOfLines: UINT_MAX];
}


//
//
//
- (NSData *) prefetchMessageAtIndex: (int) theIndex
		      numberOfLines: (unsigned int) theNumberOfLines
{
  NSMutableData *aMutableData;
  NSAutoreleasePool *pool;
  POP3Store *aStore;

  int length;

  // We obtain the pointer to our store
  aStore = (POP3Store *)[self store];
  
  // We create our local autorelease pool
  pool = [[NSAutoreleasePool alloc] init];
  length = 0;
      
  // Then we transfer our messsage by getting the length of the message first
  length = [self lengthOfMessageAtIndex: theIndex];

  // We create our mutable data object holding the message as raw source
  aMutableData = [[NSMutableData alloc] initWithCapacity: length];

  // We ask for a number of lines or for the complete message
  if ( theNumberOfLines == UINT_MAX )
    {
      [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"RETR %i", theIndex]];
    }
  else
    {
      [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"TOP %i %i", theIndex, theNumberOfLines]];
    }

  if ( ![aStore responseFromServerIsValid: NULL] )
    {
      NSDebugLog(@"POP3Folder: Invalid request for message index = %d", theIndex);
      RELEASE(aMutableData);
      RELEASE(pool);
      return nil;
    }
  
  while ( YES )
    {
      NSData *theData;

      theData = [[aStore tcpConnection] readDataToEndOfLine];
 
      if ( [theData hasCSuffix: "\r\n"] )
	{
	  // We verify if we're at the end of the message
	  if ( ([theData length] > 2) && [theData hasCPrefix: ".\r\n"] )
	    {
	      break;
	    }
	  // If not, replace \r\n (CRLF) by \n (LF)
	  else
	    {
	      NSMutableData *aSubData;
	      
	      aSubData = [NSMutableData dataWithData: [theData subdataToIndex: ([theData length] - 2)]];
	      [aSubData appendCString: "\n"];
	      theData = aSubData;
	    }
	} // if ( [theLine hasSuffix: @"\r\n"] )
      
      //
      // We append the line in our message
      // RFC1939, chap. 3 says:
      //
      //    When examining a multi-line response, the client checks
      //    to see if the line begins with the termination octet.  If so and if
      //    octets other than CRLF follow, the first octet of the line (the
      //    termination octet) is stripped away.
      //
      if ( [theData length] )
	{
	  char c;

	  [theData getBytes: &c  length: 1];

	  if ( c == '.' )
	    {
	      [aMutableData appendData: [theData subdataFromIndex: 1]];
	    }
	  else
	    {
	      [aMutableData appendData: theData];
	    }
	}
    } // while ( YES ) ...
      
  // We release our local pool
  RELEASE(pool);

  return AUTORELEASE(aMutableData);
}


//
// This method is used to cache the messages from the POP3 server
// locally (in memory).
//
- (BOOL) prefetch
{
  BOOL didTransferMessages;
  NSData *aData;
  int i, aCount;

  didTransferMessages = NO;
  aCount = [self count];

  for (i = 1; i <= aCount; i++)
    {
      aData = [self prefetchMessageAtIndex: i];
      
      if ( aData )
	{
	  POP3Message *aMessage;
	  
	  // The data read is valid, we can now add the message to our folder
	  aMessage = [[POP3Message alloc] initWithData: aData];
	  
	  // We set some initial properties to our message
	  [aMessage setInitialized: YES];
	  [aMessage setMessageNumber: i];
	  [aMessage setFolder: self];
	  [aMessage setSize: [aData length]];
	  [aMessage setUID: [self UIDOfMessageAtIndex: i]];
	  
	  [self appendMessage: aMessage];
	  RELEASE(aMessage);

	  didTransferMessages = YES;
	}
    } // for
  
  // We mark it as deleted if we need to
  if ( ![self leaveOnServer] )
    {
      for (i = 1; i <= aCount; i++)
	{
	  [self deleteMessageAtIndex: i];
	}
    }
  else if ( [self retainPeriod] > 0 )
    {
      [self _deleteOldMessagesWithMessageCount: aCount];
    }
  
  return didTransferMessages;
}


//
// This method returns the number of message in this POP3 folder.
// It re-implements the method of the super class.
// 
- (int) count
{
  if ( count < 0 )
    {
      [self _stat];
    }
  
  return count;
}


//
// This method returns the size of the "INBOX" POP3 folder.
// It re-implements the method of the super class since we don't need to
// (and don't want to) prefetch all messages in order to know the total
// size of the folder.
//
- (long) size
{
  if ( size < 0 )
    {
      [self _stat];
    }

  return size;
}


//
// This method does nothing.
//
- (void) close
{
  // We do nothing.
}


//
// This method is used to read the UID (obtained from the command UIDL)
// of a message.
//
- (NSString *) UIDOfMessageAtIndex: (int) theIndex
{
  NSString *aString;

  aString = [UIDCache objectForKey: [NSNumber numberWithInt: theIndex]];

  // We cache all UIDs
  if ( !aString )
    {
      POP3Store *aStore;
      char buf[71];
      int num;
  
      aStore = (POP3Store *)[self store];
      [[aStore tcpConnection] writeLine: @"UIDL"];

      // We read our "+OK response"
      aString = [[aStore tcpConnection] readStringToEndOfLine];
      
      if ( ![aString hasCaseInsensitivePrefix: @"+OK"] )
	{
	  return nil;
	}
      
      // We read the "unique-id listing" for the first message
      aString = [[aStore tcpConnection] readStringToEndOfLine];

      while ( [aString characterAtIndex: 0] != '.' )
	{
	  memset(buf, 0, 71);
	  sscanf([aString cString],"%i %s\r\n", &num, buf);
	  aString = [NSString stringWithCString: buf];
	
	  [UIDCache setObject: aString
		    forKey: [NSNumber numberWithInt: num]];
	  aString = [[aStore tcpConnection] readStringToEndOfLine];
	}
	
	// We get the UID from our newly created cache, for the index wanted.
	aString = [UIDCache objectForKey: [NSNumber numberWithInt: theIndex]];
    }
 
  return aString;
}


//
// This command is used to read the length of a message
// obtained by the command LIST
//
- (int) lengthOfMessageAtIndex: (int) theIndex
{
  NSNumber *aNumber;

  aNumber = [sizeCache objectForKey: [NSNumber numberWithInt: theIndex]];

  // We cache all size information
  if ( !aNumber )
    {
      POP3Store *aStore;
      NSString *aString;
      int num, length;
      
      aStore = (POP3Store *)[self store];
      [[aStore tcpConnection] writeLine: @"LIST"];

      // We read our "+OK response"
      aString = [[aStore tcpConnection] readStringToEndOfLine];
      
      if ( ![aString hasCaseInsensitivePrefix: @"+OK"] )
	{
	  return 0;
	}
   
      // We read the size for the first message
      aString = [[aStore tcpConnection] readStringToEndOfLine];

      while ( [aString characterAtIndex: 0] != '.' )
	{
	  sscanf([aString cString],"%i %i\r\n", &num, &length);

	  [sizeCache setObject: [NSNumber numberWithInt: length]
		    forKey: [NSNumber numberWithInt: num]];
	  aString = [[aStore tcpConnection] readStringToEndOfLine];
	}
	
      // We get the UID from our newly created cache, for the index wanted.
      aNumber = [sizeCache objectForKey: [NSNumber numberWithInt: theIndex]];
    }

  return [aNumber intValue];
}


//
// Return YES/NO depending if we leave or not the messages on the POP3 Server
//
- (BOOL) leaveOnServer
{
  return leaveOnServer;
}


//
// This method is used to set the flag to leave or not the messages on the POP3
// server after fetching them.
//
- (void) setLeaveOnServer: (BOOL) aBOOL
{
  leaveOnServer = aBOOL;
}


//
//
//
- (int) retainPeriod
{
  return retainPeriod;
}


//
// The retain period is set in days.
//
- (void) setRetainPeriod: (int) theRetainPeriod
{
  retainPeriod = theRetainPeriod;
}


//
//
//
- (int) mode
{
  return PantomimeReadOnlyMode;
}


//
// This method does nothing except returning an empty array.
// Expunging a POP3 folder doesn't make a lot of sense.
//
- (NSArray *) expunge: (BOOL) returnDeletedMessages
{
  int aCount;

  aCount = [self count];

  // We mark it as deleted if we need to
  if ( ![self leaveOnServer] )
    {
      int i;

      for (i = 1; i <= aCount; i++)
	{
	  [self deleteMessageAtIndex: i];
	}
    }
  else if ( [self retainPeriod] > 0 )
    {
      [self _deleteOldMessagesWithMessageCount: aCount];
    }

  // FIXME : return the list of message if needed
  return [NSArray array];
}


//
// In POP3, we do nothing.
//
- (NSArray *) search: (NSString *) theString
                mask: (int) theMask
             options: (int) theOptions
{
  return [NSArray array];
}

@end


//
// Private methods
//
@implementation POP3Folder (Private)

- (void) _deleteOldMessagesWithMessageCount: (int) theCount
{
  int i;

  for (i = theCount; i >= 1; i--)
    {
      POP3CacheObject *pop3CacheObject;
      NSString *anUIDString;
      
      NS_DURING
	{
	  // We ask for the UIDL of the message
	  anUIDString = [self UIDOfMessageAtIndex: i];
	  
	  // We get our POP3 cached object
	  pop3CacheObject = [[self cacheManager] findPOP3CacheObject: anUIDString];
	  
	  if ( pop3CacheObject )
	    {
	      NSCalendarDate *date;
	      int days;
	      
	      // We get the days interval between our two dates
	      date = [NSCalendarDate calendarDate];
	      [date years: NULL
		    months: NULL
		    days: &days
		    hours: NULL
		    minutes: NULL
		    seconds: NULL
		    sinceDate: [pop3CacheObject date]];
	      if ( days >= [self retainPeriod] )
		{
		  NSDebugLog(@"Deleting message with UID %@ since it's %d days old", anUIDString, days);
		  [self deleteMessageAtIndex: i];
		}
	    }
	}
      NS_HANDLER
	{
	  NSDebugLog(@"POP3Folder: Error occured while deleting message %d");
	}
      NS_ENDHANDLER
    }
}


//
//
//
- (void) _stat
{
  POP3Store *aStore;
  NSString *aString;
  
  aStore = (POP3Store *)[self store];
  count = size = 0;
  
  // We sent the STAT command to our POP3 server
  [[aStore tcpConnection] writeLine: @"STAT"];
  
  // We read back the response and we put it in our buffer buf
  aString = [[aStore tcpConnection] readStringToEndOfLine];
  
  if ( !aString )
    {
      NSDebugLog(@"POP3Folder: An error occured while STATing the POP3 folder.");
      return;
    }
  
  // We get the number of messages in the mailbox by scanning our buffer
  sscanf([aString cString], "+OK %i %li\r\n", &count, &size);
}

@end
