/*
  Copyright (C) 2004-2005 SKYRIX Software AG

  This file is part of OpenGroupware.org.

  OGo 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, or (at your option) any
  later version.

  OGo 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 OGo; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

/*
  UIxMailListView
  
  This component represent a list of mails and is attached to an SOGoMailFolder
  object.
*/

#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSValue.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSString+misc.h>

#import <EOControl/EOQualifier.h>

#import <SoObjects/Mailer/SOGoMailFolder.h>
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/SOGoDateFormatter.h>
#import <SoObjects/SOGo/SOGoUser.h>

#import "UIxMailListView.h"

#define messagesPerPage 50

@implementation UIxMailListView

- (id) init
{
  SOGoUser *user;

  if ((self = [super init]))
    {
      qualifier = nil;
      user = [context activeUser];
      ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
      ASSIGN (userTimeZone, [user timeZone]);
    }

  return self;
}

- (void) dealloc 
{
  [qualifier release];
  [sortedUIDs release];
  [messages release];
  [message release];
  [dateFormatter release];
  [userTimeZone release];
  [super dealloc];
}

/* accessors */

- (void) setMessage: (id) _msg
{
  ASSIGN (message, _msg);
}

- (id) message 
{
  return message;
}

- (NSString *) messageDate
{
  NSCalendarDate *messageDate;

  messageDate = [[message valueForKey: @"envelope"] date];
  [messageDate setTimeZone: userTimeZone];

  return [dateFormatter formattedDateAndTime: messageDate];
}

- (NSString *) messageSubject
{
  NSString *subject;
  id envSubject;

  envSubject = [[message valueForKey: @"envelope"] subject];
  if ([envSubject isKindOfClass: [NSData class]])
    {
      subject = [[NSString alloc] initWithData: envSubject
				  encoding: NSUTF8StringEncoding];
      [subject autorelease];
    }
  else
    subject = envSubject;

  return subject;
}

- (BOOL) showToAddress 
{
  NSString *ftype;
  
  ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
  return [ftype isEqual:@"IPF.Sent"];
}

/* title */

- (NSString *) objectTitle 
{
  return [[self clientObject] nameInContainer];
}

- (NSString *) panelTitle 
{
  NSString *s;
  
  s = [self labelForKey:@"View Mail Folder"];
  s = [s stringByAppendingString:@": "];
  s = [s stringByAppendingString:[self objectTitle]];
  return s;
}

/* derived accessors */

- (BOOL) isMessageDeleted 
{
  NSArray *flags;
  
  flags = [[self message] valueForKey:@"flags"];
  return [flags containsObject:@"deleted"];
}

- (BOOL) isMessageRead 
{
  NSArray *flags;
  
  flags = [[self message] valueForKey:@"flags"];
  return [flags containsObject:@"seen"];
}

- (NSString *) messageUidString 
{
  return [[[self message] valueForKey:@"uid"] stringValue];
}

- (NSString *) messageRowStyleClass 
{
  return [self isMessageDeleted]
    ? @"mailer_listcell_deleted"
    : @"mailer_listcell_regular";
}

- (NSString *) messageSubjectCellStyleClass 
{
  NSArray *flags;
  NSString *cellClass;

  flags = [[self message] valueForKey:@"flags"];

  if ([flags containsObject: @"seen"])
    {
      if ([flags containsObject: @"answered"])
	{
	  if ([flags containsObject: @"$forwarded"])
	    cellClass = @"mailer_forwardedrepliedmailsubject";
	  else
	    cellClass = @"mailer_repliedmailsubject";
	}
      else if ([flags containsObject: @"$forwarded"])
	cellClass = @"mailer_forwardedmailsubject";
      else
	cellClass = @"mailer_readmailsubject";
    }
  else
    cellClass = @"mailer_unreadmailsubject";

  return cellClass;
//   return ([self isMessageRead]
// 	  ? @"mailer_readmailsubject"
// 	  : @"mailer_unreadmailsubject");
}

- (BOOL) hasMessageAttachment 
{
  NSArray *parts;
  NSEnumerator *dispositions;
  NSDictionary *currentDisp;
  BOOL hasAttachment;

  parts = [[message objectForKey: @"body"] objectForKey: @"parts"];
  if ([parts count] > 1)
    {
      dispositions
	= [[parts objectsForKey: @"disposition"] objectEnumerator];
      while (!hasAttachment
	     && (currentDisp = [dispositions nextObject]))
	hasAttachment = ([[currentDisp objectForKey: @"type"]
			   isEqualToString: @"ATTACHMENT"]);
    }
  else
    hasAttachment = NO;

  return hasAttachment;
}

/* fetching messages */

- (NSArray *) fetchKeys 
{
  /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
  static NSArray *keys = nil;

  if (!keys)
    keys = [[NSArray alloc] initWithObjects: @"UID",
			    @"FLAGS", @"ENVELOPE", @"RFC822.SIZE",
			    @"BODYSTRUCTURE", nil];

  return keys;
}

- (NSString *) defaultSortKey 
{
  return @"ARRIVAL";
}

- (NSString *) imap4SortKey 
{
  NSString *sort;
  
  sort = [[context request] formValueForKey: @"sort"];

  if (![sort length])
    sort = [self defaultSortKey];

  return [sort uppercaseString];
}

- (NSString *) imap4SortOrdering 
{
  NSString *sort, *ascending;

  sort = [self imap4SortKey];

  ascending = [[context request] formValueForKey: @"asc"];
  if (![ascending boolValue])
    sort = [@"REVERSE " stringByAppendingString: sort];

  return sort;
}

- (NSRange) fetchRange 
{
  if (firstMessageNumber == 0)
    return NSMakeRange(0, messagesPerPage);
  return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
}

- (NSArray *) sortedUIDs 
{
  EOQualifier *fetchQualifier, *notDeleted;

  if (!sortedUIDs)
    {
      notDeleted = [EOQualifier qualifierWithQualifierFormat:
				  @"(not (flags = %@))",
				@"deleted"];
      if (qualifier)
	{
	  fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
						     notDeleted, qualifier,
						   nil];
	  [fetchQualifier autorelease];
	}
      else
	fetchQualifier = notDeleted;

      sortedUIDs
        = [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier
			       sortOrdering: [self imap4SortOrdering]];
      [sortedUIDs retain];
    }

  return sortedUIDs;
}

- (unsigned int) totalMessageCount 
{
  return [sortedUIDs count];
}

- (BOOL) showsAllMessages 
{
  return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
}

- (NSRange) fetchBlock 
{
  NSRange  r;
  unsigned len;
  NSArray  *uids;
  
  r    = [self fetchRange];
  uids = [self sortedUIDs];
  
  /* only need to restrict if we have a lot */
  if ((len = [uids count]) <= r.length) {
    r.location = 0;
    r.length   = len;
    return r;
  }
  
  if (len < r.location) {
    // TODO: CHECK CONDITION (< vs <=)
    /* out of range, recover at first block */
    r.location = 0;
    return r;
  }
  
  if (r.location + r.length > len)
    r.length = len - r.location;
  return r;
}

- (unsigned int) firstMessageNumber 
{
  return [self fetchBlock].location + 1;
}

- (unsigned int) lastMessageNumber 
{
  NSRange r;
  
  r = [self fetchBlock];
  return r.location + r.length;
}

- (BOOL) hasPrevious 
{
  return [self fetchBlock].location == 0 ? NO : YES;
}

- (BOOL) hasNext 
{
  NSRange r = [self fetchBlock];
  return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
}

- (unsigned int) nextFirstMessageNumber 
{
  return [self firstMessageNumber] + [self fetchRange].length;
}

- (unsigned int) lastFirstMessageNumber 
{
  unsigned int max, modulo;

  if (!sortedUIDs)
    [self sortedUIDs];

  max = [sortedUIDs count];
  modulo = (max % messagesPerPage);
  if (modulo == 0)
    modulo = messagesPerPage;

  return (max + 1 - modulo);
}

- (unsigned int) prevFirstMessageNumber 
{
  NSRange  r;
  unsigned idx;
  
  idx = [self firstMessageNumber];
  r   = [self fetchRange];
  if (idx > r.length)
    return (idx - r.length);
  return 1;
}

- (NSArray *) messages 
{
  NSArray *uids;
  NSDictionary *msgs;
  NSRange r;
  unsigned len;
  
  if (!messages)
    {
      r    = [self fetchBlock];
      uids = [self sortedUIDs];
      len = [uids count];
      if (len > r.length)
	/* only need to restrict if we have a lot */
	uids = [uids subarrayWithRange: r];
  
      msgs = [[self clientObject] fetchUIDs: uids parts: [self fetchKeys]];
      messages = [[msgs objectForKey: @"fetch"] retain];
    }

  return messages;
}

/* URL processing */

- (NSString *) messageViewTarget
{
  return [NSString stringWithFormat: @"SOGo_msg_%@",
                   [self messageUidString]];
}

- (NSString *) messageViewURL 
{
  // TODO: noframe only when view-target is empty
  // TODO: markread only if the message is unread
  NSString *s;
  
  s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
  if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
  return s;
}
- (NSString *) markReadURL 
{
  return [@"markMessageRead?uid=" stringByAppendingString:
	     [self messageUidString]];
}
- (NSString *) markUnreadURL 
{
  return [@"markMessageUnread?uid=" stringByAppendingString:
	     [self messageUidString]];
}

/* JavaScript */

- (NSString *)msgRowID
{
  return [@"row_" stringByAppendingString:[self messageUidString]];
}

- (NSString *)msgDivID
{
  return [@"div_" stringByAppendingString:[self messageUidString]];
}

- (NSString *)msgIconReadImgID
{
  return [@"readdiv_" stringByAppendingString:[self messageUidString]];
}

- (NSString *)msgIconUnreadImgID
{
  return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
}

/* error redirects */

- (id) redirectToViewWithError: (id) _error 
{
  // TODO: DUP in UIxMailAccountView
  // TODO: improve, localize
  // TODO: there is a bug in the treeview which preserves the current URL for
  //       the active object (displaying the error again)
  id url;
  
  if (![_error isNotNull])
    return [self redirectToLocation:@"view"];
  
  if ([_error isKindOfClass:[NSException class]])
    _error = [_error reason];
  else if ([_error isKindOfClass:[NSString class]])
    _error = [_error stringValue];
  
  url = [_error stringByEscapingURL];
  url = [@"view?error=" stringByAppendingString:url];
  return [self redirectToLocation:url];
}

/* actions */

- (int) firstMessageOfPageFor: (int) messageNbr
{
  NSArray *messageNbrs;
  int nbrInArray;
  int firstMessage;

  messageNbrs = [self sortedUIDs];
  nbrInArray
    = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
  if (nbrInArray > -1)
    firstMessage = ((int) (nbrInArray / messagesPerPage)
                    * messagesPerPage) + 1;
  else
    firstMessage = 1;

  return firstMessage;
}

- (void) _setQualifierForCriteria: (NSString *) criteria
			 andValue: (NSString *) value
{
  [qualifier release];

  if ([criteria isEqualToString: @"subject"])
    qualifier = [EOQualifier qualifierWithQualifierFormat:
			       @"(subject doesContain: %@)", value];
  else if ([criteria isEqualToString: @"sender"])
    qualifier = [EOQualifier qualifierWithQualifierFormat:
			       @"(from doesContain: %@)", value];
  else if ([criteria isEqualToString: @"subject_or_sender"])
    qualifier = [EOQualifier qualifierWithQualifierFormat:
			       @"((subject doesContain: %@)"
			     @" OR (from doesContain: %@))",
			     value, value];
  else if ([criteria isEqualToString: @"to_or_cc"])
    qualifier = [EOQualifier qualifierWithQualifierFormat:
			       @"((to doesContain: %@)"
			     @" OR (cc doesContain: %@))",
			     value, value];
  else if ([criteria isEqualToString: @"entire_message"])
    qualifier = [EOQualifier qualifierWithQualifierFormat:
			       @"(body doesContain: %@)", value];
  else
    qualifier = nil;

  [qualifier retain];
}

- (id) defaultAction 
{
  WORequest *request;
  NSString *specificMessage, *searchCriteria, *searchValue;
  SOGoUserFolder *co;

  request = [context request];

  co = [self clientObject];
  [co flushMailCaches];
  [co expungeLastMarkedFolder];

  specificMessage = [request formValueForKey: @"pageforuid"];
  searchCriteria = [request formValueForKey: @"search"];
  searchValue = [request formValueForKey: @"value"];
  if ([searchValue length])
    [self _setQualifierForCriteria: searchCriteria
	  andValue: searchValue];

  firstMessageNumber
    = ((specificMessage)
       ? [self firstMessageOfPageFor: [specificMessage intValue]]
       : [[request formValueForKey:@"idx"] intValue]);

  return self;
}

- (id) getMailAction 
{
  // TODO: we might want to flush the caches?
  id client;

  if ((client = [self clientObject]) == nil) {
    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
			reason:@"did not find mail folder"];
  }

  if (![client respondsToSelector:@selector(flushMailCaches) ]) 
    {
      return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
                          reason:
                            @"invalid client object (does not support flush)"];
    }

  [client flushMailCaches];

  return [self redirectToLocation:@"view"];
}

- (NSString *) msgLabels
{
  NSMutableArray *labels;
  NSEnumerator *flags;
  NSString *currentFlag;

  labels = [NSMutableArray new];
  [labels autorelease];

  flags = [[message objectForKey: @"flags"] objectEnumerator];
  while ((currentFlag = [flags nextObject]))
    if ([currentFlag hasPrefix: @"$label"])
      [labels addObject: [currentFlag substringFromIndex: 1]];

  return [labels componentsJoinedByString: @" "];
}

@end

/* UIxMailListView */
