/*
**  LibrarySource.m
**
**  Copyright (c) 2004
**
**  Author: Yen-Ju Chen <yjchenx@hotmail.com>
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation; either version 2 of the License, or
**  (at your option) any later version.
**
**  This program 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 General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "GNUstep.h"
#include "LibrarySource.h"
#include "AttributesSource.h"
#include "CodeParser.h"
#include "RenderHandler.h"
#include "LinkReplaceHandler.h"
#include "Utilities.h"
#include "HRCell.h"

static LibrarySource *sharedInstance = nil;
/* Keys for data source */
static NSString *ItemPathKey = @"ItemPathKey";
static NSString *ItemTypeKey = @"ItemTypeKey";
static NSString *ItemNoteKey = @"ItemNoteKey";
  /* Value for ItemType */
  static NSString *NoteItem = @"NoteItem";
  static NSString *FileItem = @"FileItem";
  static NSString *UnknownItem = @"UnknownItem";

@implementation LibrarySource
/* Write Accessory */

- (BOOL) _testAndCreateDirectory: (NSString *) dir
{ 
  NSFileManager *fm = [NSFileManager defaultManager];
  BOOL isDir; 
  if ([fm fileExistsAtPath: dir isDirectory: &isDir])
    {     
      if (isDir == NO)
        {
          return NO;
        }
    }
  else
    {
      if ([fm createDirectoryAtPath: dir attributes: nil] == NO)
        {
          return NO;
        }
    }
  return YES;
}

/* For newer format */
- (unsigned int) _reIndexUniqueNumber
{
  NSLog(@"_reIndexUniqueNumber");
  unsigned int i, count = [self count];
  id object;
  for(i = 0; i < count; i++)
    {
      [self setUniqueNumber: [self uniqueNumber] atIndex: i];
    }
}

- (NSString *) _uniqueName: (NSString *) name
{
  NSString *s, *f;
  unsigned int i, newItemCount=0;
  BOOL isUniqueName = YES;

  while(1)
    {
      s = [NSString stringWithFormat: @"%@ %d", name, newItemCount];
      for (i = 0; i < [self count]; i++)
        {
	  if ([s caseInsensitiveCompare: [self titleAtIndex: i]] == NSOrderedSame)
	    {
  	      isUniqueName = NO;
	      break;
	    }
        }

      if (isUniqueName == YES)
	break;

      newItemCount++;
      isUniqueName = YES;
    }

  RETAIN(s);
  return AUTORELEASE(s);
}

/* Return path in library */
- (NSString *) _addFileIntoLibrary: (NSString *) source
{
  NSString *path;
  NSDictionary *fileAttr;
  NSCalendarDate *creationDate;
  int year, month, day;
  NSString *libraryPath = [[self path] stringByDeletingLastPathComponent];
  NSFileManager *fm = [NSFileManager defaultManager];
  /* get create date of file */
  fileAttr = [fm fileAttributesAtPath: source
                         traverseLink: YES];
  if (fileAttr == nil) return nil;
  creationDate = [[fileAttr objectForKey: NSFileCreationDate]
				     dateWithCalendarFormat: nil
				                  timeZone: nil];
  year = [creationDate yearOfCommonEra];
  month = [creationDate monthOfYear];
  day = [creationDate dayOfMonth];
  // NSLog(@"%d/%2d/%2d", year, month, day);

  /* create year */
  path = [libraryPath stringByAppendingPathComponent: [NSString stringWithFormat: @"%d", year]];
  if ([self _testAndCreateDirectory: path] == NO)
    {
      return nil;
    }
  /* create month */
  path = [path stringByAppendingPathComponent: [NSString stringWithFormat: @"%02d", month]];
  if ([self _testAndCreateDirectory: path] == NO)
    {
       return nil;
    }
  /* create date */
  path = [path stringByAppendingPathComponent: [NSString stringWithFormat: @"%02d", day]];
  if ([self _testAndCreateDirectory: path] == NO)
    {
      return nil;
    }

  /* put file into directory */
  NSString *filename = [source lastPathComponent];
  NSString *extension = [source pathExtension];
  NSString *newPath = [path stringByAppendingPathComponent: filename];
  NSString *newFilename = AUTORELEASE([filename copy]);
  unsigned int acc = 0;
  while(1)
    {
      if (([fm fileExistsAtPath: newPath]) ||
	  ([self isDuplicatedTitle: newFilename]))
        { /* Searhc a new filename */
	  newFilename = [NSString stringWithFormat: @"%@_%d", [filename stringByDeletingPathExtension], acc];
	  newFilename = [newFilename stringByAppendingPathExtension: extension];
	  newPath = [newPath stringByDeletingLastPathComponent];
	  newPath = [newPath stringByAppendingPathComponent: newFilename];
	  acc++;
	}
      else
        break;
    }

  //NSLog(@"new %@", newPath);
  if ([fm copyPath: source toPath: newPath handler: nil] == NO)
    {
       return nil;
    }
  return AUTORELEASE([newPath copy]);;
}

/* over-riding superclass */
- (void) removeObjectAtIndex: (unsigned int) index
{
  if (index >= [self count]) return;

  if ([self typeOfItemAtIndex: index] == ItemFileType)
    {
      /* remove file first */
      NSFileManager *fm = [NSFileManager defaultManager];
      NSString *libraryPath = [[self path] stringByDeletingLastPathComponent];
      NSString *path = [libraryPath stringByAppendingPathComponent: [self pathOfItemAtIndex: index]];
      //NSLog(@"remove path %@", path);
      [fm removeFileAtPath: path handler: nil];
    }
  [super removeObjectAtIndex: index];
}

- (void) setTitle: (NSString *) title atIndex: (unsigned int) index
{
  [super setTitle: title atIndex: index];
  if ([[self objectAtIndex: index forKey: ItemTypeKey] isEqualToString: NoteItem] == YES)
    {
      /* Update attributes if necessary */
      unsigned int attrIndex, uid;
      uid = [self uniqueNumberAtIndex: index];
      attrIndex = [[AttributesSource sharedSource] indexOfUniqueNumber: uid];
      [[AttributesSource sharedSource] setTitle: title atIndex: attrIndex];

      [self setObject: title atIndex: index forKey: ItemPathKey];
    }
}

/* high-level methods */
- (void) replaceContentOfLink: (NSString *) oldString
                   withString: (NSString *) newString
{
  unsigned int i, count = [self count];
  NSString *content, *newContent;
  for(i = 0; i < count; i++)
    {
      content = [self noteAtIndex: i];
      LinkReplaceHandler *handler = [[LinkReplaceHandler alloc] init];
      CodeParser *parser = [[CodeParser alloc] initWithCodeHandler: handler
	                                       withString: content];
      [handler setOldLink: oldString replacedBy: newString];
      [parser parse];
      newContent = [handler updatedString];
      [self setNote: newContent atIndex: i];
      RELEASE(handler);
      RELEASE(parser);
    }
}

/* write accessory methods */

- (unsigned int) newItem
{
  NSString *s = [self _uniqueName: @"New Note"];
  return [self newItemWithTitle: s];
}

- (unsigned int) newItemWithTitle: (NSString *) title
{
  unsigned int index = [self newTitle: title];
  if (index == NSNotFound) return NSNotFound;
  [self setObject: title atIndex: index forKey: ItemPathKey];
  [self setObject: NoteItem atIndex: index forKey: ItemTypeKey];
  return index;
}

- (unsigned int) newItemWithFile: (NSString *) absolutePath
{
  NSString *path;
  unsigned int attrIndex, uid;

  absolutePath = [absolutePath stringByStandardizingPath];
  path = [self _addFileIntoLibrary: absolutePath];
  if (path == nil)
    return NSNotFound;

  NSString *title = [path lastPathComponent];
  unsigned int index = [self newTitle: title];
  if (index == NSNotFound) return NSNotFound;

  //NSLog(@"%@", attributesFromFile(path));

  NSString *libraryPath = [[self path] stringByDeletingLastPathComponent];
  NSString *relativePath = [path substringFromIndex: [libraryPath length]+1];

  [self setObject: relativePath atIndex: index forKey: ItemPathKey];
  [self setObject: FileItem atIndex: index forKey: ItemTypeKey];

  uid = [self uniqueNumberAtIndex: index];
  attrIndex = [[AttributesSource sharedSource] newTitle: relativePath];
  [[AttributesSource sharedSource] setAttributes: attributesFromFile(path) atIndex: attrIndex];
  [[AttributesSource sharedSource] setUniqueNumber: uid atIndex: attrIndex];

  return index;
}

- (void) setPath: (NSString *) path atIndex: (unsigned int) index
{
  if ([[self objectAtIndex: index forKey: ItemTypeKey] isEqualToString: NoteItem])
    return;
  [self setObject: path atIndex: index forKey: ItemPathKey];
}

- (void) setNote: (NSString *) note atIndex: (unsigned int) index
{
  if ([note length] == 0)
    {
      if ([self objectAtIndex: index forKey: ItemNoteKey])
        [self removeObjectForKey: ItemNoteKey atIndex: index];
      return;
    }
  [self setObject: note atIndex: index forKey: ItemNoteKey];
}

/* Items methods */
- (NSString *) pathOfItemAtIndex: (unsigned int) index
{
  return [self objectAtIndex: index forKey: ItemPathKey];
}

- (NSString *) fullPathOfItemAtIndex: (unsigned int) index
{
  id path = [self pathOfItemAtIndex: index];
  if (path == nil) return nil;
  NSString *libraryPath = [[self path] stringByDeletingLastPathComponent];
  return [libraryPath stringByAppendingPathComponent: path];
}

- (ItemType) typeOfItemAtIndex: (unsigned int) index
{
  NSString *value;
  if (index < [self count])
    value = [self objectAtIndex: index forKey: ItemTypeKey];
  else
    return ItemErrorType;

  if ([value isEqualToString: UnknownItem])
    return ItemUnknownType;
  else if ([value isEqualToString: NoteItem])
    return ItemNoteType;
  else if ([value isEqualToString: FileItem])
    return ItemFileType;
  else 
    return ItemErrorType;
}

- (NSString *) typeStringOfItemAtIndex: (unsigned int) index
{
  switch ([self typeOfItemAtIndex: index])
    {
      case ItemNoteType:
	return sNote;
      case ItemFileType:
	return [[self pathOfItemAtIndex: index] pathExtension];
      default:
	return sUnknown;
    }
}

- (NSAttributedString *) renderedNoteAtIndex: (unsigned int) index
{
  if (index < [self count])
    {
      CodeParser *parser;
      NSAttributedString *as;
      NSString *content = [self noteAtIndex: index];

      if (content == nil)
	return nil;

      // Render page
      RenderHandler *handler = [[RenderHandler alloc] init];
      parser = [[CodeParser alloc] initWithCodeHandler: handler
	                                    withString: content];
      NS_DURING
      [parser parse];
      NS_HANDLER
      NSLog(@"MyLibrary Error: render fails");
      AUTORELEASE(handler);
      AUTORELEASE(parser);
      if ([[localException name] isEqualToString: RenderTextExceptionName])
        {
	  [localException raise];
        }
      NS_ENDHANDLER

      as = [handler renderedString];
      RETAIN(as);
      AUTORELEASE(handler);
      AUTORELEASE(parser);
      return AUTORELEASE(as);
    }
  else
    return nil;
}

- (NSString *) noteAtIndex: (unsigned int) index
{
  return [self objectAtIndex: index forKey: ItemNoteKey];
}

- (NSAttributedString *) summaryAtIndex: (unsigned int) index
                      maximalAttributes: (int) max
                                  style: (SummaryStyle) style
{
  NSMutableAttributedString *summary = [[NSMutableAttributedString alloc] init];
  NSAttributedString *as;
  NSString *title, *path;
  NSString *s, *s1, *summaryStyle, *format;
  NSDictionary *attr, *dict;
  NSFont *font;
  NSColor *color;
  NSTextAttachment *textAttachment;
  HRCell *hr = [HRCell sharedHRCell];
  AttributesSource *attributes = [AttributesSource sharedSource];
  NSNumberFormatter *numberFormatter;
  int intNumber;
  unsigned int attrIndex, uid;
  unsigned int i, count;

  switch (style)
    {
      case SummaryBriefStyle:
	summaryStyle = sBriefSummary;
	break;
      case SummaryRegularStyle:
	summaryStyle = sRegularSummary;
	break;
      case SummaryCompleteStyle:
	summaryStyle = sCompleteSummary;
	break;
    }

#define ADD_s_attr_INTO_summary \
          as = [[NSAttributedString alloc] initWithString: s \
	                                      attributes: attr]; \
          [summary appendAttributedString: as]; \
          RELEASE(as);

  /* Title */
  title = [self titleAtIndex: index];
  uid = [self uniqueNumberAtIndex: index];
  s = [NSString stringWithFormat: @"%@\n", title];
  font = [NSFont boldSystemFontOfSize: 14];
  attr = [NSDictionary dictionaryWithObjectsAndKeys:
	                          font, NSFontAttributeName, nil];
  ADD_s_attr_INTO_summary;

#if 0
  /* Summary style */
  if (flag == YES)
    s = [NSString stringWithFormat: @" (%@)\n", summaryStyle];
  else
    s = [NSString stringWithFormat: @"\n"];
  font = [NSFont boldSystemFontOfSize: 14];
  color = [NSColor lightGrayColor];
  attr = [NSDictionary dictionaryWithObjectsAndKeys:
	                          font, NSFontAttributeName, 
                                 color, NSForegroundColorAttributeName, nil];
  ADD_s_attr_INTO_summary;
#endif

  /* Horizontal bar */
  textAttachment = [[NSTextAttachment alloc] init];
  [textAttachment setAttachmentCell: hr];
  [summary appendAttributedString: [NSAttributedString attributedStringWithAttachment: textAttachment]];
  RELEASE(textAttachment);

  s = @"\n";
  font = [NSFont userFontOfSize: 12];
  attr = [NSDictionary dictionaryWithObjectsAndKeys:
	                          font, NSFontAttributeName, nil];
  ADD_s_attr_INTO_summary;

  path = [self pathOfItemAtIndex: index];
  attrIndex = [attributes indexOfUniqueNumber: uid];
  /* Make sure file does exist */
  if ([path isEqualToString: title] == NO)
    {
      /* Make sure file does exist */
      s = [path lastPathComponent];

      /* file size */
      intNumber = [[attributes attributeAtIndex: attrIndex
	                                forName: FileInfoSize] intValue];
      if (intNumber)
        {
	  numberFormatter = [[NSNumberFormatter alloc] init];
          [numberFormatter setLocalizesFormat: YES];
          format = [numberFormatter positiveFormat];
          format = [format substringToIndex: [format length]-2];
          [numberFormatter setFormat: format];
	  // [numberFormatter setFormat: @"#,##0"];
	  s1 = [numberFormatter stringForObjectValue: [NSNumber numberWithInt: intNumber]];
	  s = [NSString stringWithFormat: @"%@ (%@ %@)\n\n", s, s1, sBytes];
	  RELEASE(numberFormatter);
	}

      font = [NSFont userFontOfSize: 12];
      font = [[NSFontManager sharedFontManager] convertFont: font 
	                                        toHaveTrait: NSItalicFontMask];  
      attr = [NSDictionary dictionaryWithObjectsAndKeys:
	                              font, NSFontAttributeName, nil];
      ADD_s_attr_INTO_summary;
    }

  /* attributes */
  dict = [attributes attributesAtIndex: attrIndex];
  if (dict)
    {
      id key, value;
      NSString *category, *name, *type;
      NSMutableDictionary *d1 = [[NSMutableDictionary alloc] init];
      NSArray *allKeys = [dict allKeys];
      NSMutableArray *a1;
      count = [allKeys count];
      for(i = 0; i < count; i++)
        {
	  if ((i >= max) && (max >= 0)) break;
	  key = [allKeys objectAtIndex: i];
	  category = categoryOfAttributeKey(key);
	  if ([category isEqualToString: FileInfoCategory])
	    continue;
	  name = nameOfAttributeKey(key);
	  type = typeOfAttributeKey(key);
	  value = [dict objectForKey: key];
	  /* format attribute */
	  if ([type isEqualToString: AttributeStringType])
	    {
	      s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, value];
	    }
	  else if ([type isEqualToString: AttributeFloatType])
	    {
#if 0
	      numberFormatter = [[NSNumberFormatter alloc] init];
	      [numberFormatter setLocalizesFormat: YES];
	      s1 = [numberFormatter stringForObjectValue: value];
	      s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, s1];
	      RELEASE(numberFormatter);
#endif
	      s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, value];
	    }
	  else if ([type isEqualToString: AttributeIntegerType])
	    {
#if 0
	      numberFormatter = [[NSNumberFormatter alloc] init];
	      [numberFormatter setLocalizesFormat: YES];
	      format = [numberFormatter positiveFormat];
	      format = [format substringToIndex: [format length]-2];
	      [numberFormatter setFormat: format];
	      s1 = [numberFormatter stringForObjectValue: value];
	      s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, s1];
	      RELEASE(numberFormatter);
#endif
	      s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, value];
	    }
	  else if ([type isEqualToString: AttributeCalendarDateType])
	    {
	      s1 = [value descriptionWithCalendarFormat: CalendarDateDisplayFormat timeZone: nil locale: nil];
	      s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, s1];
	    }
	  else
	    {
	      NSLog(@"Internal Error: Invalide AttributeType for summary");
	      continue;
	    }
	  a1 = [d1 objectForKey: category];
	  if (a1 == nil)
	    {
	      a1 = [[NSMutableArray alloc] init];
	      [d1 setObject: a1 forKey: category];
	      RELEASE(a1);
	    }
	  [a1 addObject: s1];
        }
      /* Go through all attributes */
      allKeys = [d1 allKeys];
      count = [allKeys count];
      for(i = 0; i < count; i++)
        {
	  category = [allKeys objectAtIndex: i];
	  if (count > 1) /* Put category only for multiple categories */
	    {
	      s = [NSString stringWithFormat: @"%@\n", category];

              font = [NSFont userFontOfSize: 12];
              font = [[NSFontManager sharedFontManager] convertFont: font 
	                                        toHaveTrait: NSBoldFontMask];  
              attr = [NSDictionary dictionaryWithObjectsAndKeys: 
	                              font, NSFontAttributeName, nil];
              ADD_s_attr_INTO_summary;
	    }

	  a1 = [d1 objectForKey: category];
	  [a1 sortUsingSelector: @selector(compare:)];
	  NSEnumerator *e = [a1 objectEnumerator];
	  id object;
          font = [NSFont userFontOfSize: 12];
          attr = [NSDictionary dictionaryWithObjectsAndKeys:
	                              font, NSFontAttributeName, nil];
	  while ((object = [e nextObject]))
	    {
	      s = [NSString stringWithFormat: @"%@\n", object];
              ADD_s_attr_INTO_summary;
	    }
          s = @"\n";
          ADD_s_attr_INTO_summary;
        }
    }

  /* rendered note */
  as = [self renderedNoteAtIndex: index];
  if (as)
    {
      unsigned int length = [as length];
      s = @"";
      switch (style)
        {
	  case SummaryBriefStyle:
	    if (length > 200)
	      {
                length = 200;
		s = @"...";
	      }
	    break;
	  case SummaryRegularStyle:
	    if (length > 500)
	      {
                length = 500;
		s = @"...";
	      }
	    break;
	  case SummaryCompleteStyle:
	    break;
        }
      as = [as attributedSubstringFromRange: NSMakeRange(0, length)];
      [summary appendAttributedString: as]; 
      ADD_s_attr_INTO_summary;
    }

  /* return */
  return AUTORELEASE(summary);
}


/* Basic methods */
+ (LibrarySource *) sharedSource
{
  if (sharedInstance == nil)
    {
      sharedInstance = [[LibrarySource alloc] init];
    }
  return sharedInstance;
}

- (BOOL) loadSourceAtPath: (NSString *) path
{
  BOOL isDir, loadResult;
  NSFileManager *fm = [NSFileManager defaultManager];

  if ([super loadSourceAtPath: path] == NO) return NO;
  NSString *libraryFile = [self path];
  NSArray *array;
  if (libraryFile == nil) return NO;

  if ([fm fileExistsAtPath: libraryFile isDirectory: &isDir])
    {
      if (isDir)
        {
           NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCorrupted_, libraryFile], sQuit, nil, nil, nil);
           return NO;
        }
      else
        {
          /* Should be correct one */
	  loadResult = [self readFromFile: libraryFile];
          if (loadResult == NO)
            {
              NSRunAlertPanel(sFileError, [NSString stringWithFormat: sNotValidLibrary_, libraryFile], sQuit, nil, nil, nil);
   	      return NO;
            }
          else
            {
              makeBackup(libraryFile);
	      /* Check whether it is a new version */
              if (([self count] > 0) && 
                  ([self uniqueNumberAtIndex: 0] == NSNotFound))
	        {
	          [self _reIndexUniqueNumber];
	        }
	      return YES;
            }
        }
    }
  else
    {
      /* Check backup file */
      NSString *backup = [libraryFile stringByAppendingString: @"~"];
      loadResult = [self readFromFile: backup];
      if (loadResult == YES)
        {
	  int result = NSRunAlertPanel(sFileError, [NSString stringWithFormat: sFileNotExistWantBackup____, sLibrary, libraryFile, sLibrary, sLibrary], sYES, sNO, nil, nil);
	  if (result == NSAlertDefaultReturn)
	    {
	      /* Check whether it is a new version */
              if (([self count] > 0) && 
                  ([self uniqueNumberAtIndex: 0] == NSNotFound))
	        {
	          [self _reIndexUniqueNumber];
	        }
	      return YES;
	    }
        }
    }

  [container removeAllObjects];
  [self newItemWithTitle: sHome];
  return YES;
}

- (BOOL) unloadSourceAtPath: (NSString *) absolutePath
{
  if ([super unloadSourceAtPath: nil] == NO)
    {
      int result = NSRunAlertPanel(sFileErrorWrite, [NSString stringWithFormat: sCannotSaveLibrary_, [self path]], sDontQuit, sQuit, nil, nil);
      if (result == NSAlertDefaultReturn)
	return NO;
    }
  return YES;
}

- (void) dealloc
{
  [super dealloc];
}
@end
