/*
  Copyright (C) 2006-2021 Inverse inc.

  This file is part of SOGo.

  SOGo 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.

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

/*
  UIxMailPartICalViewer
 
  Show plain/calendar mail parts.
*/

#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSValue.h>


#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>

#import <NGImap4/NGImap4EnvelopeAddress.h>

#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>

#import <SOGo/SOGoDateFormatter.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSString+Utilities.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentFolders.h>
#import <Appointments/SOGoAppointmentObject.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoMailBodyPart.h>

#import "UIxMailPartICalViewer.h"

@implementation UIxMailPartICalViewer

- (void) dealloc
{
  [storedEventObject release];
  [storedEvent release];
  [inCalendar release];
  [dateFormatter release];
  [super dealloc];
}

/* maintain caches */

- (void) resetPathCaches
{
  [super resetPathCaches];
  inEvent = nil;
  [inCalendar release]; inCalendar = nil;
  [storedEventObject release]; storedEventObject = nil;
  [storedEvent release]; storedEvent = nil;
}

/* accessors */

- (iCalCalendar *) inCalendar
{
  if (!inCalendar)
    {
      inCalendar = [iCalCalendar parseSingleFromSource: [self flatContentAsString]];
      [inCalendar retain];
    }

  return inCalendar;
}

- (BOOL) couldParseCalendar
{
  return ([self inCalendar] != nil);
}

- (iCalEvent *) inEvent
{
  NSArray *events;
 
  if (!inEvent)
    {
      events = [[self inCalendar] events];
      if ([events count] > 0)
	inEvent = [events objectAtIndex: 0];
    }

  return inEvent;
}

/* formatters */

- (SOGoDateFormatter *) dateFormatter
{
  if (!dateFormatter)
    {
      dateFormatter = [[context activeUser] dateFormatterInContext: context];
      [dateFormatter retain];
    }

  return dateFormatter;
}

- (NSString *) _personForDisplay: (iCalPerson *) person
{
  NSString *fn, *email, *result;

  fn = [person cnWithoutQuotes];
  email = [person rfc822Email];
  if ([fn length])
    result = [NSString stringWithFormat: @"%@ <%@>",
                       fn, email];
  else
    result = email;

  return result;
}

- (NSCalendarDate *) startCalendarDate
{
  NSCalendarDate *date;
  SOGoUserDefaults *ud;
 
  date = [[self inEvent] startDate];
  ud = [[context activeUser] userDefaults];
  if (![[self inEvent] isAllDay])
    [date setTimeZone: [ud timeZone]];
  
  return date;
}

/*
  In v3.0, we moved the template's logic here to format
  the event's start/end date-time. The previous logic was:

          <dd><var:string value="startDate" />
            <var:if condition="inEvent.isAllDay" const:negate="YES">
              <var:string value="startTime" />
            </var:if>
            <var:if condition="isEndDateOnSameDay">
              <var:if condition="inEvent.isAllDay" const:negate="YES">
                <var:string label:value="to" />
                <var:string value="endTime" />
              </var:if>
            </var:if>
            <var:if condition="isEndDateOnSameDay" const:negate="YES">
              <var:string label:value="to" />
              <var:string value="endDate" />
              <var:if condition="inEvent.isAllDay" const:negate="YES">
                <var:string value="endTime" />
              </var:if>
            </var:if>
          </dd>

*/
- (NSString *) formattedDateTime
{
  NSMutableString *s;

  s = [NSMutableString string];

  [s appendString: [self startDate]];

  if (![[self inEvent] isAllDay])
    [s appendFormat: @" %@", [self startTime]];

  if ([self isEndDateOnSameDay] &&
      ![[self inEvent] isAllDay])
    {
      [s appendFormat: @" %@", [self labelForKey: @"to_time"]];
      [s appendFormat: @" %@", [self endTime]];
    }
  else if (![self isEndDateOnSameDay])
    {
      [s appendFormat: @" %@", [self labelForKey: @"to_date"]];
      [s appendFormat: @" %@", [self endDate]];

      if (![[self inEvent] isAllDay])
        [s appendFormat: @" %@", [self endTime]];
    }

  return s;
}

- (NSString *) startDate
{
  return [[self dateFormatter] formattedDate: [self startCalendarDate]];
}

- (NSString *) startTime
{
  return [[self dateFormatter] formattedTime: [self startCalendarDate]];
}

- (NSCalendarDate *) endCalendarDate
{
  NSCalendarDate *date;
  SOGoUserDefaults *ud;
 
  date = [[self inEvent] endDate];
  ud = [[context activeUser] userDefaults];
  if (![[self inEvent] isAllDay])
    [date setTimeZone: [ud timeZone]];

  return date;
}

- (NSString *) endDate
{
  NSCalendarDate *aDate;
  if ([[self inEvent] isAllDay])
    aDate = [[self endCalendarDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-1];
  else
    aDate = [self endCalendarDate];

  return [[self dateFormatter] formattedDate: aDate];
}

- (NSString *) endTime
{
  return [[self dateFormatter] formattedTime: [self endCalendarDate]];
}

- (NSTimeInterval) duration
{
  return [[self endCalendarDate] timeIntervalSinceDate:[self startCalendarDate]];
}

- (BOOL) isEndDateOnSameDay
{
  if ([[self inEvent] isAllDay])
    return [self duration] <= 86400;

  return [[self startCalendarDate] isDateOnSameDay: [self endCalendarDate]];
}

/* calendar folder support */
- (BOOL) hasCalendarAccess
{
  return [[context activeUser] canAccessModule: @"Calendar"];
}

/* lookup object in the user calendars */
- (SOGoAppointmentObject *) storedEventObject
{
  NSArray *folders;
  NSEnumerator *e;
  NSString *uid, *cname, *userLogin;
  SOGoAppointmentFolder *folder;
  SOGoUser *user;

  if (!storedEventFetched)
    {
      if ([self hasCalendarAccess])
        {
          user = [context activeUser];
          userLogin = [user login];
          uid = [[self inEvent] uid];
          storedEventObject = nil;

          folders = [[user calendarsFolderInContext: context] subFolders];
          e = [folders objectEnumerator];
          while (storedEventObject == nil && (folder = [e nextObject]))
            {
              if ([[folder ownerInContext: nil] isEqualToString: userLogin])
                {
                  cname = [folder resourceNameForEventUID: uid];
                  if (cname)
                    {
                      storedEventObject = [folder lookupName: cname inContext: context
                                               acquire: NO];
                      if ([storedEventObject isKindOfClass: [NSException class]])
                        storedEventObject = nil;
                      else
                        [storedEventObject retain];
                    }
                }
            }
        }
      storedEventFetched = YES;
    }
 
  return storedEventObject;
}

- (BOOL) isEventStoredInCalendar
{
  NSCalendarDate *recurrenceId;
  NSString *recurrenceTime;
  BOOL isInCalendar;

  recurrenceId = [[self inEvent] recurrenceId];

  if (recurrenceId)
    {
      recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
      isInCalendar = ([[self storedEventObject] lookupOccurrence: recurrenceTime] != nil);
    }
  else
    isInCalendar = ([self storedEventObject] != nil);

  return isInCalendar;
}

- (iCalEvent *) storedEvent
{
  if (!storedEvent)
    {
      NSCalendarDate *recurrenceId;

      recurrenceId = [[self inEvent] recurrenceId];

      if (recurrenceId == nil)
	storedEvent = [[self storedEventObject] component: NO secure: NO];
      else
	{
	  // Find the specific occurence within the repeating vEvent.
	  NSString *recurrenceTime;
	  iCalPerson *organizer;

	  recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
	  storedEvent = (iCalEvent*)[[self storedEventObject] lookupOccurrence: recurrenceTime];

	  if (storedEvent == nil)
	    // If no occurence found, create one
	    storedEvent = (iCalEvent*)[storedEventObject newOccurenceWithID: recurrenceTime];

	  // Add organizer to occurence if not present
	  organizer = [storedEvent organizer];
	  if (organizer == nil || [organizer isVoid])
	    {
	      organizer = [(iCalEntityObject *)[[storedEvent parent] firstChildWithTag: [storedEventObject componentTag]] organizer];
	      [storedEvent setOrganizer: organizer];
	    }
	}
      
      [storedEvent retain];
    }

  return storedEvent;
}

/* organizer tracking */

- (NSString *) loggedInUserEMail
{
  NSDictionary *identity;

  identity = [[context activeUser] primaryIdentity];

  return [identity objectForKey: @"email"];
}

- (iCalEvent *) authorativeEvent
{
  iCalEvent *authorativeEvent;

  [self storedEvent];
  if (!storedEvent
      || ([storedEvent compare: [self inEvent]] == NSOrderedAscending))
    authorativeEvent = inEvent;
  else
    authorativeEvent = [self storedEvent];

  return authorativeEvent;
}

- (BOOL) isLoggedInUserTheOrganizer
{
  return [[self authorativeEvent] userIsOrganizer: [context activeUser]];
}

- (BOOL) isLoggedInUserAnAttendee
{
  return [[self authorativeEvent] userIsAttendee: [context activeUser]];
}

/* derived fields */
- (BOOL) hasOrganizer
{
  iCalPerson *organizer;

  organizer = [[self authorativeEvent] organizer];

  if ([organizer isVoid])
    return NO;

  return YES;
}

- (NSString *) organizerDisplayName
{
  iCalPerson *organizer;
  NSString *value;

  organizer = [[self authorativeEvent] organizer];
  value = [self _personForDisplay: organizer];

  return value;
}

/* replies */

- (NGImap4EnvelopeAddress *) replySenderAddress
{
  /* 
     The iMIP reply is the sender of the mail, the 'attendees' are NOT set to
     the actual attendees. BUT the attendee field contains the reply-status!
  */
  id tmp;
 
  tmp = [[self clientObject] fromEnvelopeAddresses];
  if ([tmp count] == 0) return nil;
  return [tmp objectAtIndex:0];
}

- (NSString *) replySenderEMail
{
  return [[self replySenderAddress] email];
}

- (NSString *) replySenderBaseEMail
{
  return [[self replySenderAddress] baseEMail];
}

- (iCalPerson *) inReplyAttendee
{
  NSArray *attendees;
 
  attendees = [[self inEvent] attendees];
  if ([attendees count] == 0)
    return nil;
  if ([attendees count] > 1)
    [self warnWithFormat:@"More than one attendee in REPLY: %@", attendees];
 
  return [attendees objectAtIndex:0];
}

- (iCalPerson *) currentUserAttendee
{
  iCalPerson *currentUser;

  currentUser = [[self authorativeEvent] userAsAttendee: [context activeUser]];

  return currentUser;
}

- (iCalPerson *) _attendeeFromMailSenderInEvent: (iCalEvent *) e
{
  NSString *baseEmail;
  iCalPerson *p;

  baseEmail = [self replySenderBaseEMail];

  /*
    Note: -findAttendeeWithEmail: does not parse the email!
  */
  p = [e findAttendeeWithEmail: baseEmail];
  if (!p)
    p = [e findAttendeeWithEmail:[self replySenderEMail]];

  // We haven't found it yet, let's look in the identities
  // associated to this user
  if (!p)
    {
      SOGoUserManager *sm;
      NSString *uid;

      sm = [SOGoUserManager sharedUserManager];
      uid = [sm getUIDForEmail: baseEmail];

      if (uid)
	{
	  NSArray *allEmails;
	  NSString *email;
	  SOGoUser *u;
	  int i;

	  u = [SOGoUser userWithLogin: uid];
	  allEmails = [u allEmails];
	  for (i = 0; i < [allEmails count]; i++)
	    {
	      email = [allEmails objectAtIndex: i];
	      if ([email caseInsensitiveCompare: baseEmail] == NSOrderedSame)
		continue;

	      p = [e findAttendeeWithEmail: email];

	      if (p)
		break;
	    }
	}
    }

  return p;
}

- (iCalPerson *) storedReplyAttendee
{
  iCalEvent *e;

  e = [self storedEvent];
  if (e)
    return [self _attendeeFromMailSenderInEvent: e];

  return nil;
}

- (BOOL) isReplySenderAnAttendee
{
  return ([self storedReplyAttendee] != nil);
}

- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
  return [self _attendeeFromMailSenderInEvent: event];
}

- (BOOL) hasSenderStatusChanged
{
  iCalPerson *emailParticipant, *calendarParticipant;

  [self inEvent];
  [self storedEvent];
  emailParticipant = [self _emailParticipantWithEvent: inEvent];
  calendarParticipant = [self _emailParticipantWithEvent: storedEvent];

  return ([[emailParticipant partStat]
	    caseInsensitiveCompare: [calendarParticipant partStat]]
	  != NSOrderedSame);
}

- (BOOL) canOriginalEventBeUpdated
{
  return ([self hasCalendarAccess]
	  && [self hasSenderStatusChanged]
	  && ([[inEvent sequence] compare: [storedEvent sequence]]
	      != NSOrderedAscending));
}

- (id) renderedPart
{
  NSMutableDictionary *d;
  NSArray *participants;
  iCalPerson *person;
  NSMutableArray *a;
  int i;

  d = [NSMutableDictionary dictionaryWithDictionary: [super renderedPart]];

  // We also add our participants
  participants = [[self authorativeEvent] participants];
  a = [NSMutableArray array];

  for (i = 0; i < [participants count]; i++)
    {
      person = [participants objectAtIndex: i];

      if (![[person delegatedTo] length])
        [a addObject: [NSDictionary dictionaryWithObjectsAndKeys: ([[person cnWithoutQuotes] length] ? [person cnWithoutQuotes] : [person rfc822Email]), @"name",
                                    [person rfc822Email], @"email",
                                    [[person partStatWithDefault] lowercaseString], @"partstat", nil]];
    }

  [d setObject: a  forKey: @"participants"];

  //Sanitise the html content
  if([d objectForKey:@"content"]){
    [d setObject: [[d objectForKey:@"content"] stringWithoutHTMLInjection: NO] forKey: @"content"];
  }

  return d;
}

- (BOOL) hasLocation {
  return [self inEvent] != nil 
          && [[self inEvent] location] != nil 
          && [[[self inEvent] location] length] > 0;
}

- (NSString *) location {
  return [[self inEvent] location];
}

@end /* UIxMailPartICalViewer */
