/******************************************************************************
  @header FTServer
  @abstract Base class for all FT classes
 
  @availability OS X, GNUstep
  @copyright 2004, 2005, 2006 Free Software Foundation, Inc.
  
  This library 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.1 of the License, or (at your option) any later version.

  This library 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 this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

  <pre>
  -------------------------------------------------------------------------
  Modification history

  30.01.2005 ola     initial version
  23.08.2006 ola     license changed
  -------------------------------------------------------------------------
  </pre>
******************************************************************************/

#include <Encore/Encore.h>
#include <FT/FTDefines.h>
#include <FTServerImpl.h>
#include <FT/FTGenericTransactionOptimizer.h>
#include <FT/FTDefaultServiceManagerImpl.h>
#include <FT/FTLogging.h>


/**
 * Mutex used for synchronization within server impl.
 */
static NSLock *_ftserverimpl_lock = nil;


@implementation FTServerImpl

+ initialize {
  _ftserverimpl_lock = [[NSLock alloc] init];
  
  return self;
}


- initWithConfig: (FTConfig *) theConfig {
  self = [super init];
  self->server_state = FT_SERVER_STATE_STARTED;
  self->notificationCenter = [[NSNotificationCenter alloc] init];
  self->config = [theConfig retain];
  self->sessionManager = nil;
  self->nodeidToProviderManagerDB = nil;
  self->graphIdToGraphDB = nil;
  self->graphManager = nil;
  self->defaultDictionaryProviderDB = nil;
  self->systemDictionary = nil;
  self->transactionManager = nil;
  
  self->serviceManager = [[FTDefaultServiceManagerImpl alloc] 
    initWithServer: self];
  
  return self;
}


- (void) dealloc {
  if( FT_SERVER_STATE_MOUNTED == self->server_state ) {
    NS_DURING
      [self unmount];
	NS_HANDLER
      [[FTLogging coreLog]
        error: @"FTServerImpl::dealloc: unmounting all databases FAILED!" ];
	NS_ENDHANDLER
  }
  
  NS_DURING
    [self->serviceManager release];
  NS_HANDLER
    [[FTLogging coreLog]
      error: @"FTServerImpl::dealloc: ERROR while releasing service manager!" ];
  NS_ENDHANDLER
  
  [self->notificationCenter release]; 
  if( nil != self->config ) {
    [self->config release];
  }

  self->server_state = FT_SERVER_STATE_UNKNOWN;
  
  [super dealloc];
}


- addOberver: (id) anObserver selector: (SEL) aSelector {
  [self->notificationCenter addObserver: anObserver selector: aSelector
    name: [FTServerNotification notificationName] object: nil];
  return self;
}


- (NSString *) baseDataDir {
  return [self->config baseDataDir];
}


- checkServerState: (__ft_req_operation_t) reqOperation {
  if( reqOperation & __FT_REQ_MOUNT_DATABASES ) {
    if( !(FT_SERVER_STATE_STARTED & self->server_state) ) {
      [[FTLogging coreLog]
        error: @"FTServerImpl::checkServerState: Server is not in the "\
	      "state FT_SERVER_STATE_STARTED!" ];
	  [[[ECIllegalStateException alloc] 
	    initWithIllegalStateInfo: 
	      @"FTServerImpl::checkServerState: Server is not in the "\
		    "state FT_SERVER_STATE_STARTED!" ] raise];
    }
  }
  
  if( reqOperation & __FT_REQ_DATA_ACCESS ) {
    /* 
	 * Check wether all databases are mounted
	 */
	if( !(FT_SERVER_STATE_MOUNTED & self->server_state) ) {
	  [[[ECIllegalStateException alloc] initWithIllegalStateInfo:
	    @"FTServerImpl::checkServerState: Data access not allowed since "\
		  "databases are not mounted" ] raise];
	}
  }
  
  if( reqOperation & __FT_REQ_UNMOUNT_DATABASES ) {
    /**
	 * Are the databases mount?
	 */
	if( !(FT_SERVER_STATE_MOUNTED & self->server_state) ) {
	  [[[ECIllegalStateException alloc] initWithIllegalStateInfo:
	    @"FTServerImpl::checkServerState: Umount not possible since "\
		  "databases are not mounted" ] raise];
	}
  }
  return self;
}


- (FTConfig *) config {
  return self->config;
}


- (NSString *) constructDatabaseFilename: (NSString *) databaseName {
  NSMutableString *filename = nil;
  
  if( nil == [self baseDataDir] ) {
    [[[ECIncompleteInitializationException alloc] 
	  initWithReason: @"Base data directory not set"] raise];
  }
  
  if( nil == databaseName ) {
    [[[ECIllegalArgumentException alloc]
	  initWithArgumentInfo: @"Argument databaseName equals nil"] raise];
  }
  
  filename = [[NSMutableString alloc] initWithString: [self baseDataDir] ];
  [filename appendString: @"/"];
  [filename appendString: databaseName ];
	
  return filename;
}


- (BDBDatabase *) createDatabaseWithName: (NSString *) databaseName
  withConfig: (BDBDatabaseConfig *) aConfig {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSFileManager *fileMgr = [NSFileManager defaultManager];
  NSMutableString *filename;
  BDBDatabase *toReturn = nil;
  
  filename = [[self constructDatabaseFilename: databaseName] autorelease]; 

  if( [fileMgr fileExistsAtPath: filename] ) {
    NSMutableString *info = [[NSMutableString alloc]
	  initWithFormat: @"Database named %@ already exists", filename];
	[pool release];
    [[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: info] raise];
  }
  
  toReturn = [BDBDatabase initWithFilename: filename
    databaseName: nil databaseConfig: aConfig];

  [pool release];
  return toReturn;
}


- (BDBDatabaseConfig *) dbConfigForDefaultDictionaryProvider {
  BDBDatabaseConfig * dbConfig = [[BDBDatabaseConfig alloc] init];

  [dbConfig setDatabaseType: BDB_BTREE];
  [dbConfig setBTreeRecordNumbering: YES];
  [dbConfig setAllowDuplicates: NO];
  
  return dbConfig;
}


- (BDBDatabaseConfig *) dbConfigForGraphIdToGraphDatabase {
  BDBDatabaseConfig * dbConfig = [[BDBDatabaseConfig alloc] init];

  [dbConfig setDatabaseType: BDB_BTREE];
  [dbConfig setBTreeRecordNumbering: YES];
  [dbConfig setAllowDuplicates: NO];
  
  return dbConfig;
}


- (BDBDatabaseConfig *) dbConfigForNodeIdToProviderManager {
  BDBDatabaseConfig * dbConfig = [[BDBDatabaseConfig alloc] init];

  [dbConfig setDatabaseType: BDB_BTREE];
  [dbConfig setBTreeRecordNumbering: YES];
  [dbConfig setAllowDuplicates: NO];
  
  return dbConfig;
}


- (id <FTDictionaryProvider>) defaultDictionaryProvider {
  return self->defaultDictionaryProvider;
}


- (id <FTObjectToIdMapper>) defaultObjectToIdMapper {
  return self->defaultObjectToIdMapper;
}


- (FTGraphManagerImpl *) graphManager {
  return self->graphManager;
}


- (BOOL) databasesMounted {
  return FT_SERVER_STATE_MOUNTED == self->server_state;
}


- mountDatabases {
  NSString *dbName;
  BDBDatabaseConfig *dbConfig;
  EC_AUTORELEASEPOOL_BEGIN
  
  [self checkServerState: __FT_REQ_MOUNT_DATABASES];
  
  if( [[FTLogging coreLog] isInfoEnabled] ) {    
    [[FTLogging coreLog]
      info: @"Opening database connections...." ];
  }
  
  NS_DURING
    [_ftserverimpl_lock lock];
    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Database: DefaultObjectToIdMapper...." ];
    }
    dbName = [self nameOfDefaultObjectToIdMapperDatabase];
	
    defaultObjectToIdMapper = [[FTDefaultObjectToIdMapper alloc]
	  initWithDatabaseName: [self constructDatabaseFilename: dbName]
	  forServer: self];
	  
    [defaultObjectToIdMapper mountDatabase];
	
	if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Database: GraphIdToGraphDatabase...." ];
    }
    
	dbName = [self nameOfGraphIdToGraphDatabase];
	dbConfig = [[self dbConfigForGraphIdToGraphDatabase] autorelease];
    [dbConfig setAllowCreate: NO];

    self->graphIdToGraphDB 
	  = [self openDatabaseWithName: dbName withConfig: dbConfig];

    self->graphManager = [[FTGraphManagerImpl alloc] 
	  initForServer: self withGraphIdToGraphDatabase: self->graphIdToGraphDB ];

    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        error: @"Database: NodeIdToProviderManager...." ];
    }
    
	dbName = [self nameOfNodeIdToProviderManagerDatabase];

	dbConfig = [[self dbConfigForNodeIdToProviderManager] autorelease];
    [dbConfig setAllowCreate: YES];

    self->nodeidToProviderManagerDB = 
	  [self openDatabaseWithName: dbName withConfig: dbConfig];
	  
	if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Database: DefaultDictionaryProvider...." ];
    }
    
	dbName = [self nameOfDefaultDictionaryProviderDatabase];
	dbConfig = [[self dbConfigForDefaultDictionaryProvider] autorelease];
	
	self->defaultDictionaryProviderDB =
	  [self openDatabaseWithName: dbName withConfig: dbConfig];

    self->defaultDictionaryProvider = [[FTGenericDictionaryProviderImpl alloc]
	  initWithDatabase: self->defaultDictionaryProviderDB ];
	  
	if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Reading system dictionary...." ];
    }
    
	self->systemDictionary = [[FTSystemDictionary alloc] 
	  initWithDictionaryProvider: self->defaultDictionaryProvider
          forServer: self];
	  
	if( [[FTLogging coreLog] isInfoEnabled] ) {    
      [[FTLogging coreLog]
        info: @"Connected to all databases." ];
    }

  NS_HANDLER
    [_ftserverimpl_lock unlock];
    EC_AUTORELEASEPOOL_RELEASE;
    [localException raise];
  NS_ENDHANDLER
  
  [_ftserverimpl_lock unlock];
  
  self->sessionManager = [[FTSessionManagerImpl alloc] initForServer: self];
  self->transactionManager = [[FTTransactionManagerImpl alloc] init];
  [self->transactionManager 
    addTransactionOptimizer: [[FTGenericTransactionOptimizer alloc] init]
    withPriority: 0];
    
  self->server_state = FT_SERVER_STATE_MOUNTED;
  
  if( [[FTLogging coreLog] isInfoEnabled] ) {    
    [[FTLogging coreLog]
      info: @"FTServerImpl::mountDatabases: Now switching services ONLINE..." ];
  }
  
  [self->serviceManager switchAllServicesToMode: FT_SERVICE_MODE_ONLINE];
  
  EC_AUTORELEASEPOOL_END
  return self;
}


- (NSString *) nameOfGraphIdToGraphDatabase {
  return [[self->config databaseNames]
    databaseNameForEntry: FT_IP_GRAPHID_2_GRAPH_DBNAME];
}


- (NSString *) nameOfNodeIdToProviderManagerDatabase {
  return [[self->config databaseNames]
    databaseNameForEntry: FT_IP_NODEID_2_PROVIDERMGR_DBNAME];
}


- (NSString *) nameOfDefaultDictionaryProviderDatabase {
  return [[self->config databaseNames]
    databaseNameForEntry: FT_IP_DEFAULT_DICTIONARYPROVIDER_DBNAME];
}


- (NSString *) nameOfDefaultObjectToIdMapperDatabase {
  return [[self->config databaseNames]
    databaseNameForEntry: FT_IP_DefaultObject_2_IDMAPPER_DBNAME];
}



- (BDBDatabase *) openDatabaseWithName: (NSString *) databaseName
  withConfig: (BDBDatabaseConfig *) aConfig {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSFileManager *fileMgr = [NSFileManager defaultManager];
  NSMutableString *filename;
  BDBDatabase *toReturn = nil;
  
  filename = [[self constructDatabaseFilename: databaseName] autorelease]; 

  if( ![fileMgr fileExistsAtPath: filename] ) {
    NSMutableString *info = [[NSMutableString alloc]
	  initWithFormat: @"Database named %@ does not exist", filename];
	[pool release];
    [[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: info] raise];
  }
  
  toReturn = [BDBDatabase initWithFilename: filename
    databaseName: nil databaseConfig: aConfig];

  [pool release];
  return toReturn;
}


- (id <FTSessionManager>) sessionManager {
  
  return self->sessionManager;
}


- (id <FTServiceManager>) serviceManager {
  return self->serviceManager;
}

- setupDatabases {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  [self checkServerState: __FT_REQ_MOUNT_DATABASES];
  
  NS_DURING
    NSString *dbName;
	BDBDatabaseConfig *dbConfig;
	
	[_ftserverimpl_lock lock];
	/**
	 */
	if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Creating databases....\n"\
        "Database: DefaultObjectToIdMapper...." ];
    }
    
	dbName = [self nameOfDefaultObjectToIdMapperDatabase];
	
	defaultObjectToIdMapper = [[FTDefaultObjectToIdMapper alloc]
	  initWithDatabaseName: [self constructDatabaseFilename: dbName]
	  forServer: self];
	  
    [defaultObjectToIdMapper setupDatabase];
	
	if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Database: GraphIdToGraphDatabase...." ];
    }
	dbName = [self nameOfGraphIdToGraphDatabase];
	dbConfig = [[self dbConfigForGraphIdToGraphDatabase] autorelease];
    [dbConfig setAllowCreate: YES];

    self->graphIdToGraphDB 
	  = [self createDatabaseWithName: dbName withConfig: dbConfig];

    self->graphManager = [[FTGraphManagerImpl alloc] 
	  initForServer: self withGraphIdToGraphDatabase: self->graphIdToGraphDB ];
	  
    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug:  @"Database: NodeIdToProviderManager...." ];
    }
	dbName = [self nameOfNodeIdToProviderManagerDatabase];

	dbConfig = [[self dbConfigForNodeIdToProviderManager] autorelease];
    [dbConfig setAllowCreate: YES];

    self->nodeidToProviderManagerDB = 
	  [self createDatabaseWithName: dbName withConfig: dbConfig];
	  
	if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Database: DefaultDictionaryProvider...." ];
    }
    
	dbName = [self nameOfDefaultDictionaryProviderDatabase];
	dbConfig = [[self dbConfigForDefaultDictionaryProvider] autorelease];
	[dbConfig setAllowCreate: YES];
	
	self->defaultDictionaryProviderDB =
	  [self createDatabaseWithName: dbName withConfig: dbConfig];
	self->defaultDictionaryProvider = [[FTGenericDictionaryProviderImpl alloc]
	  initWithDatabase: self->defaultDictionaryProviderDB ];
	
	if( [[FTLogging coreLog] isInfoEnabled] ) {    
      [[FTLogging coreLog]
        info: @"Setting up system dictionary..." ];
    }
    
	self->systemDictionary = [[FTSystemDictionary alloc] 
	  initWithDictionaryProvider: self->defaultDictionaryProvider
          forServer: self];
	[self->systemDictionary setup];
	
	if( [[FTLogging coreLog] isInfoEnabled] ) {    
      [[FTLogging coreLog]
        info: @"Databases successfully created!" ];
    }
    
  NS_HANDLER
    [_ftserverimpl_lock unlock]; 
	[pool release]; 
	[localException raise];
  NS_ENDHANDLER
  self->sessionManager = [[FTSessionManagerImpl alloc] initForServer: self];
  self->transactionManager = [[FTTransactionManagerImpl alloc] init];
  [self->transactionManager 
    addTransactionOptimizer: [[FTGenericTransactionOptimizer alloc] init]
    withPriority: 0];
    
  self->server_state = FT_SERVER_STATE_MOUNTED;
  [_ftserverimpl_lock unlock];
  
  
  if( [[FTLogging coreLog] isInfoEnabled] ) {    
    [[FTLogging coreLog]
      info: @"FTServerImpl::setupDatabases: Now switching services ONLINE..." ];
  }
  
  [self->serviceManager switchAllServicesToMode: FT_SERVICE_MODE_ONLINE];
  
  [pool release];
  
  return self;
}


- mountDatabasesByAdminSession: (id <FTAdministrationSession>) adminSession {
  [self mountDatabases];
  [self->sessionManager reconnectSession: adminSession];
  
  return self;
}


- shutdown {
  if( [[FTLogging coreLog] isInfoEnabled] ) {    
    [[FTLogging coreLog]
      info: @"FTServerImpl::shutdown: Shutting down server..." ];
  }
  
  [self checkServerState: __FT_REQ_UNMOUNT_DATABASES];

  // TODO 1: Kill all sessions
  /**
   * TODO 2: At present we use a very simple mechanism here: We simply close
   *   all databases and inform the related instances about it. In future
   *   we better introduce proxy objects to be hand-off to the client instead
   *   of the real objects.
   */
  // informing all listeners first:
  
  [FTServerNotification sendShutdownNotificationTo: self->notificationCenter ];
  
  return [self unmount];
}


- unmount {
  if( [[FTLogging coreLog] isInfoEnabled] ) {    
    [[FTLogging coreLog]
      info: @"Unmounting databases...\n"\
        "FTServerImpl::umount: Switching all services to mode OFFLINE..." ];
  }
  
  [self->serviceManager switchAllServicesToMode: FT_SERVICE_MODE_SHUTDOWN];
  /**
   * TODO: better handling for the case where closing a database fails!
   */
  NS_DURING
    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Closing graphIdToGraphDB..." ];
    }
	[self->graphManager release];
	self->graphManager = nil;

    [self->graphIdToGraphDB close];
	[self->graphIdToGraphDB release];
	self->graphIdToGraphDB = nil;

  NS_HANDLER
    [[FTLogging coreLog]
      error:@"FTServerImpl::unmount: Closing database graphIdToGraphDB FAILED!"\
        " Exception: %@", localException ];
  NS_ENDHANDLER

  NS_DURING
    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Closing nodeidToProviderManagerDB..." ];
    }
    
    [self->nodeidToProviderManagerDB close];
	[self->nodeidToProviderManagerDB release];
	self->nodeidToProviderManagerDB = nil;
  NS_HANDLER
    [[FTLogging coreLog]
      error: @"FTServerImpl::unmount: Closing database "\
	  "nodeidToProviderManagerDB FAILED! Exception: %@", localException ];
  NS_ENDHANDLER

  NS_DURING
    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Closing defaultDictionaryProviderDB..." ];
    }
    
	[self->systemDictionary release];
	self->systemDictionary = nil;

    [self->defaultDictionaryProvider release];
	self->defaultDictionaryProvider = nil;
	
    [self->defaultDictionaryProviderDB close];
	[self->defaultDictionaryProviderDB release];
	self->defaultDictionaryProviderDB = nil;
  NS_HANDLER
    [[FTLogging coreLog]
      error: @"FTServerImpl::unmount: Closing database "\
	    "defaultDictionaryProviderDB FAILED! Exception: %@", localException ];
  NS_ENDHANDLER

  NS_DURING
    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"Closing defaultObjectToIdMapperDB..." ];
    }
	[self->defaultObjectToIdMapper unmountDatabase];
	[self->defaultObjectToIdMapper release];
	self->defaultObjectToIdMapper = nil;
  NS_HANDLER
    [[FTLogging coreLog]
      error: @"FTServerImpl::unmount: Closing database "\
        "defaultObjectToIdMapperDB FAILED! Exception: %@", localException ];
  NS_ENDHANDLER

  [self->sessionManager release];
  self->sessionManager = nil;
  
  [self->transactionManager release];
  self->transactionManager = nil;
  if( [[FTLogging coreLog] isInfoEnabled] ) {    
    [[FTLogging coreLog]
      info: @"All datbases have been unmounted..." ];
  }
  self->server_state = FT_SERVER_STATE_STARTED;

  return self;
}


- (FTSystemDictionary *) systemDictionary {
  return self->systemDictionary;
}


- (id <FTTransactionManager>) transactionManager {
  return transactionManager;
}
@end


// ------------------------------------------------------- FTServerNotification
@implementation FTServerNotification

+ (void) sendShutdownNotificationTo: (NSNotificationCenter *) center {
  FTServerNotification *n = [[FTServerNotification alloc] 
	  initWithServerActionHint: FT_SERVER_ACTION_SHUTDOWN ];
	  
  NS_DURING
    [ center postNotificationName: [self notificationName]
      object: n ];
  NS_HANDLER
    [n release];
    [localException raise];
  NS_ENDHANDLER
}


+ (NSString *) notificationName {
  return @"FTServerNotification";
}


- initWithServerActionHint: (ft_server_action_hint_t) actionHint; {
  self = [super init];
  self->serverActionHint = actionHint;
  
  return self;
}


- (ft_server_action_hint_t) serverActionHint {
  return self->serverActionHint;
}
@end

