/*
**  AttributesViewer.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 "LibrarySource.h"
#include "AttributesViewer.h"
#include "AttributesSource.h"
#include "AttributesOutlineView.h"
#include "GNUstep.h"
#include "Constants.h"
#include "Utilities.h"
#include "RegEx.h"

static NSString *AttributesPboard = @"MyLibraryAttributesPboard";
/* use when copy and paste attributes */
static NSString *AttributesPboardType = @"MyLibraryAttributesPboardType";
/* use when copy and paste category */
static NSString *AttributesCategoryPboardType = @"MyLibraryAttributesCategoryPboardType";

static AttributesViewer *sharedInstance;

@implementation AttributesViewer

/* Not duplicated title even in different category */
- (BOOL) _isDuplicatedTitle: (NSString *) t
{
  Node *category, *attribute;
  unsigned int i, icount, j, jcount;
  icount = [[root children] count];
  for(i = 0; i < icount; i++)
    {
      category = [[root children] objectAtIndex: i];
      jcount = [[category children] count];
      for(j = 0; j < jcount; j++)
        {
	  attribute = [[category children] objectAtIndex: j];
	  if ([t isEqualToString: [attribute name]])
	    return YES;
        }
    }
  return NO;
}

- (NSString *) _stringFromFloatNumber: (NSNumber *) number
{
  unsigned int index = [numberPopUpButton indexOfSelectedItem];
  NSString *description = [number description];
  if (index == 1)
    {
       NSRange range = [description rangeOfString: @"."];
       if (range.location != NSNotFound)
         {
 	   description = [NSString stringWithFormat: @"%@,%@", [description substringToIndex: range.location], [description substringFromIndex: range.location+1]];
         }
    }
  return description;
}

- (void) _addNewNodeWithKey: (NSString *) key value: (id) value
{
  unsigned int acc = 0;
  int order;
  NSString *category, *name, *type;
  Node *categoryNode, *nameNode;

  order = orderOfAttributeKey(key);
  category = categoryOfAttributeKey(key);
  name = nameOfAttributeKey(key);
  type = typeOfAttributeKey(key);
  categoryNode = [root childWithName: category];

  NSString *proposed = name;
  while(1)
    {
      acc++;
      if ([self _isDuplicatedTitle: proposed])
        {
          proposed = [name stringByAppendingFormat: @" %d", acc];
        }
      else
	break;
    }

  if (categoryNode == nil)
    {
      categoryNode = [[Node alloc] init];
      [categoryNode setName: category];
      [root addChild: categoryNode];
      RELEASE(categoryNode);
    }
  nameNode = [categoryNode childWithName: proposed];
  if (nameNode == nil)
    {
      nameNode = [[Node alloc] init];
      [nameNode setCategory: category];
      [nameNode setName: proposed];
      [nameNode setValue: value];
      [nameNode setOrder: order];
      [nameNode setType: type];
      [categoryNode addChild: nameNode];
      RELEASE(nameNode);
     }
  categoryNode = nil;
  nameNode = nil;
}

/* accessories */
#if 0
- (void) setTitleOfViewer: (NSString *) string
{
  NSString *t = [NSString stringWithFormat: sAttributesViewer_, string];
  [viewer setTitle: t];
}
#endif

//- (void) setTitleOfAttributes: (NSString *) string
- (void) setUniqueNumber: (unsigned int) u
{
  CREATE_AUTORELEASE_POOL(pool);

  unsigned int index;
  unsigned int i, count;
  id key, value;
  LibrarySource *library = [LibrarySource sharedSource];
  AttributesSource *source= [AttributesSource sharedSource];

  //ASSIGN(title, AUTORELEASE([string copy]));
  uid = u;
  NSLog(@"uid %d", u);
  index = [library indexOfUniqueNumber: uid];
  title = [library pathOfItemAtIndex: index];
  NSString *t = [NSString stringWithFormat: sAttributesViewer_, [library titleAtIndex: index]];
  [viewer setTitle: t];

  index = [source indexOfUniqueNumber: uid];
  NSDictionary *attr = [source attributesAtIndex: index];

  if (attr == nil)
    [attributes removeAllObjects];
  else
    [attributes setDictionary: attr];
  //NSLog(@"attributes %@", attributes);

  [allNamesOfAttributes removeAllObjects];
  [allNamesOfAttributes setSet: [source namesOfAttributes]];

  /* build node tree */
  NSEnumerator *e = [[attributes allKeys] objectEnumerator];
  [[root children] removeAllObjects];
  while ((key = [e nextObject]))
    {
      value = [attributes objectForKey: key];
      [self _addNewNodeWithKey: key value: value];
    }
  /* Sort each children*/
  [root sortChildren];
  count = [[root children] count];
  for(i = 0; i < count; i++)
    {
      [[[root children] objectAtIndex: i] sortChildren];
    }

  [outlineView reloadData];
  if ([[root children] count] > 0)
    [outlineView expandItem: [[root children] objectAtIndex: 0]];
  DESTROY(pool);
}

- (NSDictionary *) attributes
{
  return AUTORELEASE([attributes copy]);;
}

/* Deal editing */

- (BOOL) control: (NSControl *) control
         textShouldBeginEditing: (NSText *) fieldEditor
{
  //NSLog(@"%@ begins editing %@ at row %d column %d", control, [fieldEditor string], [control editedRow], [control editedColumn]);
  editedStringLength = 0;
  return YES;
}

- (BOOL) control: (NSControl *) control
         textShouldEndEditing: (NSText *) fieldEditor
{
//  NSLog(@"%@ ends editing %@", control, [fieldEditor string]);
  editedStringLength = 0;
  return YES;
}

- (void) controlTextDidChange: (NSNotification *) not
{
  /* Only work for attribute name, not attribute value */
  if ([not object] != outlineView) return;

  unsigned int row = [outlineView editedRow];
  Node *item = [outlineView itemAtRow: row];
  if ([item category] == nil) return;
  if ([outlineView editedColumn] != 0) return;

  NSText *fieldEditor = [[not userInfo] objectForKey: @"NSFieldEditor"];
  NSString *original = [fieldEditor string];
  unsigned int length = [original length];
  NSString *append;
  NSRange selected;
  NSArray *names = [allNamesOfAttributes allObjects];
  /* Search for the best fit */
  if (editedStringLength < [original length])
    {
      unsigned int i, count = [names count];
      for (i = 0; i < count; i++)
       {
         if ([[names objectAtIndex: i] hasPrefix: original])
           {
  	     append = [names objectAtIndex: i];
             selected = NSMakeRange([original length], [append length]);
             [fieldEditor setString: append];
             [fieldEditor setSelectedRange: selected];
	     break;
  	   }
       }
    }
  editedStringLength = length;
}

/* outline view delegate */
- (id) outlineView: (NSOutlineView *) outlineView 
             child: (int) index 
            ofItem: (id) item
{
  if (item == nil) /* Root */
    return [[root children] objectAtIndex: index];

  /* Others */
  if ([[item children] count])
    return [[item children] objectAtIndex: index];
  else
    return nil;
}

- (BOOL) outlineView: (NSOutlineView *) outlineView
         isItemExpandable: (id) item
{
  if (item == nil) /* root */
    {
      if ([[root children] count])
	return YES;
      else
        return NO;
    }

  /* others */
  if ([[item children] count])
    return YES;
  else
    return NO;
}

- (int) outlineView: (NSOutlineView *) outlineView
        numberOfChildrenOfItem: (id) item
{
  if (item == nil) /* root */
    return [[root children] count];

  /* others */
  return [[item children] count];
}

- (id) outlineView: (NSOutlineView *) view
       objectValueForTableColumn: (NSTableColumn *) tableColumn 
       byItem: (id) item
{
  NSString *identifier = [tableColumn identifier];
  if ([identifier isEqualToString: AttributeNameIdentifier])
    {
      /* Translate file information */
      if ([[(Node *)item name] isEqualToString: FileInfoCategory])
	return sFileInfo;
      else if ([[(Node *)item name] isEqualToString: FileInfoCreationDate])
	return sCreationDate;
      else if ([[(Node *)item name] isEqualToString: FileInfoModificationDate])
	return sModificationDate;
      else if ([[(Node *)item name] isEqualToString: FileInfoSize])
	return sFileSize;
      else if ([[(Node *)item name] isEqualToString: FileInfoOwner])
	return sOwner;
      else
        return [(Node *)item name];
    }
  else if ([identifier isEqualToString: AttributeValueIdentifier])
    {
      if ([[(Node *)item type] isEqualToString: AttributeFloatType])
        {
	  return [self _stringFromFloatNumber: (NSNumber *)[item value]];
        }
      else
        return [(Node *)item value];
    }
}

- (void) outlineView: (NSOutlineView *) view
         setObjectValue: (id) object
	 forTableColumn: (NSTableColumn *) column
	 byItem: (id) item
{
  if (object == nil) return;

  if ([[column identifier] isEqualToString: AttributeNameIdentifier])
    {
      /* No change */
      if ([object isEqualToString: [(Node *)item name]])
	return;

      if ([self _isDuplicatedTitle: object])
	return;

      if ([object hasPrefix: @"-"])
	return;

      [(Node *)item setName: object];
      if ([item category] == nil) /* Category */
        {
	  /* update all children */
	  NSArray *nodes = [item children];
	  unsigned int i, count = [nodes count];
	  for(i = 0; i < count; i++)
	    {
	      [[nodes objectAtIndex: i] setCategory: object];
	    }
	  [root sortChildren];
	  [outlineView reloadData];
        }
    }
  else
    {
      int index;
      NSString *testString = [object stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];

      unsigned dateIndex = [datePopUpButton indexOfSelectedItem];
      unsigned numberIndex = [numberPopUpButton indexOfSelectedItem];
      RegExFloatNumber numberType = RegExDotFloatNumber;
      RegExDate dateType = RegExMMDDYYYYDate;
      RegExType type;
      id returnObject;

      if (dateIndex == 1) dateType = RegExDDMMYYYYDate;
      else if (dateIndex == 2) dateType = RegExYYYYMMDDDate;

      if (numberIndex == 1) numberType = RegExCommaFloatNumber;
      NSLog(@"object %@, number %d, date %d", object, numberType, dateType);
      returnObject = objectFromRegExParsedString(object, numberType, dateType, &type);

      RETAIN(returnObject);
      NSLog(@"Type %d, return %@", type, returnObject);
      if (type == RegExCalendarDateType)
	[returnObject setCalendarFormat: CalendarDateDisplayFormat];
      [(Node *)item setValue: returnObject];
      RELEASE(returnObject);

      switch(type)
        {
          case RegExStringType:
            [(Node *)item setType: AttributeStringType];
	    break;
          case RegExCalendarDateType:
            [(Node *)item setType: AttributeCalendarDateType];
	    break;
          case RegExFloatNumberType:
            [(Node *)item setType: AttributeFloatType];
	    break;
          case RegExIntegerType:
            [(Node *)item setType: AttributeIntegerType];
	    break;
	}
    }

  return;
}

- (void) outlineView: (NSTableView *) view
         didClickIconOfItem: (Node *) item
{
  unsigned int index;
  if ([item category] == nil) /* category */
    {
      if ([[item name] isEqualToString: FileInfoCategory])
        {
	  /* Refresh file information */
	  //NSString *path = [[[LibraryLocation stringByStandardizingPath] stringByAppendingPathComponent: LibraryPath] stringByAppendingPathComponent: title];
	  NSString *path = [MyLibraryPath(nil) stringByAppendingPathComponent: title];
	  //NSLog(@"path %@", path);
	  NSDictionary *dict = attributesFromFile(path);
	  //NSLog(@"new %@", dict);
	  NSArray *allKeys = [dict allKeys];
	  unsigned int i, count = [allKeys count];
	  id key, value;
	  [[item children] removeAllObjects];
	  for(i = 0; i < count; i++)
	    {
	      key = [allKeys objectAtIndex: i];
	      value = [dict objectForKey: key];
	      [self _addNewNodeWithKey: key value: value];
	    }
	  [item sortChildren];
	}
      else
        {
          index = [[root children] indexOfObject: item];
          //NSLog(@"Remove category %@", [[[root children] objectAtIndex: index] name]);
          [root removeChildAtIndex: index];
	}
    }
  else /* attribute */
    {
      Node *cat = [root childWithName: [item category]];
      index = [[cat children] indexOfObject: item];
      //NSLog(@"Remove category %@", [[[cat children] objectAtIndex: index] name]);
      [cat removeChildAtIndex: index];
      [cat resetOrderOfChildren];
    }
  [view reloadData];
}

- (BOOL) outlineView: (NSOutlineView *) view
         shouldEditTableColumn: (NSTableColumn *) column
	 item: (id) item
{
  if ([item category] == nil) /* Category */
    {
      if ([[(Node *)item name] isEqualToString: FileInfoCategory])
        return NO;
      if ([[column identifier] isEqualToString: AttributeValueIdentifier])
	return NO;
    }
  else if ([[item category] isEqualToString: FileInfoCategory])
    {
        return NO;
    }
  else if ([[column identifier] isEqualToString: AttributeValueIdentifier])
    {
      /* convert value into NSString.
       * After editing or cancelEditing,
       * setObjectValue will be called 
       * and it will be converted into proper class
       */
      if ([[(Node *)item type] isEqualToString: AttributeCalendarDateType])
        {
	  /* change format before editiong */
	  unsigned int index = [datePopUpButton indexOfSelectedItem];
	  NSString *format;
	  if (index == 1)
	    format = CalendarDateDDMMYYYYInputFormat;
	  else if (index == 2)
	    format = CalendarDateYYYYMMDDInputFormat;
	  else 
	    format = CalendarDateMMDDYYYYInputFormat;
	  NSCalendarDate *date = [[(Node *)item value] dateWithCalendarFormat: format timeZone: nil];;
	  [(Node *)item setValue: [date description]];
        }
      else if ([[(Node *)item type] isEqualToString: AttributeFloatType])
        {
	  /* change format before editiong */
	  unsigned int index = [numberPopUpButton indexOfSelectedItem];
	  id value = [(Node *)item value];
	  if ([value isKindOfClass: [NSNumber class]])
	  [(Node *)item setValue: [self _stringFromFloatNumber: value]];
        }
      else if ([[(Node *)item type] isEqualToString: AttributeIntegerType])
        {
	  id value = [(Node *)item value];
	  [(Node *)item setValue: [value description]];
	}
    }
  return YES;
}

- (NSImage *) outlineView: (NSOutlineView *) view
              iconForItem: (Node *) item
{
  if ([item category] == nil) 
    {
      if ([[item name] isEqualToString: FileInfoCategory])
        {
          return [NSImage imageNamed: @"refresh12.png"];
        }
      return [NSImage imageNamed: @"remove12.png"];
    }
  else if ([[item category] isEqualToString: FileInfoCategory])
    {
      return nil;
    }
  else
    {
      return [NSImage imageNamed: @"remove12.png"];
    }
}

- (BOOL) outlineView: (NSOutlineView *) view
         writeItems: (NSArray *) items
	 toPasteboard: (NSPasteboard *) pboard
{
  /* Only allow one selection for drag-and-drop */
  //NSLog(@"%@ write %@ to %@", view, items, pboard);
  if ([items count] > 1) return NO;

  BOOL success;
  NSMutableArray *array = [[NSMutableArray alloc] init];
  Node *item = [items objectAtIndex: 0];;
#if 0 /* Category is sorted by alphabet */
  if ([item category] == nil)
     {
       /* Category */
       [pboard declareTypes: [NSArray arrayWithObject: AttributesCategoryPboardType]
	             owner: nil];
       success = [pboard setString: [item name]
                         forType: AttributesCategoryPboardType];
     }
  else
#endif
    {
      /* Attribute */
      [pboard declareTypes: [NSArray arrayWithObject: AttributesPboardType]
	            owner: nil];
       success = [pboard setString: [item name]
	                 forType: AttributesPboardType];
    }
  return success;
}

- (NSDragOperation) outlineView: (NSOutlineView *) view
                    validateDrop: (id <NSDraggingInfo>) info
		    proposedItem: (id) item
		    proposedChildIndex: (int) index
{
  if (outlineView != [info draggingSource])
    {
      return NSDragOperationNone;
    }

  NSPasteboard *pboard = [info draggingPasteboard];
  if ([[pboard types] containsObject: AttributesCategoryPboardType])
    {
#if 0 /* Category is sorted by alphabet */
      /* Category */
      if (item)
	return NSDragOperationNone;
      if (index == NSOutlineViewDropOnItemIndex)
	return NSDragOperationNone;

      return NSDragOperationMove;
#endif
      return NSDragOperationNone;
    }
  else
    {
      /* item */
      if (item == root)
	return NSDragOperationNone;
      if (index == NSOutlineViewDropOnItemIndex)
	return NSDragOperationNone;
      if ([[(Node *)item name] isEqualToString: FileInfoCategory])
	return NSDragOperationNone;

      //NSLog(@"drop on %@ at %d", [(Node *)item name], index);
      return NSDragOperationMove;
    }
}

- (BOOL) outlineView: (NSOutlineView *) view
         acceptDrop: (id <NSDraggingInfo>) info
	 item: (id) item
	 childIndex: (int) index
{
  if (outlineView != [info draggingSource])
    {
      return NSDragOperationNone;
    }

  NSPasteboard *pboard = [info draggingPasteboard];
  NSString *name;
  Node *node;
  unsigned int original;
  if ([[pboard types] containsObject: AttributesCategoryPboardType])
    {
#if 0 /* Category is sorted by alphabet */
      /* Category */
      if (item)
	return NO;
      if (index == NSOutlineViewDropOnItemIndex)
	return NO;

      name = [pboard stringForType: AttributesCategoryPboardType];
      node = [root childWithName: name];
      original = [[root children] indexOfObject: node];
      if (original < index)
        {
          [[root children] insertObject: node atIndex: index];
	  [root removeChildAtIndex: original];
	}
      else if (original > index)
        {
          [[root children] insertObject: node atIndex: index];
	  [root removeChildAtIndex: original+1];
        }
      [outlineView reloadData];
      return YES;
#endif
    }
  else
    {
      /* Item */
      name = [pboard stringForType: AttributesPboardType];
      NSString *newCategoryName = [(Node *)item name];
      Node *newCategory = item;
      /* Find the old category */
      Node *oldCategory;
      unsigned int i, count = [[root children] count];
      unsigned int original;
      for(i = 0; i < count; i++)
        {
	  oldCategory = [[root children] objectAtIndex: i];
          node = [oldCategory childWithName: name];
	  if (node != nil)
	    {
	      break;
	    }
        }
      if (node == nil) return NO;

      if (oldCategory == newCategory)
        {
	  original = [[oldCategory children] indexOfObject: node];
          if (original < index)
            {
              [[oldCategory children] insertObject: node atIndex: index];
	      [oldCategory removeChildAtIndex: original];
	    }
          else if (original > index)
            {
              [[oldCategory children] insertObject: node atIndex: index];
	      [oldCategory removeChildAtIndex: original+1];
            }
	  [oldCategory resetOrderOfChildren];
        }
      else
        {
	  if ([newCategory nameExistsInChildren: name])
	    return NO;

	  if ([[oldCategory name] isEqualToString: FileInfoCategory])
	    {
	      id key = [node keyOfItem];
	      id value = [node value];
	      [self _addNewNodeWithKey: keyOfAttributes(index,
			                                newCategoryName,
							nameOfAttributeKey(key),
							typeOfAttributeKey(key))
		                 value: value];
	    }
	  else
	    {
	      [node setCategory: newCategoryName];
	      [[newCategory children] insertObject: node atIndex: index];
	      [[oldCategory children] removeObject: node];
	    }
	  [newCategory resetOrderOfChildren];
        }
      [outlineView reloadData];
      return YES;
    }
  return NO;
}

/* window delegate */
- (BOOL) windowShouldClose: (id) sender
{
  [NSApp stopModal];
  return YES;
}

/* actions */
- (void) okAction: (id) sender
{
  /* Generate Attributes */
  [attributes removeAllObjects];

  id key, value, object;
  NSEnumerator *e;
  unsigned int i, count = [[root children] count];
  for(i = 0; i < count; i++)
    {
      e = [[[[root children] objectAtIndex: i] children] objectEnumerator];
      while((object = [e nextObject]))
        {
	  key = [object keyOfItem];
	  value = [object value];
	  [attributes setObject: value forKey: key];
        }
    }
  //NSLog(@"%@", [attributes description]);
  AttributesSource *source = [AttributesSource sharedSource];
  unsigned int index;
  index = [source indexOfUniqueNumber: uid];
  if (index == NSNotFound)
    {
      index = [source newTitle: title];
      [source setUniqueNumber: uid atIndex: index];
    }
  [source setAttributes: attributes atIndex: index];

  [viewer performClose: sender];
}

- (void) cancelAction: (id) sender
{
  [viewer performClose: sender];
}

- (void) newCategoryAction: (id) sender
{
  unsigned int acc = 0;
  NSString *category = @"Customized Attributes";
  NSString *proposed = category;
  while(1)
    {
      if ([root nameExistsInChildren: proposed] == NO)
	break;

      proposed = [NSString stringWithFormat: @"%@ %d", category, acc];
      acc++;
    }
  Node *new = [[Node alloc] init];
  [new setName: proposed];
  [root addChild: new];
  RELEASE(new);
  [outlineView reloadData];
  unsigned int row;
  row = [outlineView rowForItem: new];
  [outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: row]
               byExtendingSelection: NO];
}

- (void) newAttributeAction: (id) sender
{
  unsigned int acc = 0;
  int selectedRow;
  NSString *attr = @"New Attribute";
  NSString *proposed = attr;
  Node *category, *node;
  if ([[root children] count] == 0)
    {
      [self newCategoryAction: sender];
      category = [[root children] lastObject];
    }

  selectedRow = [outlineView selectedRow];
  if ((selectedRow == NSNotFound) || (selectedRow < 0))
    {
      [outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
                   byExtendingSelection: NO];
      selectedRow = [outlineView selectedRow];
    }

  node = [outlineView itemAtRow: selectedRow];
  if ([node category] == nil)
    category = node;
  else
    category = [root childWithName: [node category]];

  if ([[category name] isEqualToString: FileInfoCategory])
    {
      category = [[root children] lastObject];
      if ([[category name] isEqualToString: FileInfoCategory])
        {
          [self newCategoryAction: sender];
           category = [[root children] lastObject];
	}
    }

  while(1)
    {
//      if ([category nameExistsInChildren: proposed] == NO)
      if ([self _isDuplicatedTitle: proposed] == NO)
	break;

      proposed = [NSString stringWithFormat: @"%@ %d", attr, acc];
      acc++;
    }
  Node *new = [[Node alloc] init];
  [new setCategory: [category name]];
  [new setName: proposed];
  [new setValue: @"New Value"];
  [new setOrder: [[category children] count]];
  [new setType: AttributeStringType];
  [category addChild: new];
  [outlineView reloadData];
  [outlineView expandItem: category];
}

- (void) popupButtonWillPop: (NSNotification *) not
{
#ifndef GNUSTEP
  [outlineView cancelOperation: self];
#endif
}

- (void) numberPopUpButtonAction: (id) sender
{
  /* reload data in order to display new format */
  [outlineView reloadData];
}

- (void) datePopUpButtonAction: (id) sender
{
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  NSString *format;
  unsigned int index = [sender indexOfSelectedItem];
  switch(index)
    {
      case 1:
        format = CalendarDateDDMMYYYYInputFormat;
        break;
      case 2:
        format = CalendarDateYYYYMMDDInputFormat;
        break;
      default:
        format = CalendarDateMMDDYYYYInputFormat;
    }
  [defaults setObject: format forKey: CalendarInputFormatDefault];
}

- (void) cut: (id) sender
{
//  NSLog(@"%@ cut", sender);
}

- (void) copy: (id) sender
{
  NSPasteboard *pboard = [NSPasteboard pasteboardWithName: AttributesPboard];
  [pboard declareTypes: [NSArray arrayWithObjects: AttributesCategoryPboardType,
                                                   AttributesPboardType, nil]
		  owner: nil];

  NSIndexSet *indexSet = [outlineView selectedRowIndexes];
  unsigned int maxCount = [indexSet count];
  unsigned int *buffer = malloc(sizeof(unsigned int) * maxCount);
#ifdef GNUSTEP
  unsigned int c = [outlineView numberOfRowsInTableView: outlineView];
  NSLog(@"c %d", c);
  NSRange r = NSMakeRange(0, c);
  unsigned int result = [indexSet getIndexes: buffer maxCount: maxCount inIndexRange: &r];
#else
  unsigned int result = [indexSet getIndexes: buffer maxCount: maxCount inIndexRange: NULL];
#endif
  if (result == 0) return;

  unsigned int i, index;
  unsigned int j, jcount;
  id key, value;
  Node *item;
  NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
  NSMutableDictionary *cat = [[NSMutableDictionary alloc] init];

  for(i = 0; i < result; i++)
    {
      index = *(buffer+i);
      item = [outlineView itemAtRow: index];
      if ([item category] == nil)
        {
          if (([outlineView isItemExpanded: item] == NO) ||
	      (([outlineView isItemExpanded: item] == YES) && (result == 1))
	     )
            {
              /* Category collapsed. Copy all. */
	      /* Special case:
	       * if only one selected and it's category
	       * copy all item under this category
	       */
	      Node *node;
	      jcount = [[item children] count];
	      for(j = 0; j < jcount; j++)
	        {
	          node = [[item children] objectAtIndex: j];
                  key = [node keyOfItem];
                  value = [node value];
                  [cat setObject: value forKey: key];
		}
	    }
	  else
	    {
	      /* Ignore expanded item */
	      continue;
	    }
	}
      else /* Copy item */
        {
	  key = [item keyOfItem];
	  value = [item value];
	  [dict setObject: value forKey: key];
        }
    }
  //NSLog(@"copy %@", dict);
  //NSLog(@"copy %@", cat);
  [pboard setPropertyList: dict forType: AttributesPboardType];
  [pboard setPropertyList: cat forType: AttributesCategoryPboardType];
  AUTORELEASE(dict);
  AUTORELEASE(cat);
}

- (void) paste: (id) sender
{
  NSPasteboard *pboard = [NSPasteboard pasteboardWithName: AttributesPboard];
  NSDictionary *dict, *cat;

  if (([[pboard types] containsObject: AttributesCategoryPboardType] == NO) ||
      ([[pboard types] containsObject: AttributesPboardType] == NO))
    {
      return; 
    }
  else 
    {
      dict = [pboard propertyListForType: AttributesPboardType];
      cat = [pboard propertyListForType: AttributesCategoryPboardType];
    }

  NSArray *allKeys;
  unsigned int i, count;
  unsigned int selectedRow = [outlineView selectedRow];
  Node *item = [outlineView itemAtRow: selectedRow];
  Node *category;
  NSString *newCategory;

  id value;
  NSString *key, *newKey;

  if ([dict count])
    {
      /* Substitue categoriy */
      if (item == nil) /* No category */
        {
          [self newCategoryAction: sender];
          item = [[root children] lastObject];
        }

      if ([item category] == nil) /* Category */
        {
          newCategory = [item name];
          category = item;
        }
      else
        {
          newCategory = [item category];
          category = [root childWithName: newCategory];
        }

      /* Avoid add attributes into file information */
      if ([newCategory isEqualToString: FileInfoCategory])
        {
          [self newCategoryAction: sender];
          category = [[root children] lastObject];
          newCategory = [category name];
        }

      allKeys = [dict allKeys];
      count = [allKeys count];
      for(i = 0; i < count; i++)
        {
          key = [allKeys objectAtIndex: i];
          value = [dict objectForKey: key];
          /* build new key when category changed */
          newKey = keyOfAttributes(i, newCategory, 
   		                   nameOfAttributeKey(key), 
		                   typeOfAttributeKey(key));
          [self _addNewNodeWithKey: newKey value: value];
        }
      [category resetOrderOfChildren];
      [outlineView reloadData];
      [outlineView expandItem: category];
    }

  if ([cat count])
    {
      /* Without substitute category */
      allKeys = [cat allKeys];
      count = [allKeys count];
      for(i = 0; i < count; i++)
        {
          key = [allKeys objectAtIndex: i];
          value = [cat objectForKey: key];
          [self _addNewNodeWithKey: key value: value];
	  category = [root childWithName: categoryOfAttributeKey(key)];
	  [outlineView reloadData];
	  [outlineView expandItem: category];
        }
    }
}

/* basic methods */

+ (AttributesViewer *) sharedViewer
{
  if (sharedInstance == nil)
    {
      sharedInstance = [[AttributesViewer alloc] init];
    }
  return sharedInstance;
}

- (void) awakeFromNib
{
  //NSLog(@"AttributesViewer awakeFromNib");
  /* Make sure PopUpButton is selected */
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

  NSString *separator = [defaults stringForKey: NSDecimalSeparator];
  if ([separator isEqualToString: @","])
    [numberPopUpButton selectItemAtIndex: 1];
  else
    [numberPopUpButton selectItemAtIndex: 0];

  NSString *format = [defaults stringForKey: CalendarInputFormatDefault];
  if ([format isEqualToString: CalendarDateDDMMYYYYInputFormat])
    [datePopUpButton selectItemAtIndex: 1];
  else if ([format isEqualToString: CalendarDateYYYYMMDDInputFormat])
    [datePopUpButton selectItemAtIndex: 2];
  else
    [datePopUpButton selectItemAtIndex: 0];

  [outlineView registerForDraggedTypes: [NSArray arrayWithObjects: AttributesCategoryPboardType, AttributesPboardType, nil]];
}

- (void) makeViewerKeyAndOrderFront: (id) sender
{
  [viewer makeKeyAndOrderFront: sender];
  [NSApp runModalForWindow: viewer];
  [outlineView reloadData];
  if ([[root children] count] > 0)
    [outlineView expandItem: [[root children] objectAtIndex: 0]];
  //[outlineView setAutoresizesAllColumnsToFit: YES];
}

- (id) init
{
  self = [super init];

  BOOL result = [NSBundle loadNibNamed: @"AttributesViewer" owner: self];
  if (result == NO)
    {
      NSLog(@"Unable to load AttributesViewer interface");
      return nil;
    }

  attributes = [[NSMutableDictionary alloc] init];
  root = [[Node alloc] init];

  allNamesOfAttributes = [[NSMutableSet alloc] init];
  editedStringLength = 0;

  /* Listen to NSPopUpButtonWillPopUpNotification */
  [[NSNotificationCenter defaultCenter] addObserver: self
	                          selector: @selector(popupButtonWillPop:)
				  name: NSPopUpButtonWillPopUpNotification
				  object: nil];

  return self;
}

- (void) dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver: self];
  RELEASE(title);
  RELEASE(attributes);
  RELEASE(allNamesOfAttributes);
  RELEASE(root);
  [super dealloc];
}

@end
