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

#include "NGImap4ConnectionManager.h"
#include "NGImap4Connection.h"
#include "NGImap4Client.h"
#include "imCommon.h"

@implementation NGImap4ConnectionManager

static BOOL           debugOn    = NO;
static BOOL           debugCache = NO;
static BOOL           poolingOff = NO;
static NSTimeInterval PoolScanInterval = 5 * 60 /* every five minutes */;
static NSString       *AuthMechanism = nil;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  
  debugOn       = [ud boolForKey:@"NGImap4EnableIMAP4Debug"];
  debugCache    = [ud boolForKey:@"NGImap4EnableIMAP4CacheDebug"];
  poolingOff    = [ud boolForKey:@"NGImap4DisableIMAP4Pooling"];
  AuthMechanism = [ud stringForKey:@"NGImap4AuthMechanism"];
  [AuthMechanism retain];
  
  if ([ud objectForKey:@"NGImap4PoolingCleanupInterval"])
    PoolScanInterval = [[ud objectForKey:@"NGImap4PoolingCleanupInterval"] doubleValue];

  //if (debugOn)    NSLog(@"Note: NGImap4EnableIMAP4Debug is enabled!");
  //if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!");
}

+ (id)defaultConnectionManager:(NSString *) imapAuthMech {
  static NGImap4ConnectionManager *manager = nil; // THREAD
  static NGImap4ConnectionManager *managerXoauth = nil; // THREAD
  
  //In case of diffrent auth type for domains, instantiate the proper imap object.
  if(imapAuthMech && [imapAuthMech length] > 0) {
    if([imapAuthMech isEqualToString: @"xoauth2"]) {
      if(managerXoauth == nil) {
        managerXoauth = [[self alloc] init];
        AuthMechanism = imapAuthMech;
      }
      return managerXoauth;
    }
    if([imapAuthMech isEqualToString: @"plain"]) {
      if(manager == nil) {
        manager = [[self alloc] init];
        AuthMechanism = imapAuthMech;
      }
      return manager;
    }
  }
  else if (manager == nil) 
    manager = [[self alloc] init];
  
  return manager;
}

- (id)init {
  if ((self = [super init])) {
    if (!poolingOff) {
      self->urlToEntry = [[NSMutableDictionary alloc] initWithCapacity:256];
      self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
				  PoolScanInterval
				target:self selector:@selector(_garbageCollect:)
				userInfo:nil repeats:YES] retain];
    }
  }
  return self;
}

- (void)dealloc {
  [self->gcTimer invalidate];
  [self->urlToEntry release];
  [self->gcTimer    release];
  [super dealloc];
}

/* cache */

- (id)cacheKeyForURL:(NSURL *)_url {
  // protocol, user, host, port
  return [NSString stringWithFormat:@"%@://%@@%@:%@",
		   [_url scheme], [_url user], [_url host], [_url port]];
}

- (NGImap4Connection *)entryForURL:(NSURL *)_url {
  if (_url == nil)
    return nil;
  
  return [self->urlToEntry objectForKey:[self cacheKeyForURL:_url]];
}
- (void)cacheEntry:(NGImap4Connection *)_entry forURL:(NSURL *)_url {
  if (_entry == nil) _entry = (id)[NSNull null];
  [self->urlToEntry setObject:_entry forKey:[self cacheKeyForURL:_url]];
}

- (void)_garbageCollect:(NSTimer *)_timer {
  // TODO: scan for old IMAP4 channels
  NGImap4Connection *entry;
  NSDate *now;
  NSArray *a;
  int i;

  a = [self->urlToEntry allKeys];
  now = [NSDate date];

  for (i = 0; i < [a count]; i++)
    {
      entry = [self->urlToEntry objectForKey: [a objectAtIndex: i]];

      if ([now timeIntervalSinceDate: [entry creationTime]] > PoolScanInterval)
	{
	  [[entry client] logout];
	  [self->urlToEntry removeObjectForKey: [a objectAtIndex: i]];
	}
    }

  [self debugWithFormat:@"should collect IMAP4 channels (%d active)",
	  [self->urlToEntry count]];
}

- (NGImap4Connection *)connectionForURL:(NSURL *)_url password:(NSString *)_p {
  /*
    Three cases:
    a) not yet connected             => create new entry and connect
    b) connected, correct password   => return cached entry
    c) connected, different password => try to recreate entry
  */
  NGImap4Connection *entry;
  NGImap4Client *client;

  if (poolingOff) {
    client = [self imap4ClientForURL:_url password:_p];
    entry = [[NGImap4Connection alloc] initWithClient:client 
				       password:_p];
    return [entry autorelease];
  }
  else {
  /* check cache */
  
    if ((entry = [self entryForURL:_url]) != nil) {
      if ([entry isValidPassword:_p]) {
	if (debugCache)
	  [self logWithFormat:@"valid password, reusing cache entry ..."];
	return entry;
      }
    
      /* different password, password could have changed! */
      if (debugCache)
	[self logWithFormat:@"different password than cached entry: %@", _url];
      entry = nil;
    }
    else
      [self debugWithFormat:@"no connection cached yet for url: %@", _url];
  
    /* try to login */
  
    client = [entry isValidPassword:_p]
      ? [entry client]
      : [self imap4ClientForURL:_url password:_p];
    
    if (client == nil)
      return nil;
  
  /* sideeffect of -imap4ClientForURL:password: is to create a cache entry */
    return [self entryForURL:_url];
  }
}

/* client object */

- (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd {
  // TODO: move to some global IMAP4 connection pool manager
  NGImap4Connection *entry;
  NGImap4Client *client;
  NSDictionary  *result;
  
  if (_url == nil)
    return nil;

  /* check connection pool */
  
  if ((entry = [self entryForURL:_url]) != nil) {
    if ([entry isValidPassword:_pwd]) {
      [self debugWithFormat:@"reused IMAP4 connection for URL: %@", _url];
      return [entry client];
    }
    
    /* different password, password could have changed! */
    entry = nil;
  }
  
  /* setup connection and attempt login */
  
  if ((client = [NGImap4Client clientWithURL:_url]) == nil)
    return nil;
  
  if (AuthMechanism)
    result = [client authenticate:[_url user] password:_pwd
                        mechanism:AuthMechanism];
  else
    result = [client login:[_url user] password:_pwd];
  if (![[result valueForKey:@"result"] boolValue]) {
    [self errorWithFormat:
            @"IMAP4 login failed:\n"
	    @"  host=%@, user=%@, pwd=%s\n"
	    @"  url=%@\n  base=%@\n  base-class=%@)\n"
	    @"  = %@", 
            [_url host], [_url user], [_pwd length] > 0 ? "yes" : "no", 
	    [_url absoluteString],
	    [_url baseURL],
            NSStringFromClass([[_url baseURL] class]),
            client];
    return nil;
  }
  
  [self debugWithFormat:@"created new IMAP4 connection for URL: %@", _url];
  
  /* cache connection in pool */
  
  entry = [[NGImap4Connection alloc] initWithClient:client 
					   password:_pwd];
  [self cacheEntry:entry forURL:_url];
  [entry release]; entry = nil;
  
  return client;
}

- (void)flushCachesForURL:(NSURL *)_url {
  NGImap4Connection *entry;
  
  if ((entry = [self entryForURL:_url]) == nil) /* nothing cached */
    return;
  
  [entry flushFolderHierarchyCache];
  [entry flushMailCaches];
}

/* debugging */

- (BOOL)isDebuggingEnabled {
  return debugOn;
}

@end /* NGImap4ConnectionManager */
