/*!
  @header FTNodeImpl
  @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

  31.03.05 ola     initial version
  11.08.05 ola     simpler architecture through usage of FTOrderEdgeSet
  23.08.06 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include <Encore/Encore.h>
#include <FT/FTIdImpl.h>
#include <FT/FTNodeImpl.h>
#include <FT/FTGraphImpl.h>
#include <FT/FTEdgeImpl.h>
#include <FT/FTIdToNodeIterator.h>
#include <FT/FTSessionImpl.h>
#include <FT/FTOrderedEdgeSetImpl.h>
#include <FT/FTOrderedReferenceSetImpl.h>
#include <FT/FTLogging.h>

@implementation FTNodeImpl


- init {
  self = [super init];
  
  self->graph = nil;
  self->nodeId = nil;
  self->lock = [[NSLock alloc] init];
  
  self->incomingReferences = nil;
  self->outgoingReferences = nil;
    
  return self;
}


- initWithNodeId: (id <FTId>) anId forGraph: (FTGraphImpl *) aGraph {
  self = [self init];
  self->nodeId = [anId retain];
  self->graph = [aGraph retain];
  
  self->incomingReferences = [[FTOrderedReferenceSetImpl alloc]
    initForBaseNode: self];
  self->outgoingReferences = [[FTOrderedReferenceSetImpl alloc]
    initForBaseNode: self];

  return self;
}


- (id) initWithCoder:(NSCoder *) decoder {
  id <FTId> graphId = nil;
  FTSessionImpl *session;
  id correspondingGraph;
  
  self = [super initWithCoder: decoder];

  self->lock = [[NSLock alloc] init];
  
  NS_DURING
    graphId = [[decoder decodeObject] retain];
  
    self->nodeId = [[decoder decodeObject] retain];
    
    self->incomingReferences = [[decoder decodeObject] retain];
    self->outgoingReferences = [[decoder decodeObject] retain];
    
  NS_HANDLER
    if( nil != graphId ) {
      [graphId release];
      graphId = nil;
    }
    
    [localException raise];
  NS_ENDHANDLER

  /**
   * Now find out corresponding FTGraphImpl instance based on the retrieved id
   */
  session = [FTSessionImpl currentSession];
  NSAssert( nil != session, @"FTNodeImpl::initWithCoder: Unable to determine "\
    "current session!" );
  
  correspondingGraph = [[session graphManager] graphWithId: graphId];
  NSAssert1( nil != correspondingGraph, @"FTNodeImpl::initWithCoder: Unable "\
    "to fetch graph with id=\"%@\" for the previously loaded nodes!", graphId );

  self->graph = (FTGraphImpl *) ([correspondingGraph retain]);

  return self;
}


- (void) dealloc {
  [self->lock release];
  [self->incomingReferences release];
  [self->outgoingReferences release];
  [self->graph release];
  [self->nodeId release];
  
  [super dealloc];
}


- addIncomingReferenceFromNodeId: (id <FTId>) aSourceNodeId 
  withEdgeId: (id <FTId>) anEdgeId {
  
  [self->incomingReferences createAndAppendReferenceToNode: aSourceNodeId
    withEdgeId: anEdgeId];
    
  [self internalStateChanged];
  return self;
}


- addOutgoingReferenceToNodeId: (id <FTId>) aTargetNodeId 
  withEdgeId: (id <FTId>) anEdgeId {

  [self->outgoingReferences createAndAppendReferenceToNode: aTargetNodeId
    withEdgeId: anEdgeId];
    
  [self internalStateChanged];
  return self;
}
  

- (unsigned) countIncomingReferences {
  return [self->incomingReferences countReferences];
}


- (unsigned) countOutgoingReferences {
  return [self->outgoingReferences countReferences];
}


- (id <FTEdge>) createAndAppendEdgeWithId: (id <FTId>) edgeId
  withTargetNode: (id <FTNode>) targetNode {
  FTNodeImpl *target;
  FTEdgeImpl *edge = nil;
  
  if( nil == edgeId ) {
    [[[ECIllegalArgumentException alloc]
      initWithArgumentInfo: @"FTNodeImpl::createAndAppendEdgeWithId: Edge ID "\
        "equals nil!" ] raise];
  }

  if( nil == targetNode ) {
    [[[ECIllegalArgumentException alloc]
      initWithArgumentInfo: @"FTNodeImpl::createAndAppendEdgeWithId: Target "\
        "node reference equals nil!" ] raise];
  }

  if( ![targetNode isKindOfClass: [self class]] ) {
    [[[ECIllegalArgumentException alloc]
      initWithArgumentInfo: @"FTNodeImpl::createAndAppendEdgeWithId: Illegal "\
        "target instance given!" ] raise];
  }

  target = (FTNodeImpl *) targetNode;
  [target writeLock: YES];
  [self writeLock: YES];
  
  NS_DURING
    [self addOutgoingReferenceToNodeId: [targetNode nodeId] 
      withEdgeId: edgeId];
    [target addIncomingReferenceFromNodeId: [self nodeId]
      withEdgeId: edgeId];

    edge = [[FTEdgeImpl alloc]
      initWithEdgeId: edgeId
      targetNode: targetNode
      sourceNode: self
      forGraph: self->graph ];   
  NS_HANDLER
    [target writeLock: NO];
    [self writeLock: NO];
    [localException raise];
  NS_ENDHANDLER
  
  [self internalStateChanged];
  
  [target writeLock: NO];
  [self writeLock: NO];
  
  return edge;
}


- (NSString *) description {
  NSString *desc = [[NSString alloc] initWithFormat: @"{id=%@}",
    [self->nodeId description]];
    
  return desc;
}


- (void) encodeWithCoder: (NSCoder *) encoder {
  [encoder encodeObject: [self->graph graphId]];
  [encoder encodeObject: self->nodeId];
  [encoder encodeObject: self->incomingReferences];
  [encoder encodeObject: self->outgoingReferences];
}

- (id <ECIterator>) incomingEdges {
  id <ECIterator> iterator;
  ECArrayIterator *result;
  NSMutableArray *edges;

  EC_AUTORELEASEPOOL_BEGIN
    iterator = [self->incomingReferences allReferences];
    edges = [[[NSMutableArray alloc] init] autorelease];

    while( [iterator hasNext] ) {
      id <FTReference> current = [iterator next];
      FTEdgeImpl *edge = 
        [[[FTEdgeImpl 
          alloc]
          initWithEdgeId: [current edgeId]
          targetNode: self
          sourceNode: [self->graph nodeWithId: [current nodeId]]
          forGraph: self->graph] autorelease];
      [edges addObject: edge];
    }

    result = [[ECArrayIterator alloc] initWithArray: edges];
  EC_AUTORELEASEPOOL_END

  return result;
}


- (id <ECIterator>) incomingNodes {
  return [self referencesToNodeIterator: self->incomingReferences];
}


- internalStateChanged {
  [self->graph internalStateChanged: self];
  return self;
}


/*!
 * @method nodeId
 * @result return the id of this node.
 */
- (id <FTId>) nodeId {
  return self->nodeId;
}


- (id <ECIterator>) outgoingEdges {
  id <ECIterator> iterator;
  ECArrayIterator *result;
  NSMutableArray *edges;

  EC_AUTORELEASEPOOL_BEGIN
    iterator = [self->outgoingReferences allReferences];
    edges = [[[NSMutableArray alloc] init] autorelease];

    while( [iterator hasNext] ) {
      id <FTReference> current = [iterator next];
      FTEdgeImpl *edge = 
        [[[FTEdgeImpl 
          alloc]
          initWithEdgeId: [current edgeId]
          targetNode: [self->graph nodeWithId: [current nodeId]]
          sourceNode: self
          forGraph: self->graph] autorelease];
      [edges addObject: edge];
    }

    result = [[ECArrayIterator alloc] initWithArray: edges];
  EC_AUTORELEASEPOOL_END

  return result;
}


- (id <ECIterator>) outgoingNodes {
  return [self referencesToNodeIterator: self->outgoingReferences];
}


- (id <ECIterator>) referencesToNodeIterator: 
  (id <FTOrderedReferenceSet>) references {
  id <ECIterator> iterator;
  NSMutableArray *nodes;
  ECArrayIterator *result;
  
  EC_AUTORELEASEPOOL_BEGIN
  iterator = [references allReferences];
  nodes = [[[NSMutableArray alloc] init] autorelease];
  
  while( [iterator hasNext] ) {
    id <FTReference> current = [iterator next];
    id <FTNode> outgoingNode = nil;
    
    NS_DURING
      outgoingNode = [self->graph nodeWithId: [current nodeId]];
      [nodes addObject: outgoingNode];
    NS_HANDLER
    NS_ENDHANDLER
  }

  result = [[ECArrayIterator alloc] initWithArray: nodes];
  EC_AUTORELEASEPOOL_END  
  
  return result;
}


- removeIncomingReferencePointingFrom: (FTNodeImpl *) sourceNode 
  withEdgeId: (id <FTId>) edgeId {
  
  [self removeFromReferenceSet: self->incomingReferences
    node: sourceNode
    withEdgeId: edgeId ];
    
  [self internalStateChanged];
  
  return self;
}



- removeOutgoingReferencePointingTo: (FTNodeImpl *) targetNode
  withEdgeId: (id <FTId>) edgeId {
  
  [self removeFromReferenceSet: self->outgoingReferences
    node: targetNode
    withEdgeId: edgeId ];  
    
  [self internalStateChanged];
  
  return self;
}


- removeFromReferenceSet: (id <FTOrderedReferenceSet>) references
  node: (FTNodeImpl *) aNode withEdgeId: (id <FTId>) anEdgeId {
  id <FTReference> reference;
  
  reference = [references referenceByEdgeId: anEdgeId];
  if( nil == reference ) {
    ECIllegalArgumentException *ex;
    NSString *msg;
    
    msg = [[NSString alloc] 
      initWithFormat: @"FTNodeImpl::removeFromReferences:"\
        " Unable to find in references %@ from node: %@",
        reference, aNode ];
    
    ex = [[ECIllegalArgumentException alloc] 
      initWithArgumentInfo: msg ];

    [msg release];  
    [ex raise];
  }
  
  if( ![[reference nodeId] isEqual: [aNode nodeId]] ) {
    ECIllegalStateException *ex;
    NSString *msg;
    
    msg = [[NSString alloc] initWithFormat:
      @"FTNodeImpl::removeFromReferences: node id of "\
      "reference %@ does not contain node %@", reference, aNode];

    ex = [[ECIllegalStateException alloc] initWithIllegalStateInfo: msg];
    
    [msg release];    
    [ex raise];
  }
  
  [references removeReference: reference];
  
  return self;
}


- removeAllOutgoingNodesWithId: (id <FTId>) aNodeId {
  id <ECIterator> referencesToRemove;
  
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  referencesToRemove = [self->outgoingReferences referencesByNodeId: aNodeId];
  [referencesToRemove autorelease];
  
  if( nil != referencesToRemove ) {
    while( [referencesToRemove hasNext] ) {
      id <FTReference> current = [referencesToRemove next];
      FTNodeImpl *targetNode;
      
      // fetch related target node
      NS_DURING
        targetNode = (FTNodeImpl *) [self->graph nodeWithId: [current nodeId]];
      NS_HANDLER
        NSString *msg;
        ECIllegalStateException *ex;
        
        msg = [[[NSString alloc] initWithFormat:
          @"FTNodeImpl::removeAllOutgoingNodesWithId: Unable to find "\
          "node with id=%@ to which this node with id=%@ is pointing to!",
          [current nodeId], [self nodeId] ]
          autorelease];
        
        ex = [[ECIllegalStateException alloc] 
          initWithIllegalStateInfo: msg];
          
        [pool release];
        
        [ex raise];
      NS_ENDHANDLER
      
      NS_DURING
        [targetNode removeIncomingReferencePointingFrom: self
          withEdgeId: [current edgeId]];
          
        [self removeOutgoingReferencePointingTo: targetNode 
          withEdgeId: [current edgeId]];
      NS_HANDLER
        [[FTLogging coreLog]
          error: @"FTNodeImpl::removeAllOutgoingNodesWithId: Unable to remove "\
            "reference to target node %@", targetNode ];
      NS_ENDHANDLER 
    }
  }
  
  [pool release];

  return self;
}


- (id <FTService>) serviceWithId: (NSString *) aServiceId {
  return [self->graph serviceWithId: aServiceId forNode: self];
}


- writeLock: (BOOL) enable {
  if( enable ) {
    [self->lock lock];
  } else {
    [self->lock unlock];
  }
  
  return self;
}
@end
