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

  16.06.06 ola     initial version
  23.08.06 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <Encore/Encore.h>
#include <FTGraphVisitor.h>
#include <FTExport.h>

@implementation FTGraphVisitor

+ (NSFileHandle *) createFile: (NSString *) filename {
  int fh;
  NSFileHandle *toReturn;
      
  fh = creat( [filename cString], O_CREAT | O_TRUNC | O_WRONLY );
  fchmod( fh, S_IRWXU );
    
  toReturn = [[[NSFileHandle alloc]
    initWithFileDescriptor: fh] autorelease];
  
  return toReturn;
}


+ (void) writeXMLHeaderIntoFile: (NSFileHandle *) fileHandle 
  forGraphId: (NSString *) aGraphId 
  hasToCreateGraph: (BOOL) toCreateGraph 
  encoding: (int) anEncoding{
  
  EC_AUTORELEASEPOOL_BEGIN
          
  NSMutableString *currentLine;
  
  currentLine = [[[NSMutableString alloc] init] autorelease];
    
  [currentLine
    appendFormat: @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n"];
   
#if 0 
  [currentLine
    appendFormat: @"<!DOCTYPE FTExport SYSTEM \"FTExport.dtd\">\n\n"];
#endif 

  [currentLine
    appendFormat: @"<FTExport graphId=\"%@\" createGraph=\"%@\">\n",
      aGraphId, (YES == toCreateGraph ) ? @"YES" : @"NO"];
  
  [fileHandle 
    writeData: [currentLine dataUsingEncoding: anEncoding]];
       
  EC_AUTORELEASEPOOL_END
}


+ (void) writeXMLFooterIntoFile: (NSFileHandle *) fileHandle 
  encoding: (int) anEncoding {
  [fileHandle 
    writeData: [@"</FTExport>\n" dataUsingEncoding: anEncoding]];
}


- initForSession: (id <FTAdministrationSession>) aSession
  graphId: (id) aGraphId exportDirectory: (NSString *) dir
  baseFilename: (NSString *) baseFilename 
  encoding: (int) anEncoding {
  
  self = [super init];
  
  self->session = [aSession retain];
  self->graphIdString = [aGraphId retain];
  self->exportDir = [dir retain];
  self->baseExportFilename = [baseFilename retain];
  self->graph = nil;
  self->objectToIdMapper = nil;
  self->graphManager = nil;
  self->encoding = anEncoding;
  self->blobFileIdCounter = 0;
  
  return self;
}


- (void) dealloc {
  [self->session release];
  [self->graphIdString release];
  [self->exportDir release];
  [self->baseExportFilename release];

  [super dealloc];
}


- exportContent {
  EC_AUTORELEASEPOOL_BEGIN
  id <FTTransaction> transaction;
  
  if( [[FTExport log] isTraceEnabled] ) {
    [[FTExport log] 
      trace: @"FTGraphVisitor::exportContent"];
  }
  
  self->objectToIdMapper = [self->session defaultObjectToIdMapper];
  self->graphManager = [self->session graphManager];
  
  if( [[FTExport log] isDebugEnabled] ) {
    [[FTExport log] debug: @"FTGraphVisitor::exportContent: Looking for graph "\
      "with id=\"%@\"", self->graphIdString];     
  }
  
  transaction = [self->session
    beginTransactionWithParent: nil
    withSettings:nil];

    
  self->graphId = [self->objectToIdMapper mapObject: self->graphIdString];
  NS_DURING
    self->graph = [self->graphManager graphWithId: self->graphId];    
  NS_HANDLER
    [[FTExport log] 
      error: @"FTGraphVisitor::exportContent: Unable to fetch graph with id="\
        "\"%@\"", self->graphIdString];
    [localException raise];
  NS_ENDHANDLER
  
  if( [[FTExport log] isDebugEnabled] ) {
    [[FTExport log] debug: @"FTGraphVisitor::exportContent: Got graph!"];
  }
  
  [[NSFileManager defaultManager]
    createDirectoryAtPath: 
      [NSString stringWithFormat: @"%@/%@",
        self->exportDir, self->baseExportFilename]
    attributes: nil];
    
#if 1
  [self exportGraphXml];
  [self exportNodeIds];
#endif
  
  [self olacheck];

  EC_AUTORELEASEPOOL_END
  
  return self;
}


- olacheck {
  id <FTObjectToIdMapper> graphIdMapper;
  id <ECIterator> nodeIterator;
  id <FTTransaction> transaction;

  graphIdMapper = [[graph objectToIdMapper] retain];

  transaction = [self->session beginTransactionWithParent: nil
    withSettings: nil];

  nodeIterator = [self->graph nodeIterator];

  while( [nodeIterator hasNext] ) {
    id <FTNode> currentNode = [nodeIterator next];
    id <FTDictionaryService> dictionary;
    id <ECIterator> allKeys;

    dictionary = (id <FTDictionaryService>)
      [currentNode serviceWithId: @"FTDictionaryService"];
    allKeys = [dictionary allKeys];
  }

  return self;
}


/* name of overall XML  file */
#define __graph_xml_file "%@/%@/graph.xml"
/* name of node id file: */
#define __node_ids_file "%@/%@/nodes.xml"
#define __node_refs_file "%@/%@/references.xml"
#define __node_blob_file "%@/%@/serviceData.xml"

- exportGraphXml {
  EC_AUTORELEASEPOOL_BEGIN
  NSFileHandle *fileHandle;
  
  NS_DURING
   fileHandle = [FTGraphVisitor createFile: 
     [NSString stringWithFormat: @__graph_xml_file,
       self->exportDir, self->baseExportFilename]];
  NS_HANDLER
    [[FTExport log]
      error: @"FTGraphVisitor::exportGraphXML: Unable to create overall XML "\
        "fragment. Exception: %@", localException];
    [fileHandle closeFile];
    [localException raise];
  NS_ENDHANDLER
  
  NS_DURING
    [FTGraphVisitor 
      writeXMLHeaderIntoFile: fileHandle
      forGraphId: self->graphIdString
      hasToCreateGraph: YES
      encoding: self->encoding];

    [FTGraphVisitor 
      writeXMLFooterIntoFile: fileHandle
      encoding: self->encoding];
      
  NS_HANDLER
    [[FTExport log] 
      error: @"FTGraphVisitor::exportGraphXML: Error while creating header. "\
        "Exception: %@", localException];
    [fileHandle closeFile];
    
    [localException raise];
  NS_ENDHANDLER
  
  [fileHandle closeFile];

  EC_AUTORELEASEPOOL_END
  
  return self;
}


- exportNodeIds {
  EC_AUTORELEASEPOOL_BEGIN
  id <ECIterator> nodeIterator;
  NSFileHandle *idsFileHandle, *refsFileHandle,
    *blobFileHandle;
  
  NS_DURING
     idsFileHandle = [FTGraphVisitor createFile: 
       [NSString stringWithFormat: @__node_ids_file,
         self->exportDir, self->baseExportFilename]];
       
     refsFileHandle = [FTGraphVisitor createFile: 
       [NSString stringWithFormat: @__node_refs_file,
         self->exportDir, self->baseExportFilename]];

     blobFileHandle = [FTGraphVisitor createFile: 
       [NSString stringWithFormat: @__node_blob_file,
         self->exportDir, self->baseExportFilename]];
  NS_HANDLER
    [[FTExport log]
      error: @"FTGraphVisitor::exportNodeIds: error creating file:"\
        " "__node_ids_file" Exception: %@", localException];
    [localException raise];
  NS_ENDHANDLER
  
  [FTGraphVisitor 
      writeXMLHeaderIntoFile: idsFileHandle
      forGraphId: self->graphIdString
      hasToCreateGraph: NO
      encoding: self->encoding];
  [idsFileHandle 
    writeData: [@"  <nodes>\n" dataUsingEncoding: self->encoding]];
    
  [FTGraphVisitor 
      writeXMLHeaderIntoFile: refsFileHandle
      forGraphId: self->graphIdString
      hasToCreateGraph: NO
      encoding: self->encoding];
      
  [refsFileHandle
    writeData: [@"  <nodeReferences>\n" dataUsingEncoding: self->encoding]];
    
  [FTGraphVisitor 
      writeXMLHeaderIntoFile: blobFileHandle
      forGraphId: self->graphIdString
      hasToCreateGraph: NO
      encoding: self->encoding];
  [blobFileHandle
    writeData: [@"  <serviceData>\n" dataUsingEncoding: self->encoding]];
      
  NS_DURING
    id <ECIterator> outgoingEdges;
    id <FTObjectToIdMapper> graphIdMapper;

    graphIdMapper = [[graph objectToIdMapper] retain];
    nodeIterator = [self->graph nodeIterator];
    
    while( [nodeIterator hasNext] ) {
      NSMutableString *currentLine;
      
      id <FTNode> currentNode = [nodeIterator next];
      currentLine = [[[NSMutableString alloc] 
        initWithFormat: @"    <node nodeId=\"%@\"/>\n",
          [currentNode nodeId]] autorelease];
      
      [idsFileHandle 
        writeData: [currentLine dataUsingEncoding: self->encoding]];
        
      /* determine outgoing nodes: */
      currentLine = [[[NSMutableString alloc] 
        initWithFormat: @"    <nodeReferenceList ofNodeId=\"%@\">\n", 
        [currentNode nodeId]]
        autorelease];
            
      [refsFileHandle 
        writeData: [currentLine dataUsingEncoding: self->encoding]];
    
      outgoingEdges = [currentNode outgoingEdges];
      while( [outgoingEdges hasNext] ) {
        id <FTEdge> edge;
        
        edge = [outgoingEdges next];
        currentLine = [[[NSMutableString alloc] 
          initWithFormat: @"      <reference targetNodeId=\"%@\""\
            " edgeId=\"%@\"/>\n",
            [[edge targetNode] nodeId],
            [edge edgeId]] autorelease];
      
        [refsFileHandle 
        writeData: [currentLine dataUsingEncoding: self->encoding]];  
      }
        
      currentLine = [[[NSMutableString alloc] 
        initWithFormat: @"    </nodeReferenceList>\n"] autorelease];

      [refsFileHandle 
        writeData: [currentLine dataUsingEncoding: self->encoding]];
      [self writeBlobEntriesOfNode: currentNode
        usingFileHandle: blobFileHandle];
    }
    
    [idsFileHandle 
      writeData: [@"  </nodes>\n" dataUsingEncoding: self->encoding]];
    [FTGraphVisitor 
      writeXMLFooterIntoFile: idsFileHandle
      encoding: self->encoding];
      
    [refsFileHandle 
      writeData: [@"  </nodeReferences>\n" dataUsingEncoding: self->encoding]];
    [FTGraphVisitor 
      writeXMLFooterIntoFile: refsFileHandle
      encoding: self->encoding];
      
    [blobFileHandle 
      writeData: [@"  </serviceData>\n" dataUsingEncoding: self->encoding]];
    [FTGraphVisitor 
      writeXMLFooterIntoFile: blobFileHandle
      encoding: self->encoding];
  NS_HANDLER
    [[FTExport log]
      error: @"FTGraphVisitor::exportNodeIds: Got exception while iterating "\
        "through all nodes. Exception message: %@", localException];
    [localException raise];
  NS_ENDHANDLER
  
  [idsFileHandle closeFile];
  [refsFileHandle closeFile];
  [blobFileHandle closeFile];
  
  EC_AUTORELEASEPOOL_END
  return self;
}


- writeBlobEntriesOfNode: (id <FTNode>) node 
  usingFileHandle: (NSFileHandle *) fileHandle {

  EC_AUTORELEASEPOOL_BEGIN
  NSMutableString *currentLine;
  id <ECIterator> keyIterator;
  id <FTDictionaryService> dictionaryService;
  
  currentLine = [NSMutableString 
    stringWithFormat: @"    <nodeServiceData nodeId=\"%@\">\n", [node nodeId]];

  [fileHandle 
    writeData: [currentLine dataUsingEncoding: self->encoding]];

  dictionaryService = (id <FTDictionaryService>)
    [node serviceWithId: @"FTDictionaryService"];
  
  keyIterator = [dictionaryService allKeys];
  while( [keyIterator hasNext] ) {
    id <NSCoding> key = [keyIterator next];
    id <NSCoding> value = [dictionaryService objectForKey: key];
    
    if( [[FTExport log] isDebugEnabled] ) {
      [[FTExport log] debug: @"FTGraphVisitor::writeBlobEntriesOfNode: "\
        "Got a key of dictionary service: %@", key];
    }
   
    if( nil != key && nil != value ) {
      NSString *filename;
      filename = [self writeKey: key withValue: value];
      [fileHandle
        writeData:
          [[NSString 
            stringWithFormat: @"      <dataItem forService=\"FTDictionary"\
              "Service\" file=\"%@\"/>\n", filename ]
          dataUsingEncoding: self->encoding]];
    }
  }

  [fileHandle 
    writeData: [@"    </nodeServiceData>\n" dataUsingEncoding: self->encoding]];

  EC_AUTORELEASEPOOL_END

  return self;
}


#define __blobdata_file @"%@/%@/blobdata-%u.obj"

- (NSString *) writeKey: (id <NSCoding>) aKey 
  withValue: (id <NSCoding>) value {
  NSString *filename;
  NSString *filenameEntry;
  NSMutableData *data;
  NSKeyedArchiver *archiver;
  
  /* todo: use a more space friendly approach here... */
  filenameEntry = [[NSString alloc] 
    initWithFormat: @"blobdata-%u.obj", ++(self->blobFileIdCounter)];

  filename = [NSString 
    stringWithFormat: @"%@/%@/%@", self->exportDir, 
      self->baseExportFilename, filenameEntry ];
  
  EC_AUTORELEASEPOOL_BEGIN
  
  data = [[[NSMutableData alloc] init] autorelease];
  archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData: data]
    autorelease];
  
  [archiver encodeObject: aKey forKey: @"key"];
  [archiver encodeObject: value forKey: @"value"];
  [archiver finishEncoding];
  
  [data 
    writeToFile: filename
    atomically: YES];
  
  EC_AUTORELEASEPOOL_END
  
  return [filenameEntry autorelease];
}

@end
