/*
SRPrefController.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 "SRBookmark.h"
#import "SRWebHistory.h"

#import "SRAppController.h"

#import "SRPreference.h"
#import "SRPrefController.h"
#import "SRPrefView.h"
#import "SRPrefBackForwardToolbarItem.h"
#import "SRPlugInController.h"

#import "SRMenu.h"

// Support file name
NSString*   SRPrefDirectoryName = @"Preferences";

// Toolbar identifier
NSString*   SRPrefToolbarIdentifier = @"SRPrefToolbarIdentifier";
NSString*   SRPrefToolbarLabelTable = @"PrefToolbar";

// Toolbar items
NSString*   SRPrefGoBackItem = @"GoBack";
NSString*   SRPrefGoForwardItem = @"GoForward";
NSString*   SRPrefBackForwardItem = @"BackForwad";
NSString*   SRPrefShowAllItem = @"ShowAll";

// Frame auto save name
NSString*   SRPrefPanelFrameAutoSaveName = @"SRPrefPanelFrameAutoSaveName";

@interface SRPrefController (private)
// Accessing to preferences
- (void)_loadPreferences;

// Action
- (void)showAllAction:(id)sender;

@end

@implementation SRPrefController

//--------------------------------------------------------------//
#pragma mark -- Preferences path --
//--------------------------------------------------------------//

+ (NSArray*)_plugInPreferencesPathsAtDirectory:(NSString*)dirPath
{
    NSMutableArray* prefPaths;
    prefPaths = [NSMutableArray array];
    
    // Check existense
    NSFileManager*	fileMgr;
    BOOL            isDir;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:dirPath isDirectory:&isDir] || !isDir) {
        return prefPaths;
    }
    
    // Check plug-ins
    NSEnumerator*   enumerator;
    NSString*       dirContent;
    enumerator = [[fileMgr directoryContentsAtPath:dirPath] objectEnumerator];
    while (dirContent = [enumerator nextObject]) {
        // Check extension
        NSString*   path;
        path = [dirPath stringByAppendingPathComponent:dirContent];
        if (![[path pathExtension] isEqualToString:@"plugin"]) {
            continue;
        }
        
        // Create bundle
        NSBundle*   bundle;
        bundle = [[NSBundle alloc] initWithPath:path];
        
        // Get preferences path
        NSString*   prefPath;
        prefPath = [[bundle builtInPlugInsPath] 
                stringByAppendingPathComponent:SRPrefDirectoryName];
        if ([fileMgr fileExistsAtPath:prefPath isDirectory:&isDir] && isDir) {
            [prefPaths addObject:prefPath];
        }
        
        [bundle release];
    }
    
    return prefPaths;
}

+ (NSString*)buildInPreferencesPath
{
    NSString*   prefPath;
    prefPath = [[[NSBundle mainBundle] builtInPlugInsPath] 
            stringByAppendingPathComponent:SRPrefDirectoryName];
    
    return prefPath;
}

+ (NSArray*)buildInPlugInPreferencesPaths
{
    return [self _plugInPreferencesPathsAtDirectory:
            [SRPlugInController buildInPlugInPath]];
}

+ (NSString*)userPreferencesPath
{
    NSString*   prefPath;
    prefPath = HMApplicationSupportFolder(@"Shiira");
    prefPath = [prefPath stringByAppendingPathComponent:SRPrefDirectoryName];
    
    return prefPath;
}

+ (NSArray*)userPlugInPreferencesPaths
{
    return [self _plugInPreferencesPathsAtDirectory:
            [SRPlugInController userPlugInPath]];
}

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

+ (id)sharedInstance
{
    static SRPrefController*    _sharedInstance = nil;
    if (!_sharedInstance) {
        _sharedInstance = [[SRPrefController alloc] init];
    }
    
    return _sharedInstance;
}

- (id)init
{
    self = [super initWithWindowNibName:@"PrefPanel"];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    _backForwardList = [[NSMutableArray array] retain];
    _currentBackForwardIndex = 0;
    _prefBundles = [[NSMutableArray array] retain];
    _prefDict = [[NSMutableDictionary dictionary] retain];
    
    return self;
}

- (void)windowDidLoad
{
    // Configure window
    HMWindow*   window;
    window = (HMWindow*)[self window];
    [window setFrameAutosaveName:SRPrefPanelFrameAutoSaveName];
    [window setPassMenuValidationToDelegate:YES];
    
    // Store original title
    _title = [[NSString stringWithString:[window title]] retain];
    
    // Retain pref view to avoide unintentional release
    [_prefView retain];
    
    // Create toolbar
    NSToolbar*  toolbar;
    toolbar = [[NSToolbar alloc] 
            initWithIdentifier:SRPrefToolbarIdentifier];
    [toolbar setDelegate:self];
    [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
    [toolbar setAllowsUserCustomization:NO];
    [toolbar setAutosavesConfiguration:NO];
    [window setToolbar:toolbar];
    [window setShowsToolbarButton:NO];
    [toolbar release];
    
    // Register notifications
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(preferenceSelected:) 
            name:SRPrefViewPreferenceSelected object:_prefView];
    
    // Load preferences
    [self _loadPreferences];
    
    // Show all to resize window
    [self selectAllPrefView];
    [_backForwardList addObject:@"All"];
    _currentBackForwardIndex = 0;
}

- (void)dealloc
{
    // Remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [_title release], _title = nil;
    [_backForwardList release], _backForwardList = nil;
    [_prefBundles release], _prefBundles = nil;
    [_prefDict release], _prefDict = nil;
    [_prefView release], _prefView = nil;
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- View management --
//--------------------------------------------------------------//

- (void)swapWithView:(NSView*)view
{
    // Get content view
    NSWindow*   window;
    NSView*     contentView;
    window = [self window];
    contentView = [window contentView];
    if (view == contentView) {
        return;
    }
    
    // Remove old subviews
    [[contentView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
    
    // Get frame size
    NSRect  windowFrame, contentFrame, newContentFrame;
    windowFrame = [window frame];
    contentFrame = [contentView frame];
    newContentFrame = [view frame];
    
    // Calc new frame
    NSRect  newWindowFrame;
    float   dheight;
    dheight = newContentFrame.size.height - contentFrame.size.height;
    newWindowFrame.origin.x = windowFrame.origin.x;
    newWindowFrame.origin.y = windowFrame.origin.y - dheight;
    newWindowFrame.size.width = newContentFrame.size.width;
    newWindowFrame.size.height = windowFrame.size.height + dheight;
    
	// Resize window
	[window setFrame:newWindowFrame display:YES animate:YES];
	
    // Add new view
    [contentView addSubview:view];
}

//--------------------------------------------------------------//
#pragma mark -- Accessing to preferences --
//--------------------------------------------------------------//

- (id)selectedPreference
{
    return _selectedPreference;
}

- (NSArray*)backForwardList
{
    return _backForwardList;
}

- (int)currentBackForwardIndex
{
    return _currentBackForwardIndex;
}

- (void)_loadPreferencesAtDirectory:(NSString*)prefPath 
        intoPrefBundleIds:(NSMutableArray*)prefBundleIds 
        prefIcons:(NSMutableArray*)prefIcons 
        prefLabels:(NSMutableArray*)prefLabels
{
    // Load prefrence bundles
    NSArray*        dirContents;
    NSEnumerator*   enumerator;
    NSString*       dirContent;
    dirContents = [[NSFileManager defaultManager] directoryContentsAtPath:prefPath];
    enumerator = [dirContents objectEnumerator];
    while (dirContent = [enumerator nextObject]) {
        // Check extention
        if (![dirContent hasSuffix:@".pref"]) {
            continue;
        }
        
        // Create bundle path
        NSString*   bundlePath;
        bundlePath = [prefPath stringByAppendingPathComponent:dirContent];
        
        // Create bundle
        NSBundle*       bundle;
        NSDictionary*   infoPlist;
        bundle = [NSBundle bundleWithPath:bundlePath];
        infoPlist = [bundle infoDictionary];
        if (!bundle || !infoPlist) {
            continue;
        }
        
        // Get bundle ID
        NSString*   bundleId;
        bundleId = [bundle bundleIdentifier];
        if (!bundleId) {
            continue;
        }
        if ([prefBundleIds containsObject:bundleId]) {
            // Error
            NSLog(@"Duplicated bundle ID, %@", bundleId);
            continue;
        }
        
        // Get icon
        NSString*   iconPath;
        NSImage*    icon = nil;
        iconPath = [infoPlist objectForKey:@"SRPrefPanelIconFile"];
        if (iconPath) {
            iconPath = [[bundle resourcePath] stringByAppendingPathComponent:iconPath];
            icon = [[NSImage alloc] initWithContentsOfFile:iconPath];
            if (icon) {
                [icon setSize:NSMakeSize(32.0f, 32.0f)];
                [icon autorelease];
            }
        }
        
        // Get label
        NSString*   label;
        label = [bundle localizedStringForKey:@"SRPrefPanelIconLabel" value:nil table:@"InfoPlist"];
        if (!label || [label isEqualToString:@"SRPrefPanelIconLabel"]) {
            label = [infoPlist objectForKey:@"SRPrefPanelIconLabel"];
        }
        
        // Add bundle info
        [_prefBundles addObject:bundle];
        [prefBundleIds addObject:bundleId];
        if (icon) {
            [prefIcons addObject:icon];
        }
        else {
            [prefIcons addObject:[NSNull null]];
        }
        if (label) {
            [prefLabels addObject:label];
        }
        else {
            [prefLabels addObject:[NSNull null]];
        }
    }
}

- (void)_loadPreferences
{
    NSMutableArray* prefBundleIds;
    NSMutableArray* prefIcons;
    NSMutableArray* prefLabels;
    prefBundleIds = [NSMutableArray array];
    prefIcons = [NSMutableArray array];
    prefLabels = [NSMutableArray array];
    
    // Load build-in preferences
    NSString*   prefPath;
    prefPath = [[self class] buildInPreferencesPath];
    [self _loadPreferencesAtDirectory:prefPath 
            intoPrefBundleIds:prefBundleIds 
            prefIcons:prefIcons 
            prefLabels:prefLabels];
    
    // Load buid-in plug-in preferences
    NSArray*        prefPaths;
    NSEnumerator*   enumerator;
    prefPaths = [[self class] buildInPlugInPreferencesPaths];
    enumerator = [prefPaths objectEnumerator];
    while (prefPath = [enumerator nextObject]) {
        [self _loadPreferencesAtDirectory:prefPath 
                intoPrefBundleIds:prefBundleIds 
                prefIcons:prefIcons 
                prefLabels:prefLabels];
    }
    
    // Configure pref view
    [_prefView setPreferenceIds:prefBundleIds icons:prefIcons labels:prefLabels];
}

- (NSBundle*)_preferenceBundleWithIdentifier:(NSString*)bundleId
{
    // Check argument
    if (!bundleId) {
        return nil;
    }
    
    // Get bundle
    NSEnumerator*   enumerator;
    NSBundle*       bundle;
    enumerator = [_prefBundles objectEnumerator];
    while (bundle = [enumerator nextObject]) {
        if ([bundleId isEqualToString:[bundle bundleIdentifier]]) {
            return bundle;
        }
    }
    
    return nil;
}

- (void)selectPreferenceWithBundleIdentifier:(NSString*)bundleId
{
    // Check argument
    if (!bundleId) {
        return;
    }
    
    // Get preference instance
    id  pref;
    pref = [_prefDict objectForKey:bundleId];
    if (!pref) {
        // Get bundle
        NSBundle*   bundle;
        bundle = [self _preferenceBundleWithIdentifier:bundleId];
        if (!bundle) {
            return;
        }
        
        // Load bundle
        if (![bundle isLoaded]) {
            if (![bundle load]) {
                // Error
                NSLog(@"Failed to load the bundle, %@", bundleId);
                return;
            }
        }
        
        // Create preference instance
        Class   principalClass;
        principalClass = [bundle principalClass];
        if (!principalClass) {
            return;
        }
        pref = [[principalClass alloc] initWithBundle:bundle];
        if (!pref) {
            // Error
            NSLog(@"Failed to create preference instance, %@", bundleId);
            return;
        }
        
        // Set controllers
        [pref setAppController:[SRAppController sharedInstance]];
        
        id  context, store;
        context = [[SRAppController sharedInstance] managedObjectContext];
        store = [[SRAppController sharedInstance] persistentStore];
        id  bookmarkFactory;
        bookmarkFactory = [SRBookmarkFactory 
                sharedInstanceWithManagedObjectContext:context persistentStore:store];
        [pref setBookmarkFactory:bookmarkFactory];
        
        [pref setWebHistory:[SRWebHistory sharedInstance]];
        
        [pref didLoad];
        
        // Set to pref dict
        [_prefDict setObject:pref forKey:bundleId];
        [pref release];
    }
    
    // Get main view
    NSView* mainView;
    mainView = [pref mainView];
    if (!mainView) {
        return;
    }
    
    // Swap view
    [self swapWithView:mainView];
    
    // Get title
    NSString*   title;
    title = [pref title];
    if (!title) {
        title = _title;
    }
    [[self window] setTitle:title];
    
    // Set selected preference
    _selectedPreference = pref;
}

- (void)selectAllPrefView
{
    // Swap with pref view
    [self swapWithView:_prefView];
    
    // Set original title
    [[self window] setTitle:_title];
    
    // Clear selected preference
    _selectedPreference = nil;
}

//--------------------------------------------------------------//
#pragma mark -- Font handling --
//--------------------------------------------------------------//

- (void)changeFont:(id)sender
{
    // Pass this action to preference
    id  pref;
    pref = [self selectedPreference];
    if (pref && [pref respondsToSelector:@selector(changeFont:)]) {
        [pref changeFont:sender];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Actions --
//--------------------------------------------------------------//

- (void)goBackAction:(id)sender
{
    // Decrement current index
    if (_currentBackForwardIndex == 0) {
        return;
    }
    _currentBackForwardIndex--;
    
    // Go back
    NSString*   identifier;
    identifier = [_backForwardList objectAtIndex:_currentBackForwardIndex];
    if ([identifier isEqualToString:@"All"]) {
        [self selectAllPrefView];
    }
    else {
        [self selectPreferenceWithBundleIdentifier:identifier];
    }
}

- (void)goForwardAction:(id)sender
{
    // Decrement current index
    if (_currentBackForwardIndex == [_backForwardList count] - 1) {
        return;
    }
    _currentBackForwardIndex++;
    
    // Go back
    NSString*   identifier;
    identifier = [_backForwardList objectAtIndex:_currentBackForwardIndex];
    if ([identifier isEqualToString:@"All"]) {
        [self selectAllPrefView];
    }
    else {
        [self selectPreferenceWithBundleIdentifier:identifier];
    }
}

- (void)showAllAction:(id)sender
{
    // Get content view
    NSWindow*   window;
    NSView*     contentView;
    NSArray*    subviews;
    window = [self window];
    contentView = [window contentView];
    subviews = [[[self window] contentView] subviews];
    if ([subviews count] > 0 && [subviews objectAtIndex:0] == _prefView) {
        return;
    }
    
    // Show all
    [self selectAllPrefView];
    
    // Update back forwad list
    if (_currentBackForwardIndex > 0 && _currentBackForwardIndex < [_backForwardList count] - 1) {
        [_backForwardList removeObjectAtIndex:_currentBackForwardIndex];
    }
    [_backForwardList addObject:@"All"];
    _currentBackForwardIndex = [_backForwardList count] - 1;
}

//--------------------------------------------------------------//
#pragma mark -- NSToolbar delegate --
//--------------------------------------------------------------//

- (NSArray*)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
{
    static NSArray* _toolbarItems = nil;
    if (!_toolbarItems) {
        _toolbarItems = [[NSArray arrayWithObjects:
                SRPrefBackForwardItem, SRPrefShowAllItem, nil] retain];
    }
    
    return _toolbarItems;
}

- (NSArray*)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
{
    return [self toolbarAllowedItemIdentifiers:toolbar];
}

#if 0
static NSSize   SRPrefNavigationButtonSize = { 36, 27 };
#endif
static NSSize   SRPrefShowAllButtonSize = { 96, 27 };

- (NSToolbarItem*)toolbar:(NSToolbar*)toolbar 
        itemForItemIdentifier:(NSString*)itemIdentifier 
        willBeInsertedIntoToolbar:(BOOL)flag
{
    id  item = nil;
    
    // Get label
    NSString*   label;
    label = NSLocalizedStringFromTable(itemIdentifier, SRPrefToolbarLabelTable, nil);
    
    // Back and forward
    if ([itemIdentifier isEqualToString:SRPrefBackForwardItem]) {
        // Create toolbar item
        item = [[SRPrefBackForwardToolbaarItem alloc] initWithItemIdentifier:itemIdentifier];
        [item autorelease];
        [item setTarget:self];
    }
    
#if 0
    // Go back
    if ([itemIdentifier isEqualToString:SRPrefGoBackItem]) {
        // Crete button
        NSRect      buttonRect;
        NSButton*   button;
        buttonRect.origin = NSZeroPoint;
        buttonRect.size = SRPrefNavigationButtonSize;
        button = [[NSButton alloc] initWithFrame:buttonRect];
        [button setButtonType:NSMomentaryPushInButton];
        [button setBezelStyle:NSTexturedRoundedBezelStyle];
        [button setTitle:@"<"];
        [button setTarget:self];
        [button setAction:@selector(goBackAction:)];
        
        [item setView:button];
        [item setMinSize:SRPrefNavigationButtonSize];
        [item setMaxSize:SRPrefNavigationButtonSize];
        [button release];
    }
    // Go forward
    if ([itemIdentifier isEqualToString:SRPrefGoForwardItem]) {
        // Crete button
        NSRect      buttonRect;
        NSButton*   button;
        buttonRect.origin = NSZeroPoint;
        buttonRect.size = SRPrefNavigationButtonSize;
        button = [[NSButton alloc] initWithFrame:buttonRect];
        [button setButtonType:NSMomentaryPushInButton];
        [button setBezelStyle:NSTexturedRoundedBezelStyle];
        [button setTitle:@">"];
        [button setTarget:self];
        [button setAction:@selector(goForwardAction:)];
        
        [item setView:button];
        [item setMinSize:SRPrefNavigationButtonSize];
        [item setMaxSize:SRPrefNavigationButtonSize];
        [button release];
    }
#endif
    // Show all
    if ([itemIdentifier isEqualToString:SRPrefShowAllItem]) {
        // Create toolbar item
        item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
        [item autorelease];
        [item setTarget:self];
        
        // Crete button
        NSRect      buttonRect;
        NSButton*   button;
        buttonRect.origin = NSZeroPoint;
        buttonRect.size = SRPrefShowAllButtonSize;
        button = [[NSButton alloc] initWithFrame:buttonRect];
        [button setButtonType:NSMomentaryPushInButton];
        [button setBezelStyle:NSTexturedRoundedBezelStyle];
        [button setTitle:@"Show All"];
        [button setTarget:self];
        [button setAction:@selector(showAllAction:)];
        
        [item setView:button];
        [item setMinSize:SRPrefShowAllButtonSize];
        [item setMaxSize:SRPrefShowAllButtonSize];
        [button release];
    }
    
    // Set item properties
    if (label) {
        [item setLabel:label];
    }
    
    return item;
}

//--------------------------------------------------------------//
#pragma mark -- Menu item validation --
//--------------------------------------------------------------//

- (BOOL)validateMenuItem:(id<NSMenuItem>)menuItem
{
    // Get tag
    int tag;
    tag = [menuItem tag];
    
    switch (tag) {
    case SRCloseWindowTag: {
        [menuItem setKeyEquivalent:@"w"];
        [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
        return YES;
    }
    case SRCloseTabTag: {
        [menuItem setKeyEquivalent:@""];
        return YES;
    }
    }
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- SRPrefView notification --
//--------------------------------------------------------------//

- (void)preferenceSelected:(NSNotification*)notification
{
    // Get bundle ID
    NSString*   bundleId;
    bundleId = [[notification userInfo] objectForKey:@"bundleID"];
    if (!bundleId) {
        return;
    }
    
    // Select preference
    [self selectPreferenceWithBundleIdentifier:bundleId];
    
    // Update back forwad list
    if (_currentBackForwardIndex > 0 && _currentBackForwardIndex < [_backForwardList count] - 1) {
        [_backForwardList removeObjectAtIndex:_currentBackForwardIndex];
    }
    [_backForwardList addObject:bundleId];
    _currentBackForwardIndex = [_backForwardList count] - 1;
}

@end
