/*!
  @header FTGraphManagerImpl
  @abstract Module of FT
 
  @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

  23.02.05 ola     initial version
  19.08.06 ola     removeGraphWithId added
  23.08.06 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include <Encore/Encore.h>
#include <FT/FTExceptions.h>
#include <FT/FTGraphManagerImpl.h>
#include <FT/FTServerImpl.h>
#include <FT/FTGraphImpl.h>
#include <FT/FTLogging.h>

@implementation FTGraphManagerImpl
- initForServer: (FTServerImpl *) theServer
  withGraphIdToGraphDatabase: (BDBDatabase *) theDatabase {
  self = [super init];
  
  self->lock = [[NSLock alloc] init];
  self->server = [theServer retain];
  self->graphIdToGraphDatabase = [theDatabase retain];
  self->graphIdToGraphCache = [[ECCache alloc] init];
  
  return self;
}


- (void) dealloc {
  [self->graphIdToGraphCache release];
  [self->lock release];
  [self->server release];
  [self->graphIdToGraphDatabase release];
  
  [super dealloc];
}


- (id <ECIterator>) allGraphIds {
  id <ECIterator> toReturn = nil;

  EC_AUTORELEASEPOOL_BEGIN
  NSMutableArray *graphIds = [[[NSMutableArray alloc] init] autorelease];
  
  int i = 1;

  while( 1 ) {
    BDBDatabaseRecordNumber *recno 
      = [[[BDBDatabaseRecordNumber alloc]
          initWithEntryNumber: i] autorelease];
    BDBDatabaseEntry *value = [[[BDBDatabaseEntry alloc]
      init] autorelease];
    BDBOperationStatus operationStatus;

    if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"FTGraphManagerImpl::allGraphIds: "\
          "trying to access record nr: %d", i ];
    }
    
    operationStatus = [self->graphIdToGraphDatabase
      getEntryWithTransaction:nil
      recordNumber:recno data: value];
    
    if( operationStatus != BDB_STATUS_SUCCESS ) {
      if( BDB_STATUS_NOTFOUND != operationStatus ) {
        [[FTLogging coreLog]
          error: @"FTGraphManagerImpl::allGraphIds: Unable to get graph id "\
            "stored at bdb database record nr=%d", i];
          
        [[[FTInternalDatamanagementException alloc]
          initWithOperationStatus: operationStatus]
          raise];
      }      
      break;              
    } else {
      [graphIds addObject: [[value object] graphId]];
    }

    i++;
  }
  

  toReturn = [[ECArrayIterator alloc]
    initWithArray: graphIds];

  EC_AUTORELEASEPOOL_END

  return [toReturn autorelease];
}


- (id <FTGraph>) createGraphWithId: (id <FTId> ) graphId {
  FTGraphImpl *toReturn = nil;
  BDBOperationStatus operationStatus = BDB_STATUS_UNKNOWN;

  [self->lock lock];

  EC_AUTORELEASEPOOL_BEGIN

  /* look in the cache first: */
  toReturn = (FTGraphImpl *) [self->graphIdToGraphCache objectForKey: graphId
    incrementRefCounter: YES];
  
  if( nil == toReturn ) {
    NS_DURING
      toReturn = [self lookupGraphWithId: graphId];
      if( nil != toReturn ) {
        /*
	     * graph with the given id already exists, so:
	     */
	    [[[ECIllegalArgumentException alloc]
	      initWithArgumentInfo: @"A graph with the same identifier already "\
	        "exits!" ] raise];
      }
	
  	  NS_DURING
	    BDBDatabaseEntry *key, *value;

	    key = [[[BDBDatabaseEntry alloc] initWithObject: graphId] autorelease];
	    toReturn = [[FTGraphImpl alloc] initForServer: self->server
	      graphManager: self withId: graphId ];

		// create the graph database:
		[toReturn setupDatabases];
		
	    value = [[[BDBDatabaseEntry alloc] initWithObject: toReturn]
	      autorelease];
	    operationStatus = [self->graphIdToGraphDatabase 
	      putEntryWithTransaction: nil key: key value: value];

      NS_HANDLER
        if( [localException isKindOfClass: [BDBException class]] ) {
          BDBException *e = (BDBException *) localException;
          
	       FTInternalDatamanagementException *exception =
            [[FTInternalDatamanagementException alloc] 
  	          initWithBDBException: e ];
           [exception setCause: e];
	       [exception raise];
        } else {
	     [[[[FTUnknownException alloc] initWithContextInfo:
           @"Fetching FTGraphManagerImpl::createGraphWithId: adding a graph "\
	          "instance to the database" exception: localException]
              setCause: localException]
           raise];
        }
	  NS_ENDHANDLER
	
	  if( BDB_STATUS_SUCCESS != operationStatus ) {
	    [[[FTInternalDatamanagementException alloc] 
	      initWithOperationStatus: operationStatus ] raise];
	  } else {
	    [self->graphIdToGraphCache addObject: toReturn forKey: graphId];
	  }
    NS_HANDLER
      [self->lock unlock];
      [localException raise];
    NS_ENDHANDLER
  }
  
  [self->lock unlock];
  EC_AUTORELEASEPOOL_END
  
  return toReturn;
}


- (id <FTGraph>) graphWithId: (id <FTId>) graphId {
  FTGraphImpl *toReturn = nil;
  
  EC_AUTORELEASEPOOL_BEGIN
  
  [self->lock lock];
  
  toReturn = (FTGraphImpl *) [self->graphIdToGraphCache 
   objectForKey: graphId
   incrementRefCounter: YES];

  if( nil == toReturn ) {
    /**
     * cache lookup without success
     */
    NS_DURING
	  toReturn = [self lookupGraphWithId: graphId ];
	  if( nil != toReturn ) {      
        [toReturn mountDatabases];
        [self->graphIdToGraphCache addObject: toReturn forKey: graphId];
      }
    NS_HANDLER
      [self->lock unlock];
      [localException raise];
    NS_ENDHANDLER
  } else {
    /**
     * instance found in cache. databases mounted?
     */
    if( ![toReturn databasesMounted] ) {
      NS_DURING
          [toReturn mountDatabases];
      NS_HANDLER
        [[FTLogging coreLog]
          error: @"FTGraphManagerImpl::graphWithId: Exception while mounting "\
          "databases of graph instance..." ];
        
        [toReturn release];

        [self->lock unlock];

        [localException raise];
        
        NS_ENDHANDLER
      }
	}
  
  /**
   * TODO: What to do here if the graph instance is returned, a thread-switch 
   * occurs which releases this graph instance and the instance will be removed?
   * In this case the caller of this method will receive an illegal reference
   * before it is able to call a "retain".
   *
   * Solution: Call retain in this method
   */
  [self->lock unlock];
  EC_AUTORELEASEPOOL_END
  
  return toReturn;
}


- (FTGraphImpl *) lookupGraphWithId: (id <FTId>) graphId {
  FTGraphImpl *toReturn = nil;

  EC_AUTORELEASEPOOL_BEGIN
  
  BDBDatabaseEntry *key, *value;
  BDBOperationStatus operationStatus = BDB_STATUS_UNKNOWN;

  key = [[[BDBDatabaseEntry alloc] initWithObject: graphId] autorelease];
  value = [[[BDBDatabaseEntry alloc] init] autorelease];

  NS_DURING
    operationStatus = [self->graphIdToGraphDatabase 
      getEntryWithTransaction: nil key: key data: value ];
  NS_HANDLER
    if( [localException isKindOfClass: [BDBException class]] ) {
      BDBException *e = (BDBException *) localException;
      FTInternalDatamanagementException *exception =
        [[FTInternalDatamanagementException alloc] 
  	      initWithBDBException: e ];
      [exception setCause: localException];
	  [exception raise];
    } else {
    // got unknown exception
      [[[[FTUnknownException alloc] initWithContextInfo:
        @"Fetching FTGraphManagerImpl::lookupGraphWithId: a graph from the "\
	    "database" exception: localException]
        setCause: localException]
        raise];
    }
  NS_ENDHANDLER

  if( BDB_STATUS_SUCCESS == operationStatus ) {
    // got datum
    toReturn = (FTGraphImpl *) [[value object] retain];
    [toReturn initAfterDecodeForServer: self->server graphManager: self];
  } 
  
  EC_AUTORELEASEPOOL_END
  return [toReturn autorelease];
}


- (void) releaseGraph: (FTGraphImpl *) item {
  [self->graphIdToGraphCache decrementRefCounterForKey: [item graphId]];
}


- removeGraphWithId: (id <FTId>) graphId {
  FTGraphImpl *graph;
  BOOL successful = NO;
  
  if( [[FTLogging coreLog] isTraceEnabled] ) {
    [[FTLogging coreLog]
      trace: @"FTGraphManagerImpl::removeGraphWithId: Remove graph with id="\
        "%@", graphId];
  }
  
  graph = [self lookupGraphWithId: graphId];

  successful = [graph remove];
  
  [self->graphIdToGraphCache removeObjectForKey: graphId];
  
  NS_DURING
    BDBDatabaseEntry *key;
    BDBOperationStatus operationStatus;
    
    key = [[[BDBDatabaseEntry alloc] initWithObject: graphId] autorelease];
    operationStatus = [self->graphIdToGraphDatabase 
      deleteEntryWithTransaction: nil
      key: key];
    if( BDB_STATUS_SUCCESS != operationStatus ) {
      [[FTLogging coreLog]
        error: @"FTGraphManagerImpl::removeGraphWithId: Unable to remove "\
          "graphId=%@ from mapping database!", graphId ];
      [[[FTGraphRemoveException alloc]
        initWithGraphId: graphId
        withReason: 
          [NSString stringWithFormat:
            @"FTGraphManagerImpl::removeGraphWithId: Unable to remove "\
          "graphId=%@ from mapping database!", graphId]]
        raise];
    }
  NS_HANDLER
    [[FTLogging coreLog]
        error: @"FTGraphManagerImpl::removeGraphWithId: Unable to remove "\
          "graphId=%@ from mapping database!", graphId ];
    [localException raise];
  NS_ENDHANDLER
  
  if( NO == successful ) {
    [[FTLogging coreLog]
      fatal: @"FTGraphManagerImpl::removeGraphWithId: Removing graph with id="\
        "%@ FAILED. No having an unkown state!"];
    [[[FTGraphRemoveException alloc]
      initWithGraphId: graphId
      withReason: @"Removing the data failed. Potentially inconsistent state "\
        "for this graph."]
      raise];
  } else {
    if( [[FTLogging coreLog] isDebugEnabled] ) {
      [[FTLogging coreLog]
        debug: @"FTGraphManagerImpl::removeGraphWithId: Graph with id="\
          "%@ removed", graphId];
    }
  }
  
  return self;
}
@end
