/*
HMTreeController.m

Author: Makoto Kinoshita

Copyright 2004-2006 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import "HMAppKitEx.h"
#import "HMTreeController.h"

// Pboard type
NSString*   HMTreeControllerObjectIdPboardType = @"HMTreeControllerObjectIdPboardType";

// Notification
NSString*   HMTreeControllerAcceptedDrop = @"HMTreeControllerAcceptedDrop";

@implementation HMTreeController

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

- (void)dealloc
{
    [_indexKeyPath release], _indexKeyPath = nil;
    [_parentKeyPath release], _parentKeyPath = nil;
    [_draggedObjects release], _draggedObjects = nil;
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Key path --
//--------------------------------------------------------------//

- (NSString*)indexKeyPath
{
    return _indexKeyPath;
}

- (void)setIndexKeyPath:(NSString*)keyPath
{
    [_indexKeyPath release], _indexKeyPath = nil;
    _indexKeyPath = [keyPath copy];
    
    // Set sort descriptors
    NSSortDescriptor*   sortDescriptor;
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:_indexKeyPath ascending:YES];
    [self setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    [sortDescriptor release];
}

- (NSString*)parentKeyPath
{
    if (_parentKeyPath) {
        return _parentKeyPath;
    }
    
    // Get children key
    NSString*   childrenKey;
    childrenKey = [self childrenKeyPath];
    if (!childrenKey) {
        return nil;
    }
    
    // Get entity
    NSEntityDescription*    entity;
    entity = [NSEntityDescription entityForName:[self entityName] 
            inManagedObjectContext:[self managedObjectContext]];
    if (!entity) {
        return nil;
    }
    
    // Get children relationiship description
    NSRelationshipDescription*  childrenDesc;
    childrenDesc = [[entity relationshipsByName] objectForKey:childrenKey];
    if (!childrenDesc) {
        return nil;
    }
    
    // Get inverse relationship of children
    NSRelationshipDescription*  parentDesc;
    parentDesc = [childrenDesc inverseRelationship];
    if (!parentDesc) {
        return nil;
    }
    
    // Get parent key
    _parentKeyPath = [[parentDesc name] copy];
    
    return _parentKeyPath;
}

//--------------------------------------------------------------//
#pragma mark -- Delegate --
//--------------------------------------------------------------//

- (id)delegate
{
    return _delegate;
}

- (void)setDelegate:(id)delegate
{
    _delegate = delegate;
}

//--------------------------------------------------------------//
#pragma mark -- Object management --
//--------------------------------------------------------------//

- (void)refresh
{
    // Reset content
    NSManagedObjectContext* context;
    NSFetchRequest*         request;
    context = [self managedObjectContext];
    request = [[NSFetchRequest alloc] init];
    [request setEntity:[NSEntityDescription entityForName:[self entityName] inManagedObjectContext:context]];
    [request setPredicate:[self fetchPredicate]];
    [self setContent:[context executeFetchRequest:request error:NULL]];
    [request release];
}

- (void)insertObject:(id)object atArrangedObjectIndexPath:(NSIndexPath*)indexPath
{
    [super insertObject:object atArrangedObjectIndexPath:indexPath];
    
    // Check index key
    if (!_indexKeyPath) {
        return;
    }
    
    // Get arranged objects
    id  arrangedObjects;
    arrangedObjects = [self arrangedObjects];
    
    // Get parent node of inserted object
    id  parentNode;
    parentNode = [[arrangedObjects nodeAtIndexPath:indexPath] parentNode];
    
    // Update object's index
    unsigned int    i;
    for (i = 0; i < [parentNode count]; i++) {
        // Get managed object
        id                  subNode;
        NSManagedObject*    object;
        subNode = [parentNode subnodeAtIndex:i];
        object = [subNode observedObject];
        if (!object) {
            continue;
        }
        
        // Set index value
        [object setValue:[NSNumber numberWithUnsignedInt:i] forKey:_indexKeyPath];
    }
}

- (void)insertObjects:(NSArray*)objects atArrangedObjectIndexPaths:(NSArray*)indexPaths
{
    [super insertObjects:objects atArrangedObjectIndexPaths:indexPaths];
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineView data source --
//--------------------------------------------------------------//

- (id)outlineView:(NSOutlineView*)outlineView 
        objectValueForTableColumn:(NSTableColumn*)tableColumn 
        byItem:(id)item
{
	return NULL;
}

- (int)outlineView:(NSOutlineView*)outlineView 
        numberOfChildrenOfItem:(id)item
{
	return 0;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        isItemExpandable:(id)item
{
	return NO;
}

- (id)outlineView:(NSOutlineView*)outlineView 
        child:(int)index 
        ofItem:(id)item
{
	return NULL;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        writeItems:(NSArray*)items 
        toPasteboard:(NSPasteboard*)pboard
{
    // Declare types
    [pboard declareTypes:[NSArray arrayWithObject:HMTreeControllerObjectIdPboardType] owner:nil];
    
    // Create dragged object set
    if (!_draggedObjects) {
        _draggedObjects = [[NSMutableSet set] retain];
    }
    
    // Get URI representations
    NSMutableArray* URIReps;
    NSEnumerator*   enumerator;
    id              item;
    URIReps = [NSMutableArray array];
    enumerator = [items objectEnumerator];
    [_draggedObjects removeAllObjects];
    _isCopyable = YES;
    while (item = [enumerator nextObject]) {
        // Get managed object
        NSManagedObject*    object;
        object = [item observedObject];
        if (!object) {
            continue;
        }
        
        // Get URI representation
        NSString*   URIRep;
        URIRep = [[[object objectID] URIRepresentation] absoluteString];
        if (!URIRep) {
            continue;
        }
        
        // Add URI representation
        [URIReps addObject:URIRep];
        
        // Add managed object
        [_draggedObjects addObject:object];
        
        // Check copying protocol
        if (![object conformsToProtocol:@protocol(NSCopying)]) {
            _isCopyable = NO;
        }
    }
    
    if ([URIReps count] == 0) {
        return NO;
    }
    
    // Set URI representations
    [pboard setPropertyList:URIReps forType:HMTreeControllerObjectIdPboardType];
    
    // Ask to delegate
    id  delegate;
    delegate = [self delegate];
    if ([delegate respondsToSelector:
            @selector(hmTreeController:outlineView:writeItems:toPasteboard:)])
    {
        [delegate hmTreeController:self outlineView:outlineView 
                writeItems:items toPasteboard:pboard];
    }
    
    return YES;
}

- (NSDragOperation)outlineView:(NSOutlineView*)outlineView 
        validateDrop:(id <NSDraggingInfo>)info 
        proposedItem:(id)item 
        proposedChildIndex:(int)index
{
    // Get pboard
    NSPasteboard*   pboard;
    pboard = [info draggingPasteboard];
    if (!pboard) {
        return NSDragOperationNone;
    }
    
    // Check pboard type
    if (![[pboard types] containsObject:HMTreeControllerObjectIdPboardType]) {
        // Pass to delegate
        SEL selector;
        selector = @selector(hmTreeController:outlineView:validateDrop:proposedItem:proposedChildIndex:);
        if (_delegate && [_delegate respondsToSelector:selector]) {
            return [_delegate hmTreeController:self outlineView:outlineView 
                    validateDrop:info proposedItem:item proposedChildIndex:index];
        }
        
        return NSDragOperationNone;
    }
    
    // Check index
    if (index == NSOutlineViewDropOnItemIndex) {
        return NSDragOperationNone;
    }
    
    // Verify proprosed item is not ancestor of dragged item
    NSString*           parentKey;
    NSManagedObject*    droppedObject;
    BOOL                isAncestor = NO;
    parentKey = [self parentKeyPath];
    droppedObject = [item observedObject];
    while (droppedObject) {
        if ([_draggedObjects containsObject:droppedObject]) {
            isAncestor = YES;
            break;
        }
        
        droppedObject = [droppedObject valueForKey:parentKey];
    }
    
    if (isAncestor) {
        return NSDragOperationNone;
    }
    
    // Check modifier key
    if (_isCopyable) {
        NSEvent*    event;
        event = [[NSApplication sharedApplication] currentEvent];
        if ([event modifierFlags] & NSAlternateKeyMask) {
            return NSDragOperationCopy;
        }
    }
    
    return NSDragOperationMove;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        acceptDrop:(id <NSDraggingInfo>)info 
        item:(id)item 
        childIndex:(int)index
{
    // Get pboard
    NSPasteboard*   pboard;
    pboard = [info draggingPasteboard];
    if (!pboard) {
        return NO;
    }
    
    // Check pboard type
    if (![[pboard types] containsObject:HMTreeControllerObjectIdPboardType]) {
        // Pass to delegate
        SEL selector;
        selector = @selector(hmTreeController:outlineView:acceptDrop:item:childIndex:);
        if (_delegate && [_delegate respondsToSelector:selector]) {
            return [_delegate hmTreeController:self 
                    outlineView:outlineView acceptDrop:info item:item childIndex:index];
        }
        
        return NO;
    }
    
    // Check modifier key
    BOOL    isCopying = NO;
    if (_isCopyable) {
        NSEvent*    event;
        event = [[NSApplication sharedApplication] currentEvent];
        if ([event modifierFlags] & NSAlternateKeyMask) {
            isCopying = YES;
        }
    }
    
    // Get URI representations
    NSArray*    URIReps;
    URIReps = [pboard propertyListForType:HMTreeControllerObjectIdPboardType];
    if (!URIReps || ![URIReps isKindOfClass:[NSArray class]]) {
        return NO;
    }
    if ([URIReps count] == 0) {
        return NO;
    }
    
    // Get arranged objects
    id  arrangedObjects;
    arrangedObjects = [self arrangedObjects];
    
    // Get dropped object
    NSManagedObject*    droppedObject = nil;
    if (item) {
        droppedObject = [item observedObject];
    }
    else {
        if ([_delegate respondsToSelector:@selector(hmTreeControllerRootObject:)]) {
            droppedObject = [_delegate hmTreeControllerRootObject:self];
        }
    }
    
    // Get managed object context
    NSManagedObjectContext*         context;
    NSPersistentStoreCoordinator*   store;
    context = [self managedObjectContext];
    store = [context persistentStoreCoordinator];
    if (!store) {
        return NO;
    }
    
    // Get index
    unsigned int    idx = 0;
    if (index > 0) {
        // Get above object
        NSManagedObject*    object;
        if (item) {
            object = [[item subnodeAtIndex:index - 1] observedObject];
        }
        else {
            object = [arrangedObjects objectAtIndexPath:[NSIndexPath indexPathWithIndex:index - 1]];
        }
        idx = [[object valueForKey:_indexKeyPath] unsignedIntValue] + 1;
    }
    
    // Get parent key
    NSString*   parentKey;
    parentKey = [self parentKeyPath];
    
    // Get managed objects
    NSMutableArray* objects;
    NSEnumerator*   enumerator;
    NSString*       URIRep;
    objects = [NSMutableArray array];
    enumerator = [URIReps objectEnumerator];
    while (URIRep = [enumerator nextObject]) {
        // Get managed object
        NSManagedObjectID*  objectId;
        NSManagedObject*    object;
        objectId = [store managedObjectIDForURIRepresentation:[NSURL URLWithString:URIRep]];
        object = [context objectWithID:objectId];
        if (!object) {
            continue;
        }
        
        // For copying
        if (isCopying) {
            object = [object copy];
        }
        
        // Add object as child
        if (parentKey) {
            [object setValue:droppedObject forKey:parentKey];
        }
        
        // Set index value
        [object setValue:[NSNumber numberWithUnsignedInt:idx++] forKey:_indexKeyPath];
        
        // Add managed object
        [objects addObject:object];
    }
    
    // Reset index values
    unsigned int    i;
    if (item) {
        for (i = index; i < [item count]; i++) {
            // Get managed object
            NSManagedObject*    object;
            object = [[item subnodeAtIndex:i] observedObject];
            if ([objects containsObject:object]) {
                continue;
            }
            
            // Set index value
            [object setValue:[NSNumber numberWithUnsignedInt:idx++] forKey:_indexKeyPath];
        }
    }
    else {
        for (i = index; i < [arrangedObjects count]; i++) {
            // Get managed object
            NSManagedObject*    object;
            object = [arrangedObjects objectAtIndexPath:[NSIndexPath indexPathWithIndex:i]];
            if ([objects containsObject:object]) {
                continue;
            }
            
            // Set index value
            [object setValue:[NSNumber numberWithUnsignedInt:idx++] forKey:_indexKeyPath];
        }
    }
    
    // Reset content
    [self refresh];
    
    // Clear dragged objects
    [_draggedObjects removeAllObjects];
    
    // Notify event
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:HMTreeControllerAcceptedDrop object:self];
    
    return YES;
}

@end
