/*
**  IMAPStore.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/IMAPStore.h>

#include <Pantomime/Constants.h>
#include <Pantomime/Flags.h>
#include <Pantomime/FolderInformation.h>
#include <Pantomime/IMAPCacheManager.h>
#include <Pantomime/IMAPFolder.h>
#include <Pantomime/IMAPMessage.h>
#include <Pantomime/MD5.h>
#include <Pantomime/MimeUtility.h>
#include <Pantomime/NSData+Extensions.h>
#include <Pantomime/NSString+Extensions.h>
#include <Pantomime/TCPConnection.h>
#include <Pantomime/URLName.h>

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSCharacterSet.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSPathUtilities.h>
#include <Foundation/NSScanner.h>
#include <Foundation/NSValue.h>

#include <ctype.h>
#include <stdio.h>

#define HAS_TAGGED_PREFIX(str) (\
  [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ %@", [self lastTag], str]]\
)

#define HAS_UNTAGGED_PREFIX(str) (\
  [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %@", str]]\
)

#define HAS_UNTAGGED_SUFFIX(str) (\
  [aString hasCaseInsensitiveSuffix: [NSString stringWithFormat: @"%@", str]]\
)

#define CONNECTION_WAS_LOST() \
  if ( [self delegate] && \
       [[self delegate] respondsToSelector: connectionWasLostSelector] ) \
{ \
 [[self delegate] performSelector: connectionWasLostSelector \
		       withObject: self]; \
}

//
// Selectors for our Folder's delegate
//
#define connectionWasLostSelector   @selector(connectionWasLost:)
#define selMessagesWereReceived     @selector(messagesWereReceived:)
#define selMessagesWereExpunged     @selector(messagesWereExpunged:)
#define selMessagesFlagsHaveChanged @selector(messagesFlagsHaveChanged:)

@implementation IMAPStore

//
// This method implements a part of the Service Protocol.
//
- (id) initWithName: (NSString *) theName
               port: (int) thePort
{
  NSString *aString;
  
  self = [super init];

  _status.connected = NO;

  [self setName: theName];
  [self setPort: thePort];

  [self _preInit];
  
  tcpConnection = [[TCPConnection alloc] initWithName: theName
					 port: thePort];

  if ( !tcpConnection )
    {
      AUTORELEASE(self);
      return nil;
    }
  
  aString = [[self tcpConnection] readStringToEndOfLineSkippingCR: YES];
  
  if ( [aString hasCaseInsensitivePrefix: @"* OK"] )
    {
      NSDebugLog(@"IMAPStore: Connected!");
    }
  else
    {
      AUTORELEASE(self);
      NSDebugLog(@"IMAPStore: Not connected!");
      return nil;
    }
  
  _status.connected = YES;
  
  return self;
}


//
// This method provides a default init method using
// the default IMAP port - 143.
// 
- (id) initWithName: (NSString *) theName
{
  return [self initWithName: theName
	       port: 143];
}


//
//
//
- (id) initSSLWithName: (NSString *) theName
                  port: (int) thePort
{
  NSString *aString, *aPath;
  NSMutableArray *allPaths;
  NSBundle *aBundle;
  int i;

  self = [super init];

  _status.connected = NO;

  [self setName: theName];
  [self setPort: thePort];

  [self _preInit];
  

  // We load our TCPSSLConnection bundle.
  allPaths = [NSMutableArray array];
  [allPaths addObjectsFromArray: NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
								     NSLocalDomainMask|NSNetworkDomainMask|NSSystemDomainMask|NSUserDomainMask,
								     YES)];
#ifdef MACOSX
  [allPaths insertObject: [[NSBundle mainBundle] builtInPlugInsPath]  atIndex: 0];
#endif

  aBundle = nil;
  
  for (i = 0; i < [allPaths count]; i++)
    {
      aPath = [NSString stringWithFormat: @"%@/Pantomime/TCPSSLConnection.bundle",
			[allPaths objectAtIndex: i]];

      aBundle = [NSBundle bundleWithPath: aPath];

      if ( aBundle ) break;
    }
  
  if ( !aBundle )
    {
      NSDebugLog(@"IMAPStore: Failed to load the TCPSSLConnection bundle");
      AUTORELEASE(self);
      return nil;
    }
  
  tcpConnection = [[[aBundle principalClass] alloc] initWithName: theName
						    port: thePort];

  if ( !tcpConnection )
    {
      AUTORELEASE(self);
      return nil;
    }
  
  aString = [[self tcpConnection] readStringToEndOfLineSkippingCR: YES];
  
  if ( [aString hasCaseInsensitivePrefix: @"* OK"] )
    {
      NSDebugLog(@"IMAPStore: Connected!");
    }
  else
    {
      AUTORELEASE(self);
      NSDebugLog(@"IMAPStore: Not connected!");
      return nil;
    }

  _status.connected = YES;

  return self;
}


//
//
//
- (id) initWithURL: (NSString *) theURL
{
  URLName *theURLName;

  theURLName = [[URLName alloc] initWithString: theURL];
  
  self = [self initWithName: [theURLName host]
	       port: 143];
  
  RELEASE(theURLName);
  
  return self;
}


//
//
//
- (void) dealloc
{
  RELEASE(name);
  
  RELEASE(folders);
  RELEASE(folderStatus);
  RELEASE(openedFolders);
  RELEASE(subscribedFolders);
  RELEASE(capabilities);

  // Our Store's status
  RELEASE(_status.searchResponse);
  RELEASE(_status.lastCommand);
  RELEASE(_status.lastResponse);

  TEST_RELEASE(username);

  TEST_RELEASE(folderSeparator);
  TEST_RELEASE((id<NSObject>)tcpConnection);

  RELEASE(delegate);

  [super dealloc];
}


//
// This method authenticates the Store to the IMAP server.
// In case of an error, it returns NO.
//
// FIXME: We MUST NOT send a login command if LOGINDISABLED is
//        enforced by the server (6.2.3).
//
- (BOOL) authenticate: (NSString*) theUsername
	     password: (NSString*) thePassword
	    mechanism: (NSString *) theMechanism
{
  NSString *aPassword;

  // We first retain the username for future use
  username = theUsername;
  RETAIN(username);

  if ( theMechanism && [theMechanism caseInsensitiveCompare: @"CRAM-MD5"] == NSOrderedSame )
    {
      return [self _cramMD5Authentication: theUsername  password: thePassword];
    }
  else if ( theMechanism && [theMechanism caseInsensitiveCompare: @"LOGIN"] == NSOrderedSame )
    {
      return [self _loginAuthentication: theUsername  password: thePassword];
    }

  // We must verify if we must quote the password
  if ( [thePassword rangeOfCharacterFromSet: [NSCharacterSet punctuationCharacterSet]].length ||
       [thePassword rangeOfCharacterFromSet: [NSCharacterSet whitespaceCharacterSet]].length )
    {
      aPassword = [NSString stringWithFormat: @"\"%@\"", thePassword];
    }
  else
    {
      aPassword = thePassword;
    }
  
  [self _sendCommand: [NSString stringWithFormat: @"LOGIN %@ %@", theUsername, aPassword]];
  
  return _status.lastCommandWasSuccessful;
}


//
//
//
- (NSArray *) supportedMechanisms
{
  NSMutableArray *aMutableArray;
  NSArray *theCapabilities;
  NSString *aString;
  int i;

  theCapabilities = [self capabilities];
  aMutableArray = [NSMutableArray array];

  for (i = 0; i < [theCapabilities count]; i++)
    {
      aString = [theCapabilities objectAtIndex: i];

      if ( [aString hasCaseInsensitivePrefix: @"AUTH="] )
	{
	  [aMutableArray addObject: [aString substringFromIndex: 5]];
	}
    }

  return aMutableArray;
}


//
//
//
- (NSString *) name
{
  return name;
}


//
//
//
- (void) setName: (NSString *) theName
{
  if ( theName )
    {
      RETAIN(theName);
      RELEASE(name);
      name = theName;
    }
  else
    {
      DESTROY(name);
    }
}


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


//
//
//
- (void) setPort: (int) thePort
{
  port = thePort;
}


//
//
//
- (id<Connection>) tcpConnection
{
  return tcpConnection;
}


//
//
//
- (NSString *) username
{
  return username;
}


//
// The default folder in IMAP is always Inbox. This method will prefetch
// the messages of an IMAP folder if they haven't been prefetched before.
//
- (id) defaultFolder
{
  return [self folderForName: @"INBOX"];
}


//
//
//
- (id) folderForName: (NSString *) theName
{
  return [self folderForName: [theName modifiedUTF7String]
	       mode: PantomimeReadWriteMode
	       prefetch: YES];
}


//
// This method is very useful to return a folder used when
// appending messages to an IMAP folder. It DOES NOT add the
// folder to the list of opened folders.
//
- (IMAPFolder *) folderForName: (NSString *) theName
                        select: (BOOL) aBOOL
{
  // We first verify if this folder is open. If so, we simply return nil.
  if ( [self folderForNameIsOpen: theName] )
    {
      return nil;
    }

  if ( aBOOL )
    {
      return [self folderForName: theName];
    }
  else
    {
      IMAPFolder *aFolder;
      
      aFolder = [[IMAPFolder alloc] initWithName: theName]; // FIXME - use modified UTF7?
      
      [aFolder setStore: (Store *)self];
      [aFolder setSelected: NO];
      
      return AUTORELEASE(aFolder);
    }
}


//
//
//
- (IMAPFolder *) folderForName: (NSString *) theName
			  mode: (int) theMode
		      prefetch: (BOOL) aBOOL
{  
  IMAPFolder *aFolder;

  if ( [self folderForNameIsOpen: theName] )
    {
      return nil;
    }

  aFolder = [[IMAPFolder alloc] initWithName: theName  mode: theMode];
  [aFolder setStore: (Store *)self];
  
  // We now cache it
  [openedFolders setObject: aFolder  forKey: theName];
  RELEASE(aFolder);

  if ( theMode == PantomimeReadOnlyMode )
    {
      [self _sendCommand: [NSString stringWithFormat: @"EXAMINE \"%@\"", [theName modifiedUTF7String]]];
    }
  else
    {
      [self _sendCommand: [NSString stringWithFormat: @"SELECT \"%@\"", [theName modifiedUTF7String]]];
    }

  //
  // FIXME: We must support those tagged OK reponses:
  //        1a2b OK [READ-WRITE] ...
  //        3c4d OK [READ-ONLY] ...
  //
  //        To do so, we should move the initialization of aFolder in the following if ()
  //        and use the mode read in -parseServerOutput.
  //

  if ( _status.lastCommandWasSuccessful )
    {
      if ( aBOOL )
	{
	  [aFolder prefetch];
	}
    }
  else
    {
      // Our select command failed. Let's remove the folder from the list of
      // opened folders.
      [openedFolders removeObjectForKey: theName];
      aFolder = nil;
    }

  return aFolder;
}


//
//
//
- (id) folderForURL: (NSString *) theURL
{
  URLName *theURLName;
  id aFolder;

  theURLName = [[URLName alloc] initWithString: theURL];

  aFolder = [self folderForName: [theURLName foldername]];

  RELEASE(theURLName);
  
  return aFolder;
}


//
//
//
- (NSEnumerator *) folderEnumerator
{
  [folders removeAllObjects];

  [self _sendCommand: @"LIST \"\" \"*\""];

  return [folders keyEnumerator];
}


//
//
//
- (NSEnumerator *) subscribedFolderEnumerator
{
  [subscribedFolders removeAllObjects];

  [self _sendCommand: @"LSUB \"\" \"*\""];

  return [subscribedFolders objectEnumerator];
}


//
//
//
- (NSDictionary *) folderStatus: (NSArray *) theArray
{
  int i;

  [folderStatus removeAllObjects];

  // C: A042 STATUS blurdybloop (UIDNEXT MESSAGES)
  // S: * STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)
  // S: A042 OK STATUS completed
  //
  // We send: MESSAGES UNSEEN
  for (i = 0; i < [theArray count]; i++)
    {
      // RFC3501 (IMAP RFC that obsoletes RFC2060) says we SHOULD NOT call STATUS on the
      // selected mailbox - so we won't do it.
      if ( [openedFolders objectForKey: [[theArray objectAtIndex: i] modifiedUTF7String]] )
	{
	  continue;
	}

      [self _sendCommand: [NSString stringWithFormat: @"STATUS \"%@\" (MESSAGES UNSEEN)", [[theArray objectAtIndex: i] modifiedUTF7String]]];
    }

  return folderStatus;
}


//
//
//
- (NSArray *) capabilities
{
  if ( [capabilities count] == 0 )
    {
      [self _sendCommand: @"CAPABILITY"];
    }

  return capabilities;
}


//
// All folder names returned by this method are decoded and 
//
- (NSEnumerator *) openedFoldersEnumerator
{
  return [openedFolders objectEnumerator];
}


//
//
//
- (void) removeFolderFromOpenedFolders: (Folder *) theFolder
{
  [openedFolders removeObjectForKey: [theFolder name]];
}


//
// 
//
- (BOOL) folderForNameIsOpen: (NSString *) theName
{
  NSEnumerator *anEnumerator;
  IMAPFolder *aFolder;
  
  anEnumerator = [self openedFoldersEnumerator];
 
  while ( (aFolder = [anEnumerator nextObject]) )
    {
      if ([[aFolder name] compare: theName
			  options: NSCaseInsensitiveSearch] == NSOrderedSame)
 	{
 	  return YES;
 	}
    }
  
  return NO;
}


//
//
// 
- (int) folderTypeForFolderName: (NSString *) theName
{
  [self _sendCommand: [NSString stringWithFormat: @"LIST \"\" \"%@\"", [theName modifiedUTF7String]]];

  return [[folders objectForKey: theName] intValue];
}


//
//
//
- (NSString *) folderSeparator
{
  return folderSeparator;
}


//
//
//
- (NSString *) nextTag
{
  tag = tag + 1;  
  
  return [self lastTag];
}


//
//
//
- (NSString *) lastTag
{
  char str[5];
  
  sprintf(str, "%04x", tag);

  return [NSString stringWithCString: str];
}


//
// Returns YES on success, NO otherwise.
//
- (BOOL) subscribeToFolderWithName: (NSString *) theName
{
  [self _sendCommand: [NSString stringWithFormat: @"SUBSCRIBE \"%@\"", [theName modifiedUTF7String]]];
  
  return _status.lastCommandWasSuccessful;
}


//
// Returns YES on success, NO otherwise.
//
- (BOOL) unsubscribeToFolderWithName: (NSString *) theName
{
  [self _sendCommand: [NSString stringWithFormat: @"UNSUBSCRIBE \"%@\"", [theName modifiedUTF7String]]];
  
  return _status.lastCommandWasSuccessful;
}


//
//
//
- (void) close
{
  if ( _status.connected )
    {
      // We do nothing for now when the LOGOUT failed.
      [self _sendCommand: @"LOGOUT"];
      [[self tcpConnection] close];

      _status.connected = NO;
    }
}


//
// Create the mailbox and subscribe to it. The full path to the mailbox must
// be provided.
//
- (BOOL) createFolderWithName: (NSString *) theName 
			 type: (int) theType
		     contents: (NSData *) theContents
{
  [self _sendCommand: [NSString stringWithFormat: @"CREATE \"%@\"", [theName modifiedUTF7String]]];

  if ( _status.lastCommandWasSuccessful && _status.connected )
    {
      return [self subscribeToFolderWithName: theName];
    }

  return NO;
}


//
// Unsubscribe and delete the mailbox. The full path to the mailbox must
// be provided.
//
- (BOOL) deleteFolderWithName: (NSString *) theName
{
  if ( ![self unsubscribeToFolderWithName: theName] || 
       !_status.connected )
    {
      return NO;
    }

  [self _sendCommand: [NSString stringWithFormat: @"DELETE \"%@\"", [theName modifiedUTF7String]]];

  return _status.lastCommandWasSuccessful;
}


//
// This method is used to rename a folder.
//
// 3 steps: a) Unsubscribe to mailbox theName
//          b) Rename
//          c) Subscribe to mailbox theNewName
//
- (BOOL) renameFolderWithName: (NSString *) theName
                       toName: (NSString *) theNewName
{
  [self unsubscribeToFolderWithName: theName];

  if ( !_status.connected )
    {
      return NO;
    }

  [self _sendCommand: [NSString stringWithFormat: @"RENAME \"%@\" \"%@\"",
				[theName modifiedUTF7String],
				[theNewName modifiedUTF7String]]];
  
  if ( _status.lastCommandWasSuccessful )
    {
      return [self subscribeToFolderWithName: theNewName];
    }
  
  // Since the rename failed, we resubscribe to the original folder
  [self subscribeToFolderWithName: theName];
  
  return NO;
}


//
// This method NOOPs the IMAP store. It can generate untagged responses
// like * 5 RECENT that will eventually be processed.
//
- (void) noop
{
  [self _sendCommand: @"NOOP"];
}


//
//
//
- (id) delegate
{
  return delegate;
}


//
//
//
- (void) setDelegate: (id) theDelegate
{
  if ( theDelegate )
    {
      RETAIN(theDelegate);
      RELEASE(delegate);
      delegate = theDelegate;
    }
  else
    {
      DESTROY(delegate);
    }
}


//
//
//
- (BOOL) isConnected
{
  return _status.connected;
}

@end


//
// Private methods
//
@implementation IMAPStore (Private)

- (BOOL) _cramMD5Authentication: (NSString *) theUsername
                       password: (NSString *) thePassword
{
  NSString *aString;
  MD5 *aMD5;

  [self _sendCommand: @"AUTHENTICATE CRAM-MD5"];

  // We check if the mechanism is supported...
  if ( !_status.lastCommandWasSuccessful )
    {
      return NO;
    }

  
  // We trim the "+ " and we keep the challenge phrase
  aString = [_status.lastResponse substringFromIndex: 2];
  
  //NSLog(@"Challenge phrase = |%@|", aString);

  // We trim our \r\n
  //aString = [aString substringToIndex: ([aString length] - 2)];
  
  aString = [[NSString alloc] initWithData: [MimeUtility decodeBase64: [aString dataUsingEncoding: NSASCIIStringEncoding]]
			      encoding: NSASCIIStringEncoding];;
      
  
  aMD5 = [[MD5 alloc] initWithString: aString
		      encoding: NSASCIIStringEncoding];
  [aMD5 computeDigest];
  RELEASE(aString);
      
  aString = [NSString stringWithFormat: @"%@ %@", theUsername, [aMD5 hmacAsStringUsingPassword: thePassword]];
  aString = [[NSString alloc] initWithData: [MimeUtility encodeBase64: [aString dataUsingEncoding: NSASCIIStringEncoding]
							 lineLength: 0]
			      encoding: NSASCIIStringEncoding];
  RELEASE(aMD5);
      
  [[self tcpConnection] writeLine: aString];
  RELEASE(aString);
      
  [self _parseServerOutput];
      
  return _status.lastCommandWasSuccessful;
}

//
//
//
- (NSString *) _folderNameFromString: (NSString *) theString
{
  NSString *aString, *decodedString;
  NSRange aRange;
  int len;

  aRange = [theString rangeOfString: @"\""];

  if ( aRange.length )
    {
      int mark;

      mark = aRange.location + 1;
      
      aRange = [theString rangeOfString: @"\""
			  options: 0
			  range: NSMakeRange(mark, [theString length] - mark)];
      
      TEST_RELEASE(folderSeparator);
      folderSeparator = [theString substringWithRange: NSMakeRange(mark, aRange.location - mark)];
      RETAIN(folderSeparator);
      
      mark = aRange.location + 2;
      
      aString = [theString substringFromIndex: mark];
    }
  else
    {
      aRange = [theString rangeOfString: @"NIL"
			  options: NSCaseInsensitiveSearch];
      
      aString = [theString substringFromIndex: aRange.location + aRange.length + 1];
    }
  
  //
  // We verify if we got the number of bytes to read instead of the real mailbox name.
  // Some servers seem to send that when the mailbox name is 8-bit. Those 8-bit mailbox
  // names were undefined in earlier verisions of the IMAP protocol (now deprecated).
  // See section 5.1. (Mailbox Naming) of RFC3051.
  //
  // The RFC says we SHOULD interpret that as UTF-8.
  //
  len = [aString length];
  
  if (len  > 1 && [aString characterAtIndex: 0] == '{' && [aString characterAtIndex:(len-1)] == '}')
    {
      NSData *aData;

      aData = [[self tcpConnection] readDataOfLength:
				      [[aString substringWithRange:
						  NSMakeRange(1, [aString length] - 2)] intValue]];

      aString = AUTORELEASE([[NSString alloc] initWithData: aData  encoding: NSUTF8StringEncoding]);      
      
      // We read our CR.
      [[self tcpConnection] readStringToEndOfLineSkippingCR: YES];
    }
  
  aString = [aString stringFromQuotedString];
  decodedString = [aString stringFromModifiedUTF7];
  
  return (decodedString != nil ? decodedString : aString);
}


//
// This method returns the last command sent to the IMAP server.
// It does NOT contain the IMAP tag.
//
- (NSString *) _lastCommand
{
  return _status.lastCommand;
}


//
// LOGIN authentication mechanism (undocumented but easy to figure out)
//
- (BOOL) _loginAuthentication: (NSString *) theUsername
		     password: (NSString *) thePassword
{
  NSString *un, *pw;

  [self _sendCommand: @"AUTHENTICATE LOGIN"];
    
  // We check if the mechanism is supported...
  if ( !_status.lastCommandWasSuccessful )
    {
      return NO;
    }
  
  un = [[NSString alloc] initWithData: [MimeUtility encodeBase64: [theUsername dataUsingEncoding: NSASCIIStringEncoding]
						    lineLength: 0]
			 encoding: NSASCIIStringEncoding];
      
  [[self tcpConnection] writeLine: un];
  RELEASE(un);
      
  [self _parseServerOutput];
  
  pw = [[NSString alloc] initWithData: [MimeUtility encodeBase64: [thePassword dataUsingEncoding: NSASCIIStringEncoding]
						    lineLength: 0]
			 encoding: NSASCIIStringEncoding];
	  
  [[self tcpConnection] writeLine: pw];
  RELEASE(pw);
	  
  [self _parseServerOutput];
	    
  return _status.lastCommandWasSuccessful;
}

//
//
//
- (void) _messagesWereReceived
{
  IMAPFolder *aFolder;
  int theUID;
  
  //
  // We first set our ivar to NO. This prevents doing recursive calls
  // of this method (since it'll get called again and again in _sendCommand:)
  //
  _status.messagesWereReceived = NO;

  aFolder = [[openedFolders allValues] lastObject];
  theUID = 0;
  
  // We prefetch the new messages from the last UID+1
  // found in the cache.
  if ( [aFolder cacheManager] )
    {
      theUID = [[[[aFolder cacheManager] cache] lastObject] UID];
    }
  
  [self _sendCommand: [NSString stringWithFormat: @"UID FETCH %d:* (FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (From To Cc Subject Date Message-ID References In-Reply-To MIME-Version)])", (theUID+1)]];
  
  // We inform our deletage
  if ( [aFolder delegate] && [[aFolder delegate] respondsToSelector: selMessagesWereReceived] )
    {
      [[aFolder delegate] performSelector: selMessagesWereReceived
      			  withObject: self];
    }
}


//
// This method parses an * CAPABILITY IMAP4 IMAP4rev1 ACL AUTH=LOGIN NAMESPACE ..
// untagged response (6.1.1)
//
- (void) _parseCapability: (NSString *) theString
{
  [capabilities addObjectsFromArray: [[theString substringFromIndex: 13] componentsSeparatedByString: @" "]];
}


//
// This method parses an * 23 EXISTS untagged response. (7.3.1)
//
// If we were NOT issueing a SELECT command, it fetches the
// new messages (if any) and informs the folder's delegate that
// new messages have arrived.
//
- (void) _parseExists: (NSString *) theString
{
  IMAPFolder *aFolder;
  int num;
  
  sscanf([theString cString], "* %d EXISTS", &num);
  
  aFolder = [[openedFolders allValues] lastObject];
  
  if ( aFolder && num > [aFolder->allMessages count] &&
       ![[self _lastCommand] hasPrefix: @"SELECT"] )
    {
      _status.messagesWereReceived = YES;
    }
}


//
// Example: * 44 EXPUNGE
//
- (void) _parseExpunge: (NSString *) theString
{
  IMAPMessage *aMessage;
  IMAPFolder *aFolder;
  int i, theMSN;

  _status.messagesWereExpunged = NO;

  sscanf([theString cString], "* %d EXPUNGE", &theMSN);

  aFolder = [[openedFolders allValues] lastObject];
  aMessage = [aFolder->allMessages objectAtIndex: (theMSN-1)];
  [aFolder removeMessage: aMessage];
  
  // We remove its entry in our cache
  if ( [aFolder cacheManager] )
    {
      [(IMAPCacheManager *)[aFolder cacheManager] removeMessage: aMessage];
    }

  // We update all MSNs starting from the message that has been expunged.
  for (i = (theMSN-1); i < [aFolder->allMessages count]; i++)
    {
      aMessage = [aFolder->allMessages objectAtIndex: i];
      [aMessage setMessageNumber: (i+1)];
    }

  //
  // If our previous command is NOT the EXPUNGE command, we must inform our
  // delegate that messages have been expunged. The delegate SHOULD refresh
  // its view and does NOT have to issue any command to update the state
  // of the messages (since it has been done).
  //
  if ( ![[self _lastCommand] isEqualToString: @"EXPUNGE"] )
    {
      _status.messagesWereExpunged = YES;
    }

  //NSLog(@"Expunged %d", theMSN);
}


//
// This method parses the flags received in aString and builds
// a corresponding Flags object for them.
//
- (void) _parseFlags: (NSString *) aString
	     message: (IMAPMessage *) theMessage
{
  NSRange aRange;
  Flags *theFlags;
  
  _status.messagesFlagsHaveChanged = NO;

  theFlags = [[Flags alloc] init];

  // We check if the message has the Seen flag
  aRange = [aString rangeOfString: @"\\Seen" 
		    options: NSCaseInsensitiveSearch];
      
  if ( aRange.length > 0 )
    {
      [theFlags add: SEEN];
    }

  // We check if the message has the Recent flag
  aRange = [aString rangeOfString: @"\\Recent" 
		    options: NSCaseInsensitiveSearch];
  
  if ( aRange.length > 0 )
    {
      [theFlags add: RECENT];
    }
  
  // We check if the message has the Deleted flag
  aRange = [aString rangeOfString: @"\\Deleted"
		    options: NSCaseInsensitiveSearch];
      
  if ( aRange.length > 0 )
    {
      [theFlags add: DELETED];
    }
  
  // We check if the message has the Answered flag
  aRange = [aString rangeOfString: @"\\Answered"
		    options: NSCaseInsensitiveSearch];
      
  if ( aRange.length > 0 )
    {
      [theFlags add: ANSWERED];
    }

  // We check if the message has the Flagged flag
  aRange = [aString rangeOfString: @"\\Flagged"
		    options: NSCaseInsensitiveSearch];
  
  if ( aRange.length > 0 )
    {
      [theFlags add: FLAGGED];
    }

  // We check if the message has the Draft flag
  aRange = [aString rangeOfString: @"\\Draft"
		    options: NSCaseInsensitiveSearch];
  
  if ( aRange.length > 0 )
    {
      [theFlags add: DRAFT];
    }

  [[theMessage flags] replaceWithFlags: theFlags];

  RELEASE(theFlags);
  
  //
  // If our previous command is NOT the FETCH command, we must inform our
  // delegate that messages flags have changed. The delegate SHOULD refresh
  // its view and does NOT have to issue any command to update the state
  // of the messages (since it has been done).
  //
  if ( ![[self _lastCommand] hasCaseInsensitivePrefix: @"UID FETCH"] )
    {
      _status.messagesFlagsHaveChanged = YES;
    }
}


//
//
//
// Examples of FETCH responses:
//
// * 50 FETCH (UID 50 RFC822 {6718}
// Return-Path: <...
// )
//
// * 418 FETCH (FLAGS (\Seen) UID 418 RFC822.SIZE 3565452 BODY[HEADER.FIELDS (From To Cc Subject Date Message-ID 
// References In-Reply-To MIME-Version)] {666}
// Subject: abc
// ...
// )
//
// * 50 FETCH (UID 50 BODY[HEADER.FIELDS.NOT (From To Cc Subject Date Message-ID References In-Reply-To MIME-Version)] {1412}
// Return-Path: <...
// )
//
// * 50 FETCH (BODY[TEXT] {5009}
// Hi, ...
// )
//
// "Twisted" response from Microsoft Exchange 2000:
//
// * 549 FETCH (FLAGS (\Recent) RFC822.SIZE 970 BODY[HEADER.FIELDS (From To Cc Subject Date Message-ID References In-Reply-To MIME-Version)] {196}
// From: <aaaaaa@bbbbbbbbbbbbbbb.com>
// To: aaaaaa@bbbbbbbbbbbbbbb.com
// Subject: Test mail
// Date: Tue, 16 Dec 2003 15:52:23 GMT
// Message-Id: <200312161552.PAA07523@aaaaaaa.bbb.ccccccccccccccc.com>
// 
//  UID 29905)
//
//
// Rationale:
//
// This method parses everything from ( to ). It's done when it sees the last ).
// If it hasn't seen ) in theString, it'll continue reading lines until it sees it.
//
- (void) _parseFetch: (NSString *) theString
		 msn: (int) theMSN
{
  NSCharacterSet *aCharacterSet;
  NSScanner *aScanner;
  IMAPFolder *aFolder;
  id aMessage;

  int i, j, len, size;
  BOOL done;

  // We first get our folder and our message
  aFolder = [[openedFolders allValues] lastObject];

  // The folder might have been closed so we must not try to
  // update it for no good reason.
  if ( !aFolder ) return;

  //
  // If the MSN is > then the folder's count, that means it's
  // a new message.
  //
  // We can safely assume this since what we have in aFolder->allMessages
  // is really the messages in our IMAP folder. That is true since we
  // synchronized our cache when opening the folder, in IMAPFolder: -prefetch.
  //
  if ( theMSN > [aFolder->allMessages count] )
    {
      //NSLog(@"============ NEW MESSAGE ======================");
      aMessage = [[IMAPMessage alloc] init];
      
      // We set some initial properties to our message;
      [aMessage setInitialized: NO];
      [aMessage setFolder: aFolder];
      [aMessage setMessageNumber: theMSN];
      [aFolder appendMessage: aMessage];

      // We add the new message to our cache.
      if ( [aFolder cacheManager] )
	{
	  [[aFolder cacheManager] addMessage: aMessage];
	}

      RELEASE(aMessage);
    }
  else
    {
      aMessage = [aFolder->allMessages objectAtIndex: (theMSN-1)];
      [aMessage setMessageNumber: theMSN];
      [aMessage setFolder: aFolder];
    }

  //
  //
  //
  aCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  i = j = [theString rangeOfString: @"("].location + 1;
  len = [theString length];
  
  aScanner = [[NSScanner alloc] initWithString: theString];
  [aScanner setScanLocation: i];
  
  done = ![aScanner scanUpToCharactersFromSet: aCharacterSet  intoString: NULL];

  // We tokenize our string into words
  while ( !done )
    {
      NSString *aWord;

      j = [aScanner scanLocation];
      aWord = [[theString substringWithRange: NSMakeRange(i,j-i)] stringByTrimmingWhiteSpaces];
      //NSLog(@"WORD |%@|", aWord);

      //
      // We read our UID
      //
      if ( [aWord caseInsensitiveCompare: @"UID"] == NSOrderedSame )
	{
	  int theUID;

	  [aScanner scanInt: &theUID];
	  //NSLog(@"theUID %d j = %d, scanLoc = %d", theUID, j, [aScanner scanLocation]);
	  
	  if ( [aMessage UID] == 0 )
	    {
	      [aMessage setUID: theUID];
	    }

	  j = [aScanner scanLocation];
	}
      //
      // We read our flags. We usually get something like FLAGS (\Seen)
      //
      else if ( [aWord caseInsensitiveCompare: @"FLAGS"] == NSOrderedSame )
	{
	  NSRange aRange;

	  // We get the substring inside our ( )
	  aRange = [theString rangeOfString: @")"  options: 0  range: NSMakeRange(j,len-j)]; 
	  //NSLog(@"Flags = |%@|", [theString substringWithRange: NSMakeRange(j+2, aRange.location-j-2)]);
	  [self _parseFlags: [theString substringWithRange: NSMakeRange(j+2, aRange.location-j-2)]
		message: aMessage];

	  j = aRange.location + 1;
	  [aScanner setScanLocation: j];
	}
      //
      //
      //
      else if ( [aWord caseInsensitiveCompare: @"RFC822"] == NSOrderedSame )
	{
	  NSMutableData *aMutableData;

	  i = j;

	  // Get the next word (ie., {123456}) and decode the size.
	  [aScanner scanUpToCharactersFromSet: aCharacterSet  intoString: NULL];
	  j = [aScanner scanLocation];
	  size = [self _segmentSizeFromWord: [theString substringWithRange: NSMakeRange(i,j-i)]];
	  //NSLog(@"RFC822 size = %d", size);

	  // We set the raw source of the message
	  aMutableData = [[NSMutableData alloc] initWithData: [[self tcpConnection] readDataOfLength: size]];
	  [aMutableData replaceCRLFWithLF];
	  [aMessage setRawSource: aMutableData];
	  RELEASE(aMutableData);

	  //j = [aScanner scanLocation];
	  //break;
	}
      //
      //
      //
      else if ( [aWord caseInsensitiveCompare: @"RFC822.SIZE"] == NSOrderedSame )
	{
	  [aScanner scanInt: &size];
  	  [(IMAPMessage *)aMessage setSize: size];
	  j = [aScanner scanLocation];
	}
      //
      //
      //
      else if ( [aWord caseInsensitiveCompare: @"BODY[TEXT]"] == NSOrderedSame )
	{
	  NSMutableData *aMutableData;
	  
	  i = j;
	  
	  // Get the next word (ie., {123456}) and decode the size.
	  [aScanner scanUpToCharactersFromSet: aCharacterSet  intoString: NULL];
	  j = [aScanner scanLocation];
	  size = [self _segmentSizeFromWord: [theString substringWithRange: NSMakeRange(i,j-i)]];
	  //NSLog(@"BODY[TEXT] size = %d", size);

	  // We set the raw source of the message
	  if ( size > 0 )
	    {
	      aMutableData = [[NSMutableData alloc] initWithData: [[self tcpConnection] readDataOfLength: size]];
	      [aMutableData replaceCRLFWithLF];
	    }
	  else
	    {
	      aMutableData = [[NSMutableData alloc] init];
	    }
	  
	  [aMessage setContentFromRawSource: aMutableData];
	  RELEASE(aMutableData);

	  //j = [aScanner scanLocation];
	  //break;
	}
      //
      //
      //
      else if ( [aWord caseInsensitiveCompare: @"BODY[HEADER.FIELDS.NOT"] == NSOrderedSame ||
		[aWord caseInsensitiveCompare: @"BODY[HEADER.FIELDS"] == NSOrderedSame )
	{
	  NSMutableData *aMutableData;
	  NSRange aRange;

	  // Let's search for the "]" and set the scanner's location after that character.
	  aRange = [theString rangeOfString: @"]"  options: NSBackwardsSearch];
	  
	  i = aRange.location + 1;
	  [aScanner setScanLocation: i];
	  
	  // Get the next word (ie., {123456}) and decode the size.
	  [aScanner scanUpToCharactersFromSet: aCharacterSet  intoString: NULL];
	  j = [aScanner scanLocation];
	  size = [self _segmentSizeFromWord: [theString substringWithRange: NSMakeRange(i,j-i)]];

	  // We set the raw source of the message, based on our response.
	  aMutableData = [[NSMutableData alloc] initWithData: [[self tcpConnection] readDataOfLength: size]];
	  [aMutableData replaceCRLFWithLF];

	  // Lame hack (_status.lastCommand rangeOfString...) since Novell's IMAP server doesn't
	  // distinguish a NOT BODY.PEEK from a standard one (ie., no NOT).
	  if ( [aWord caseInsensitiveCompare: @"BODY[HEADER.FIELDS.NOT"] == NSOrderedSame ||
	       [_status.lastCommand rangeOfString: @"BODY.PEEK[HEADER.FIELDS.NOT"].location != NSNotFound )
	    {
	      [aMessage addHeadersFromData: aMutableData];
	    }
	  else
	    {
	      [aMessage setHeadersFromData: aMutableData];
	    }

	  RELEASE(aMutableData);
	  //break;
	}      

      i = j;

      done = ![aScanner scanUpToCharactersFromSet: aCharacterSet  intoString: NULL];

      // We are done parsing the line but not the entire fetch response since
      // we haven't seen the ")" character.
      if ( done && [theString characterAtIndex: (len-1)] != ')' )
	{
	  RELEASE(aScanner);
	  
	  theString = [[self tcpConnection] readStringToEndOfLineSkippingCR: YES];

	  //NSLog(@"READ SUPPLEMENTAL STRING = |%@|", theString);
	  len = [theString length];
	  i = j = 0;

	  aScanner = [[NSScanner alloc] initWithString: theString];
	  [aScanner setScanLocation: i];

	  done = ![aScanner scanUpToCharactersFromSet: aCharacterSet  intoString: NULL];
	}
    }

  RELEASE(aScanner);
}


//
// This command parses the result of a LIST command. See 7.2.2 for the complete
// description of the LIST response.
//
// Rationale:
//
// In IMAP, all mailboxes can hold messages and folders. Thus, the HOLDS_MESSAGES
// flag is ALWAYS set for a mailbox that has been parsed.
//
// We also support RFC3348 \HasChildren and \HasNoChildren flags. In fact, we
// directly map \HasChildren to HOLDS_FOLDERS.
//
// We support the following standard flags (from RFC3501):
//
//      \Noinferiors
//         It is not possible for any child levels of hierarchy to exist
//         under this name; no child levels exist now and none can be
//         created in the future.
//
//      \Noselect
//         It is not possible to use this name as a selectable mailbox.
//
//      \Marked
//         The mailbox has been marked "interesting" by the server; the
//         mailbox probably contains messages that have been added since
//         the last time the mailbox was selected.
//
//      \Unmarked
//         The mailbox does not contain any additional messages since the
//         last time the mailbox was selected.
//
- (void) _parseList: (NSString *) theString
{
  NSString *aFolderName, *aString;
  NSRange r1, r2;
  int flags;

  // We first try to get our name attributes.
  r1 = [theString rangeOfString: @"("];

  if ( r1.location == NSNotFound )
    {
      return;
    }
 
  r2 = [theString rangeOfString: @")"  options: 0  range: NSMakeRange(r1.location+1, [theString length]-r1.location-1)];

  if ( r2.location == NSNotFound )
    {
      return;
    }
  
  // We get the folder name and the mailbox name attributes
  aFolderName = [self _folderNameFromString: theString];
  aString = [theString substringWithRange: NSMakeRange(r1.location+1, r2.location-r1.location-1)];

  // We get all the supported flags, starting with the flags of RFC3348
  flags = PantomimeHoldsMessages;

  if ( [aString length] )
    {
      if ( [aString rangeOfString: @"\\HasChildren" options: NSCaseInsensitiveSearch].length )
	{
	  flags = flags|PantomimeHoldsFolders;
	}
      
      if ( [aString rangeOfString: @"\\NoInferiors" options: NSCaseInsensitiveSearch].length )
	{
	  flags = flags|PantomimeNoInferiors;
	}

      if ( [aString rangeOfString: @"\\NoSelect" options: NSCaseInsensitiveSearch].length )
	{
	  flags = flags|PantomimeNoSelect;
	}

      if ( [aString rangeOfString: @"\\Marked" options: NSCaseInsensitiveSearch].length )
	{
	  flags = flags|PantomimeMarked;
	}
      
      if ( [aString rangeOfString: @"\\Unmarked" options: NSCaseInsensitiveSearch].length )
	{
	  flags = flags|PantomimeUnmarked;
	}
    }

  [folders setObject: [NSNumber numberWithInt: flags]
	   forKey: aFolderName];
}


//
// This method receives a * 5 RECENT parameter and parses it.
//
- (void) _parseRecent: (NSString *) theString
{
  // Do nothing for now. This breaks 7.3.2 since the response
  // is not recorded.
}


//
//
//
- (void) _parseSearch: (NSString *) theString
{
  IMAPMessage *aMessage;
  IMAPFolder *aFolder;
  NSScanner *aScanner;
  NSString *aString;
  int value;

  // We decode our list of UIDs. We first skip the "* SEARCH" string.
  aString = [theString substringFromIndex: 8];

  // No results found
  if ( [aString length] == 0 )
    {
      return;
    }
  
  // We get the currently selected folder.
  aFolder = [[openedFolders allValues] lastObject];

  // We scan all our UIDs.
  aScanner = [[NSScanner alloc] initWithString: aString];

  while ( ![aScanner isAtEnd] )
    {
      [aScanner scanInt: &value];

      aMessage = [[aFolder cacheManager] messageWithUID: value];

      if ( aMessage )
	{
	  [_status.searchResponse addObject: aMessage];
	}
      else
	{
	  //NSLog(@"Message with UID = %d not found in cache.", value);
	}
    }

  RELEASE(aScanner);
}


//
//
//
- (void) _parseServerOutput
{
  NSString *aString;
  BOOL done;

  done = NO;

  while ( !done )
    {
      aString = [[self tcpConnection] readStringToEndOfLineSkippingCR: YES];
      ASSIGN(_status.lastResponse, aString);
      //NSLog(@"R: |%@|", aString);

      //
      // More untagged responses to read...
      //
      if ( [aString characterAtIndex: 0] == '*' )
	{
	  //
	  // The BYE response is always untagged.
	  //
	  if ( HAS_UNTAGGED_PREFIX(@"BYE") )
	    {
	      if ( ![[self _lastCommand] isEqualToString: @"LOGOUT"] )
		{
		  // We got disconnected from the server. We might have lost the
		  // lock to the mailbox, the server might be being restarted or whatever.
		  // We must NOT destroy the tcpConnection since the MUA might verify
		  // if it's still connected to the IMAP store by calling -isConnected.
		  CONNECTION_WAS_LOST();
		  _status.connected = NO;
		  break;
		}
	    
	    }
	  //
	  // We are reading a CAPABILITY response.
	  //
	  else if ( HAS_UNTAGGED_PREFIX(@"CAPABILITY") )
	    {
	      [self _parseCapability: aString];
	    }
	  //
	  // We are reading a EXISTS response.
	  //
	  else if ( HAS_UNTAGGED_SUFFIX(@"EXISTS") )
	    {
	      [self _parseExists: aString];
	    }
	  //
	  // We are reading a EXPUNGE response.
	  //
	  else if ( HAS_UNTAGGED_SUFFIX(@"EXPUNGE") )
	    {
	      [self _parseExpunge: aString];
	    }
	  //
	  //
	  // We are reading a list of mailboxes. We add every single mailbox
	  // we read to our folders array.
	  //
	  else if ( HAS_UNTAGGED_PREFIX(@"LIST") )
	    {
	      [self _parseList: aString];
	    }
	  //
	  // We are reading a list of subscribed mailboxes. We add
	  // what we read to our subscribedFolders array.
	  //
	  else if ( HAS_UNTAGGED_PREFIX(@"LSUB") )
	    {
	      [subscribedFolders addObject: [self _folderNameFromString: aString]];
	    }
	  //
	  // We are reading an OK [UIDVALIDITY <n>] response. The value we read will
	  // be applied to the only folder in openedFolders. 
	  //
	  else if ( HAS_UNTAGGED_PREFIX(@"OK [UIDVALIDITY") )
	    {
	      [self _parseUIDValidity: aString];
	    }
	  //
	  // We are reading a RECENT response.
	  //
	  else if ( HAS_UNTAGGED_SUFFIX(@"RECENT") )
	    {
	      [self _parseRecent: aString];
	    }
	  //
	  // We are reading a STATUS response.
	  //
	  else if ( HAS_UNTAGGED_PREFIX(@"STATUS") )
	    {
	      [self _parseStatus: aString];
	    }
	  //
	  // We are reading a SEARCH response.
	  //
	  else if ( HAS_UNTAGGED_PREFIX(@"SEARCH") )
	    {
	      [self _parseSearch: aString];
	    }
	  //
	  // We are reading an other kind of untagged response, most likely to be
	  // a FETCH response.
	  //
	  else
	    {
	      int i, j, len, msn;

	      len = [aString length];
	      j = msn = 0;
	      i = 2;

	      // Let's try to decode a MSN...
	      if ( isdigit([aString characterAtIndex: i]) )
		{
		  j = i;
		  while (isdigit([aString characterAtIndex: j])) j++;
		  
		  // We get the substring and we use that as our MSN
		  msn = [[aString substringWithRange: NSMakeRange(i,j-i)] intValue];
		  //NSLog(@"msn = %d, str = |%@|", msn,  [aString substringWithRange: NSMakeRange(i,j-i)]);
		  i = j;
		}
	      
	      // Let's continue with the actual IMAP command. First skip a space.
	      i++;
	      if ( isalpha([aString characterAtIndex: i]) )
		{
		  j = i;
		  while ([aString characterAtIndex: j] != ' ') j++;

		  // We got a FETCH response.
		  if ( [[aString substringWithRange: NSMakeRange(i,j-i)] caseInsensitiveCompare: @"FETCH"] == NSOrderedSame )
		    {
		      [self _parseFetch: aString  msn: msn];
		    }
		}
	      

	      //NSLog(@"UNKNOWN untagged response: %@", aString);
	    }
	}
      //
      // We got a command continuation request (7.5). That means the callee must
      // verify if the command was successful (it was) and continue to proceed.
      //
      else if ( [aString characterAtIndex: 0] == '+' )
	{
	  _status.lastCommandWasSuccessful = YES;
	  break;
	}
      //
      // No more untagged responses to read.
      //
      else
	{
	  done = YES;

	  if ( HAS_TAGGED_PREFIX(@"OK") )
	    {
	      // Success of the IMAP command
	      _status.lastCommandWasSuccessful = YES;
	    }
	  else
	    {
	      // Failure of the IMAP command
	      _status.lastCommandWasSuccessful = NO;
	    }

	  //NSLog(@"Last command status: %@", (_status.lastCommandWasSuccessful ? @"Success!" : @"Failed :-("));
	}
    }
}


//
// This method receives a * STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)
// parameter and parses it. It then put the decoded values in the
// folderStatus dictionary.
//
- (void) _parseStatus: (NSString *) theString
{
  FolderInformation *aFolderInformation;
  NSString *aFolderName;
  NSRange aRange;

  int messages, unseen;
    
  aRange = [theString rangeOfString: @"("  options: NSBackwardsSearch];
  aFolderName = [[theString substringToIndex: (aRange.location-1)] substringFromIndex: 9];
  
  sscanf([[theString substringFromIndex: aRange.location] cString], "(MESSAGES %d UNSEEN %d)", &messages, &unseen);
  
  aFolderInformation = [[FolderInformation alloc] init];
  [aFolderInformation setNbOfMessages: messages];
  [aFolderInformation setNbOfUnreadMessages: unseen];
  
  // Before putting the folder in our dictionary, we unquote it.
  [folderStatus setObject: aFolderInformation  forKey: [aFolderName stringFromQuotedString]];
  RELEASE(aFolderInformation);
}


//
// Example: * OK [UIDVALIDITY 948394385]
//
- (void) _parseUIDValidity: (NSString *) theString
{
  IMAPFolder *aFolder;
  NSRange aRange;
  
  // We get our "opened" folder.
  aFolder = [[openedFolders allValues] lastObject];

  // We trim the  * OK [UIDVALIDITY part
  theString = [theString substringFromIndex: 17];

  // We find the ]
  aRange = [theString rangeOfString: @"]"];

  if ( aRange.length )
    {
      NSString *aString;

      aString = [theString substringWithRange: NSMakeRange(0, aRange.location)];
      
      if ( [aString length] > 0 )
	{
	  [aFolder setUIDValidity: [aString intValue]];
	}
    }
}


//
//
//
- (void) _preInit
{
  [self setDelegate: nil];
  folderSeparator = nil;
  username = nil;
  tag = 1;
  
  // Our status
  _status.messagesWereReceived = NO;
  _status.messagesWereExpunged = NO;
  _status.messagesFlagsHaveChanged = NO;
  _status.searchResponse = [[NSMutableArray alloc] init];

  folders = [[NSMutableDictionary alloc] init];
  openedFolders = [[NSMutableDictionary alloc] init];
  subscribedFolders = [[NSMutableArray alloc] init];
  folderStatus = [[NSMutableDictionary alloc] init];
  capabilities = [[NSMutableArray alloc] init];
}


//
//
//
- (int) _segmentSizeFromWord: (NSString *) theWord
{
  theWord = [theWord stringByTrimmingWhiteSpaces];
  theWord = [theWord substringWithRange: NSMakeRange(1, [theWord length]-2)];

  return [theWord intValue];
}


//
// This private method appends theCommand to the IMAP tag
// and sends it to the server.
//
// It then sets the last command (w/o the tag) that has been sent.
//
- (void) _sendCommand: (NSString *) theCommand
{
  IMAPFolder *aFolder;

  //NSLog(@"S: |%@| %d", theCommand, [theCommand length]);

  if ( [theCommand length] )
    {
      [[self tcpConnection] writeLine: [NSString stringWithFormat: @"%@ %@", [self nextTag], theCommand]];
    }
  else
    {
      [[self tcpConnection] writeLine: @""];
    }

  ASSIGN(_status.lastCommand, theCommand);

  [self _parseServerOutput];

  //
  // This imply we have a folder opened...
  //
  aFolder = [[openedFolders allValues] lastObject];

  if ( aFolder )
    {
      if ( _status.messagesWereReceived )
	{
	  [self _messagesWereReceived];
	}
      
      if ( _status.messagesWereExpunged )
	{
	  if ( [aFolder delegate] && [[aFolder delegate] respondsToSelector: selMessagesWereExpunged] )
	    {
	      [[aFolder delegate] performSelector: selMessagesWereExpunged
				  withObject: self];
	    }
	}

      if ( _status.messagesFlagsHaveChanged )
	{
	  if ( [aFolder delegate] && [[aFolder delegate] respondsToSelector: selMessagesFlagsHaveChanged] )
	    {
	      [[aFolder delegate] performSelector: selMessagesFlagsHaveChanged
				  withObject: self];
	    }
	}
    }
}

@end
