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


#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalFreeBusy.h>
#import <NGCards/iCalPerson.h>

#import <SOGo/SOGoBuild.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSource.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>

#import "SOGoAppointmentFolder.h"
#import "SOGoAppointmentFolders.h"

#import "MSExchangeFreeBusy.h"

#import "SOGoFreeBusyObject.h"

@interface SOGoFreeBusyObject (PrivateAPI)
- (NSString *) iCalStringForFreeBusyInfos: (NSArray *) _infos
                                     from: (NSCalendarDate *) _startDate
                                       to: (NSCalendarDate *) _endDate;
@end

@implementation SOGoFreeBusyObject

- (iCalPerson *) iCalPersonWithUID: (NSString *) uid
{
  iCalPerson *person;
  SOGoUserManager *um;
  NSString *domain;
  NSDictionary *contactInfos;

  um = [SOGoUserManager sharedUserManager];
  contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
  if (contactInfos == nil)
    {
      // Search among global addressbooks
      domain = [[context activeUser] domain];
      contactInfos = [um fetchContactWithUID: uid inDomain: domain];
    }

  /* iCal.app compatibility:
     - don't add "cn"; */
  person = [iCalPerson new];
  [person autorelease];
  [person setEmail: [contactInfos objectForKey: @"c_email"]];

  return person;
}

/* Private API */
- (iCalFreeBusyType) _fbTypeForEventStatus: (int) eventStatus
{
  //unsigned int status;
  iCalFreeBusyType fbType;

  //status = [eventStatus unsignedIntValue];
  if (eventStatus == 0)
    fbType = iCalFBBusyTentative;
  else if (eventStatus == 1)
    fbType = iCalFBBusy;
  else
    fbType = iCalFBFree;

  return fbType;    
}

- (NSString *) iCalStringForFreeBusyInfos: (NSArray *) _infos
			       withMethod: (NSString *) method
                                   andUID: (NSString *) uid
                             andOrganizer: (iCalPerson *) organizer
                               andContact: (NSString *) contactID
                                     from: (NSCalendarDate *) _startDate
                                       to: (NSCalendarDate *) _endDate
{
  NSArray *emails, *partstates;
  NSEnumerator *events;
  iCalCalendar *calendar;
  iCalFreeBusy *freebusy;
  NSDictionary *info;
  iCalFreeBusyType type;
  SOGoUser *user;
  NSString *login;
  int i;

  login = [container ownerInContext: context];
  user = [SOGoUser userWithLogin: login];

  calendar = [iCalCalendar groupWithTag: @"vcalendar"];
  [calendar setProdID: [NSString stringWithFormat:
                                   @"-//Inverse inc./SOGo %@//EN",
                                 SOGoVersion]];
  [calendar setVersion: @"2.0"];
  if (method)
    [calendar setMethod: method];

  freebusy = [iCalFreeBusy groupWithTag: @"vfreebusy"];
  if (uid)
    [freebusy setUid: uid];
  if (organizer)
    [freebusy setOrganizer: organizer];
  if (contactID)
    [freebusy addToAttendees: [self iCalPersonWithUID: contactID]];
  else
    [freebusy addToAttendees: [self iCalPersonWithUID: login]];
  [freebusy setTimeStampAsDate: [NSCalendarDate calendarDate]];
  [freebusy setStartDate: _startDate];
  [freebusy setEndDate: _endDate];

  /* ORGANIZER - strictly required but missing for now */

  /* ATTENDEE */
//   person = [self iCalPersonWithUid: login];
//   [person setTag: @"ATTENDEE"];
//   [ms appendString: [person versitString]];

  /* FREEBUSY */
  events = [_infos objectEnumerator];
  while ((info = [events nextObject]))
    if ([[info objectForKey: @"c_isopaque"] boolValue])
      {
	type = iCalFBFree;

	// If the event has NO organizer (which means it's the user that has created it) OR
	// If we are the organizer of the event THEN we are automatically busy
	if ([[info objectForKey: @"c_orgmail"] length] == 0 ||
	    [user hasEmail: [info objectForKey: @"c_orgmail"]])
	  {
	    type = iCalFBBusy;
	  }
	else
	  {
	    // We check if the user has accepted/declined or needs action
	    // on the current event.
	    emails = [[info objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];

	    for (i = 0; i < [emails count]; i++)
	      {
		if ([user hasEmail: [emails objectAtIndex: i]])
		  {
		    // We now fetch the c_partstates array and get the participation
		    // status of the user for the event
		    partstates = [[info objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
		    
		    if (i < [partstates count])
		      {
			type = [self _fbTypeForEventStatus: [[partstates objectAtIndex: i] intValue]];
		      }
		    break;
		  }
	      }
	  }

	if (type == iCalFBBusy
            || type == iCalFBBusyTentative
            || type == iCalFBBusyUnavailable)
	  [freebusy addFreeBusyFrom: [info objectForKey: @"startDate"]
		    to: [info objectForKey: @"endDate"]
		    type: type];
      }

  [calendar setUniqueChild: freebusy];

  return [calendar versitString];
}

- (NSString *) contentAsString
{
  NSCalendarDate *today, *startDate, *endDate;
  SOGoUserDefaults *ud;
  SOGoDomainDefaults *dd;
  NSArray *interval;
  int start, end;
  
  today = [[NSCalendarDate calendarDate] beginOfDay];
  ud = [[context activeUser] userDefaults];
  [today setTimeZone: [ud timeZone]];

  dd = [[context activeUser] domainDefaults];
  interval = [dd freeBusyDefaultInterval];
  if ([interval count] > 1)
    {
      start = [[interval objectAtIndex: 0] unsignedIntValue];
      end = [[interval objectAtIndex: 1] unsignedIntValue];
    }
  else
    {
      start = 7;
      end = 7;
    }

  startDate = [today dateByAddingYears: 0 months: 0 days: -start
                     hours: 0 minutes: 0 seconds: 0];
  endDate = [today dateByAddingYears: 0 months: 0 days: end
		   hours: 0 minutes: 0 seconds: 0];

  return [self contentAsStringFrom: startDate to: endDate];
}

- (NSString *) contentAsStringWithMethod: (NSString *) method
                                  andUID: (NSString *) UID
                            andOrganizer: (iCalPerson *) organizer
                              andContact: (NSString *) contactID
				    from: (NSCalendarDate *) _startDate
				      to: (NSCalendarDate *) _endDate
{
  NSArray *infos;

  infos = [self fetchFreeBusyInfosFrom: _startDate to: _endDate
                            forContact: contactID];

  return [self iCalStringForFreeBusyInfos: infos
                               withMethod: method
                                   andUID: UID andOrganizer: organizer
                               andContact: contactID
                                     from: _startDate to: _endDate];
}

- (NSString *) contentAsStringFrom: (NSCalendarDate *) _startDate
				to: (NSCalendarDate *) _endDate
{
  return [self contentAsStringWithMethod: nil andUID: nil
                            andOrganizer: nil
                              andContact: nil
                                    from: _startDate
                                      to: _endDate];
}

/**
 * Fetch freebusy information for a user that exists in a contact source
 * (not an authentication source) for which freebusy information is available
 * (currently limited to a Microsoft Exchange server with Web Services enabled).
 * @param startDate the beginning of the covered period
 * @param endDate the ending of the covered period
 * @param uid the ID of the contact within the current domain
 * @return an array of dictionaries containing the start and end dates of each busy period
 * @see MSExchangeFreeBusy.m
 */
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate
                                  to: (NSCalendarDate *) endDate
                          forContact: (NSString *) uid
{
  if ([uid length])
    {
      SOGoUserManager *um;
      NSString *domain, *email;
      NSDictionary *contact;
      MSExchangeFreeBusy *exchangeFreeBusy;
      NSObject <SOGoDNSource> *source;

      um = [SOGoUserManager sharedUserManager];
      domain = [[context activeUser] domain];
      contact = [um fetchContactWithUID: uid inDomain: domain];
      if (contact)
        {
          email = [contact valueForKey: @"c_email"];
          source = [contact objectForKey: @"source"];
          if ([email length]
              && [source conformsToProtocol: @protocol (SOGoDNSource)]
              && [source MSExchangeHostname])
            {
              exchangeFreeBusy = [[MSExchangeFreeBusy alloc] init];
              [exchangeFreeBusy autorelease];

              return [exchangeFreeBusy fetchFreeBusyInfosFrom: startDate
                                                           to: endDate
                                                     forEmail: email
                                                     inSource: source
                                                    inContext: context];
            }
        }
    }
  else
    {
      return [self fetchFreeBusyInfosFrom: startDate to: endDate];
    }
  
  // No freebusy information found
  return nil;
}


- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate
                                  to: (NSCalendarDate *) endDate
{
  SOGoAppointmentFolder *calFolder;
  SOGoAppointmentFolders *calFolders;
  SOGoUser *user;
  SOGoUserDefaults *ud;
  NSArray *folders;
  NSMutableArray *infos;
  NSString *login;
  unsigned int count, max;

  infos = [NSMutableArray array];

  calFolders = [container lookupName: @"Calendar"
                           inContext: context
                             acquire: NO];
  [calFolders appendSubscribedSources];
  folders = [calFolders subFolders];

  max = [folders count];
  for (count = 0; count < max; count++)
    {
      calFolder = [folders objectAtIndex: count];
      if ([calFolder includeInFreeBusy])
	[infos addObjectsFromArray: [calFolder fetchFreeBusyInfosFrom: startDate
                                                                   to: endDate]];
    }

  login = [container ownerInContext: context];
  user = [SOGoUser userWithLogin: login];
  ud = [user userDefaults];

  if ([ud busyOffHours])
    {
      NSCalendarDate *currentStartDate, *currentEndDate, *weekendStartDate, *weekendEndDate;
      NSTimeZone *timeZone;
      unsigned int dayStartHour, dayEndHour, intervalHours;
      BOOL firstRange;

      dayStartHour = [ud dayStartHour];
      dayEndHour = [ud dayEndHour];
      intervalHours = dayStartHour + 24 - dayEndHour;
      timeZone = [ud timeZone];
      firstRange = YES;

      currentStartDate = [NSCalendarDate dateWithYear: [startDate yearOfCommonEra]
                                                month: [startDate monthOfYear]
                                                  day: [startDate dayOfMonth]
                                                 hour: 0
                                               minute: 0
                                               second: 0
                                             timeZone: timeZone];
      currentEndDate = [NSCalendarDate dateWithYear: [startDate yearOfCommonEra]
                                              month: [startDate monthOfYear]
                                                day: [startDate dayOfMonth]
                                               hour: dayStartHour
                                             minute: 0
                                             second: 0
                                           timeZone: timeZone];

      while ([currentStartDate compare: endDate] == NSOrderedAscending ||
             [currentStartDate compare: endDate] == NSOrderedSame)
        {
          if ([endDate compare: currentEndDate] == NSOrderedAscending)
            currentEndDate = endDate;

          if ([currentStartDate compare: startDate] == NSOrderedAscending)
            {
              if ([startDate compare: currentEndDate] == NSOrderedAscending)
                {
                  [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys:
                                                      [NSNumber numberWithBool: YES], @"c_isopaque",
                                                  startDate, @"startDate",
                                                  currentEndDate, @"endDate", nil]];
                }
            }
          else
            {
              [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys:
                                                  [NSNumber numberWithBool: YES], @"c_isopaque",
                                              currentStartDate, @"startDate",
                                              currentEndDate, @"endDate", nil]];
            }

          if (currentEndDate != endDate
              && ([currentEndDate dayOfWeek] == 6 || [currentEndDate dayOfWeek] == 0))
            {
              // Fill weekend days
              weekendStartDate = currentEndDate;
              weekendEndDate = [weekendStartDate addYear:0 month:0 day:0 hour:(-[weekendStartDate hourOfDay] + dayEndHour) minute:0 second:0];
              [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: YES], @"c_isopaque",
                                              weekendStartDate, @"startDate",
                                              weekendEndDate, @"endDate", nil]];
            }

          // Compute next range
          if (firstRange)
            {
              currentStartDate = [currentStartDate addYear:0 month:0 day:0 hour:dayEndHour minute:0 second:0];
              firstRange = NO;
            }
          else
            {
              currentStartDate = [currentStartDate addYear:0 month:0 day:1 hour:0 minute:0 second:0];
            }      
          currentEndDate = [currentStartDate addYear:0 month:0 day:0 hour:intervalHours minute:0 second:0];
        }
    }
  
  return infos;
}

- (NSString *) iCalString
{
  // for UI-X appointment viewer
  return [self contentAsString];
}

/* deliver content without need for view method */

- (id) GETAction: (id)_ctx
{
  WOResponse *r;
  NSData *contentData;

  contentData = [[self contentAsString]
		  dataUsingEncoding: NSUTF8StringEncoding];

  r = [(WOContext *) _ctx response];
  [r setHeader: @"text/calendar" forKey: @"content-type"];
  [r setContent: contentData];
  [r setStatus: 200];

  return r;
}

- (BOOL) isFolderish
{
  return NO;
}

- (NSString *) davContentType
{
  return @"text/calendar";
}

- (NSArray *) aclsForUser: (NSString *) uid
{
  return nil;
}

@end
