/*
  Copyright (C) 2004 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.
*/
// $Id$

#include "OCSChannelManager.h"
#include "NSURL+OCS.h"
#include <GDLAccess/EOAdaptor.h>
#include <GDLAccess/EOAdaptorContext.h>
#include <GDLAccess/EOAdaptorChannel.h>
#include "common.h"

/*
  TODO:
  - implemented pooling
  - auto-close channels which are very old?! 
    (eg missing release due to an exception)
*/

@interface OCSChannelHandle : NSObject
{
@public
  EOAdaptorChannel *channel;
  NSDate           *creationTime;
  NSDate           *lastReleaseTime;
  NSDate           *lastAcquireTime;
}

@end

@implementation OCSChannelManager

static BOOL debugOn = YES;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  
  debugOn = [ud boolForKey:@"OCSChannelManagerDebugEnabled"];
}

+ (NSString *)adaptorNameForURLScheme:(NSString *)_scheme {
  // TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL72
  return @"PostgreSQL72";
}

+ (id)defaultChannelManager {
  static OCSChannelManager *cm = nil;
  if (cm == nil)
    cm = [[self alloc] init];
  return cm;
}

- (id)init {
  if ((self = [super init])) {
    self->urlToAdaptor = [[NSMutableDictionary alloc] initWithCapacity:4];
  }
  return self;
}

- (void)dealloc {
  [self->urlToAdaptor release];
  [super dealloc];
}

/* DB key */

- (NSString *)databaseKeyForURL:(NSURL *)_url {
  /*
    We need to build a proper key that omits passwords and URL path components
    which are not required.
  */
  NSString *key;
  
  key = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
		  [_url host], [_url port],
		  [_url user], [_url ocsDatabaseName]];
  return key;
}

/* adaptors */

- (NSDictionary *)connectionDictionaryForURL:(NSURL *)_url {
  NSMutableDictionary *md;
  id tmp;
  
  md = [NSMutableDictionary dictionaryWithCapacity:4];

  if ((tmp = [_url host]) != nil) 
    [md setObject:tmp forKey:@"hostName"];
  if ((tmp = [_url port]) != nil) 
    [md setObject:tmp forKey:@"port"];
  if ((tmp = [_url user]) != nil) 
    [md setObject:tmp forKey:@"userName"];
  if ((tmp = [_url password]) != nil) 
    [md setObject:tmp forKey:@"password"];
  
  if ((tmp = [_url ocsDatabaseName]) != nil) 
    [md setObject:tmp forKey:@"databaseName"];
  
  [self debugWithFormat:@"build connection dictionary for URL %@: %@", 
	[_url absoluteString], md];
  return md;
}

- (EOAdaptor *)adaptorForURL:(NSURL *)_url {
  EOAdaptor    *adaptor;
  NSString     *adaptorName;
  NSString     *key;
  NSDictionary *condict;
  
  if (_url == nil)
    return nil;
  if ((key = [self databaseKeyForURL:_url]) == nil)
    return nil;
  if ((adaptor = [self->urlToAdaptor objectForKey:key]) != nil) {
    [self debugWithFormat:@"using cached adaptor: %@", adaptor];
    return adaptor; /* cached :-) */
  }
  
  [self debugWithFormat:@"creating new adaptor for URL: %@", _url];
  
  adaptorName = [[self class] adaptorNameForURLScheme:[_url scheme]];
  if ([adaptorName length] == 0) {
    [self logWithFormat:@"ERROR: cannot handle URL: %@", _url];
    return nil;
  }
  
  condict = [self connectionDictionaryForURL:_url];
  
  if ((adaptor = [EOAdaptor adaptorWithName:adaptorName]) == nil) {
    [self logWithFormat:@"ERROR: did not find adaptor '%@' for URL: %@", 
	    adaptorName, _url];
    return nil;
  }
  
  [adaptor setConnectionDictionary:condict];
  [self->urlToAdaptor setObject:adaptor forKey:key];
  return adaptor;
}

/* channels */

- (EOAdaptorChannel *)_createChannelForURL:(NSURL *)_url {
  EOAdaptor        *adaptor;
  EOAdaptorContext *adContext;
  EOAdaptorChannel *adChannel;
  
  if ((adaptor = [self adaptorForURL:_url]) == nil)
    return nil;
  
  if ((adContext = [adaptor createAdaptorContext]) == nil) {
    [self logWithFormat:@"ERROR: could not create adaptor context!"];
    return nil;
  }
  if ((adChannel = [adContext createAdaptorChannel]) == nil) {
    [self logWithFormat:@"ERROR: could not create adaptor channel!"];
    return nil;
  }
  return adChannel;
}

- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url {
  // TODO: naive implementation, add pooling!
  EOAdaptorChannel *channel;
  
  if ((channel = [self _createChannelForURL:_url]) == nil)
    return channel;
  
  if ([channel isOpen])
    return [channel retain];
  
  if (![channel openChannel]) {
    [self logWithFormat:@"could not open channel %@ for URL: %@",
	    channel, _url];
    return nil;
  }
  
  return [channel retain];
}
- (void)releaseChannel:(EOAdaptorChannel *)_channel {
  // TODO: naive implementation, add pooling!
  if ([_channel isOpen])
    [_channel closeChannel];
  
  [_channel release];
}

/* checking for tables */

- (BOOL)canConnect:(NSURL *)_url {
  /* 
     this can check for DB connect as well as for table URLs (whether a table
     exists)
  */
  EOAdaptorChannel *channel;
  NSString *table;
  BOOL     result;
  
  if ((channel = [self acquireOpenChannelForURL:_url]) == nil) {
    if (debugOn) [self debugWithFormat:@"could not acquire channel: %@", _url];
    return NO;
  }
  if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
  result = YES; /* could open channel */
  
  /* check whether table exists */
  
  table = [_url ocsTableName];
  if ([table length] > 0)
    result = [channel tableExistsWithName:table];
  
  /* release channel */
  
  [self releaseChannel:channel]; channel = nil;
  
  return result;
}

/* debugging */

- (BOOL)isDebuggingEnabled {
  return debugOn;
}

/* description */

- (NSString *)description {
  NSMutableString *ms;

  ms = [NSMutableString stringWithCapacity:256];
  [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
  
  [ms appendFormat:@" #adaptors=%d", [self->urlToAdaptor count]];
  
  [ms appendString:@">"];
  return ms;
}

@end /* OCSChannelManager */

@implementation OCSChannelHandle

- (void)dealloc {
  [self->channel         release];
  [self->creationTime    release];
  [self->lastReleaseTime release];
  [self->lastAcquireTime release];
  [super dealloc];
}

/* accessors */

- (EOAdaptorChannel *)channel {
  return self->channel;
}

/* description */

- (NSString *)description {
  NSMutableString *ms;

  ms = [NSMutableString stringWithCapacity:256];
  [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
  
  [ms appendFormat:@" channel=0x%08X", self->channel];
  if (self->creationTime) [ms appendFormat:@" created=%@", self->creationTime];
  if (self->lastReleaseTime) 
    [ms appendFormat:@" last-released=%@", self->lastReleaseTime];
  if (self->lastAcquireTime) 
    [ms appendFormat:@" last-acquired=%@", self->lastAcquireTime];
  
  [ms appendString:@">"];
  return ms;
}

@end /* OCSChannelHandle */
