/*
SRSearchFieldController.m

Author: Hiroyuki Yamashita, Makoto Kinoshita

Copyright 2004 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 "SRDefaultsKey.h"

#import "SRAppDelegate.h"
#import "SRMainWindowController.h"
#import "SRFindWindowController.h"
#import "SRSearchFieldController.h"
#import "SRSearchEnginesController.h"

#import "SRSearchField.h"

#import "FoundationEx.h"
#import "WebKitEx.h"

NSString*    SRSearchEngineChanged = @"SRSearchEngineChanged";

// Private interface
@interface SRSearchFieldController (Private)
- (void)_updateSearchMenu;
- (void)_updatePlaceholderString;
- (void)_updateSearchButton;
- (void)_setDefaultEngine;
@end

#pragma mark -

@implementation SRSearchFieldController

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

- (id)initWithMainWindowController:(SRMainWindowController*)mainWindowController
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize member variables
    _mainWindowController = mainWindowController;
    _searchField = nil;
    _currentEngine = nil;
    
    // Set default engine
    [self _setDefaultEngine];
    
    // Register notification
    [[NSNotificationCenter defaultCenter] 
            addObserver:self 
            selector:@selector(searchEnginesListChanged:) 
            name:SRSearchEnginesListChanged 
            object:nil];
    [[NSNotificationCenter defaultCenter] 
            addObserver:self 
            selector:@selector(faviconAdded:) 
            name:WebIconDatabaseDidAddIconNotification 
            object:nil];
    
    return self;
}

- (void)dealloc
{
    [_currentEngine release];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Main window controller --
//--------------------------------------------------------------//

- (SRMainWindowController*)mainWindowController
{
    return _mainWindowController;
}

- (void)setMainWindowController:(SRMainWindowController*)mainWindowController
{
    _mainWindowController = mainWindowController;
}

//--------------------------------------------------------------//
#pragma mark -- Search menu management --
//--------------------------------------------------------------//

- (void)_addSearchEngines:(NSArray*)engines intoMenu:(NSMenu*)menu
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    id<NSMenuItem>  item;
    
    // Check favicon using
    BOOL    isFaviconUsed, isFaviconUsedSearchEngine;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
    isFaviconUsedSearchEngine = [defaults boolForKey:SRIconUseFaviconSearchEngine];
    
    // Add engines
    NSEnumerator*   enumerator;
    NSDictionary*   engine;
    enumerator = [engines objectEnumerator];
    while (engine = [enumerator nextObject]) {
        // For seaparator
        BOOL    isSeparator;
        isSeparator = [[engine objectForKey:@"isSeparator"] boolValue];
        if (isSeparator) {
            // Add separator
            [menu addItem:[NSMenuItem separatorItem]];
            
            continue;
        }
        
        // Check using flag and title
        BOOL        isUsing;
        NSString*   title;
        isUsing = [[engine objectForKey:@"isUsing"] boolValue];
        title = [engine objectForKey:@"title"];
        
        // For folder
        NSArray*    childEngines;
        childEngines = [engine objectForKey:@"child"];
        if (childEngines) {
            // Get icon
            NSImage*    icon = nil;
            if (isFaviconUsed && isFaviconUsedSearchEngine) {
                icon = [[NSImage imageNamed:@"otherFolder"] copy];
                [icon setScalesWhenResized:YES];
                [icon setSize:NSMakeSize(14, 14)];
            }
            
            // Add folder item
            item = [menu addItemWithTitle:title 
                    action:NULL 
                    keyEquivalent:@""];
            if (isFaviconUsed && isFaviconUsedSearchEngine) {
                [item setImage:icon];
            }
            
            // Create submenu
            NSMenu* submenu;
            submenu = [[NSMenu alloc] initWithTitle:@""];
            [self _addSearchEngines:childEngines intoMenu:submenu];
            
            // Add submenu
            [item setSubmenu:submenu];
            [submenu release];
            
            continue;
        }
        
        // For normal engine
        if (isUsing && title) {
            // Get icon
            NSImage*    icon = nil;
            if (isFaviconUsed && isFaviconUsedSearchEngine) {
#if 1
                NSString*   URLString;
                URLString = [engine objectForKey:@"templateURL"];
#else
                NSString*   URLString;
                NSRange     range;
                URLString = [engine objectForKey:@"templateURL"];
                range = [URLString rangeOfString:@"/" 
                        options:NSLiteralSearch 
                        range:NSMakeRange(7, [URLString length] - 7)];
                if (range.location != NSNotFound) {
                    URLString = [URLString substringWithRange:NSMakeRange(0, range.location + 1)];
                }
#endif
                icon = [[WebIconDatabase sharedIconDatabase] iconForURL:URLString withSize:NSMakeSize(16, 16)];
                icon = [[icon copy] autorelease];
                [icon setScalesWhenResized:YES];
                [icon setSize:NSMakeSize(14, 14)];
            }
            
            // Add direct search item
#ifdef SR_CHANGE_SEARCH_ENGINE_DEFAULT_BY_OPTION_KEY
            item = [menu addItemWithTitle:title 
                    action:@selector(directSearchAction:) 
                    keyEquivalent:@""];
#else
            item = [menu addItemWithTitle:title 
                    action:@selector(selectEngineAndSearchAction:) 
                    keyEquivalent:@""];
#endif // SR_CHANGE_SEARCH_ENGINE_DEFAULT_BY_OPTION_KEY
            [item setTarget:self];
            [item setRepresentedObject:engine];
            [item setTag:SRSearchEngineTag];
            if (isFaviconUsed && isFaviconUsedSearchEngine) {
                [item setImage:icon];
            }
            
#ifdef SR_CHANGE_SEARCH_ENGINE_DEFAULT_BY_OPTION_KEY
            // Add engine selection item as alternate
            item = [menu addItemWithTitle:title 
                    action:@selector(selectEngineAction:) 
                    keyEquivalent:@""];
            [item setTarget:self];
            [item setRepresentedObject:engine];
            [item setTag:SRSearchEngineTag];
            [item setKeyEquivalentModifierMask:NSAlternateKeyMask];
            [item setAlternate:YES];
            if (isFaviconUsed && isFaviconUsedSearchEngine) {
                [item setImage:icon];
            }
#endif // SR_CHANGE_SEARCH_ENGINE_DEFAULT_BY_OPTION_KEY
            
            continue;
        }
    }
}

- (void)_updateSearchMenu
{
    // Get search engines
    NSArray*    engines;
    engines = [[SRSearchEnginesManager sharedInstance] searchEngines];
    
    // Create search menu
    NSMenu* menu;
    menu = [[NSMenu alloc] initWithTitle:@"Search"];
    [menu autorelease];
    [menu setDelegate:self];
    
    id<NSMenuItem>  item;
    
#ifdef SR_CHANGE_SEARCH_ENGINE_DEFAULT_BY_OPTION_KEY
    // Add first label
    item = [menu addItemWithTitle:NSLocalizedStringFromTable(@"Direct Search", @"SearchField", nil) 
            action:nil 
            keyEquivalent:@""];
    [item setEnabled:NO];
    
    // Add alternate label
    item = [menu addItemWithTitle:NSLocalizedStringFromTable(@"Select Default Engine", @"SearchField", nil) 
            action:nil 
            keyEquivalent:@""];
    [item setKeyEquivalentModifierMask:NSAlternateKeyMask];
    [item setEnabled:NO];
    [item setAlternate:YES];
#endif // SR_CHANGE_SEARCH_ENGINE_DEFAULT_BY_OPTION_KEY
    
    // Add engines
    [self _addSearchEngines:engines intoMenu:menu];
    
    // Add separator
    [menu addItem:[NSMenuItem separatorItem]];
    
    // Get recent searches
    NSArray*    recentSearches;
    recentSearches = [[SRSearchEnginesManager sharedInstance] recentSearches];
    if ([recentSearches count] <= 0) {
        // Add 'No recent searches'
        item = [menu addItemWithTitle:NSLocalizedStringFromTable(@"No Recent Searches", @"SearchField", nil) 
                action:@selector(selectEngineAction:) 
                keyEquivalent:@""];
        [item setEnabled:NO];
    }
    else {
        // Add recent searches menu
        NSEnumerator*   enumerator;
        NSDictionary*   recentSearch;
        enumerator = [recentSearches objectEnumerator];
        while (recentSearch = [enumerator nextObject]) {
            NSString*   label;
            label = [recentSearch objectForKey:@"label"];
            if (label) {
                item = [menu addItemWithTitle:label 
                        action:@selector(searchRecentItemAction:) 
                        keyEquivalent:@""];
                [item setTarget:self];
                [item setRepresentedObject:recentSearch];
            }
        }
        
        // Add separator
        [menu addItem:[NSMenuItem separatorItem]];
        
        // Add 'Clear recent searches'
        item = [menu addItemWithTitle:NSLocalizedStringFromTable(@"Clear Recent Searches", @"SearchField", nil) 
                action:@selector(clearRecentSearchesAction:) 
                keyEquivalent:@""];
        [item setTarget:self];
    }
    
    // Update search menu template
    [[_searchField cell] setSearchMenuTemplate:menu];
}

- (BOOL)validateMenuItem:(NSMenuItem*)item
{
    // Get current engine
    if ([item tag] == SRSearchEngineTag) {
        if ([item representedObject] == _currentEngine) {
            [item setState:NSOnState];
        }
        else {
            [item setState:NSOffState];
        }
    }

    return YES;
}

- (void)_updatePlaceholderString
{
    if (!_searchField) {
        return;
    }
    
    // If current engine is nil, disable search engine and clear placehodler
    if (!_currentEngine) {
        [[_searchField cell] setPlaceholderString:@""];
        [_searchField setNeedsDisplay:YES];
        [_searchField setEnabled:NO];
        
        return;
    }
    
    // Enable search engine and set placeholder
    if (![_searchField isEnabled]) {
        [_searchField setEnabled:YES];
    }
    [[_searchField cell] setPlaceholderString:[_currentEngine objectForKey:@"title"]];
    [_searchField setNeedsDisplay:YES];
}

- (void)_updateSearchButton
{
    // Get favicon
    NSString*   URLString;
    NSImage*    icon;
    URLString = [_currentEngine objectForKey:@"templateURL"];
    icon = [[WebIconDatabase sharedIconDatabase] iconForURL:URLString withSize:NSMakeSize(16, 16)];
    if (!icon) {
        [[_searchField cell] resetSearchButtonCell];
        return;
    }
    
    // Set favicon
    icon = [[icon copy] autorelease];
    [icon setScalesWhenResized:YES];
    [icon setSize:NSMakeSize(14, 14)];
    [[[_searchField cell] searchButtonCell] setImage:icon];
}

//--------------------------------------------------------------//
#pragma mark -- Accessors --
//--------------------------------------------------------------//

- (SRSearchField*)searchField
{
    return _searchField;
}

- (void)setSearchField:(SRSearchField*)searchField
{
    _searchField = searchField;
    
    // Set delegate
    [_searchField setDelegate:self];
    
    // Update appearance
    [self _updateSearchMenu];
    [self _updatePlaceholderString];
    [self _updateSearchButton];
}

- (BOOL)_updateDefaultEngineFromArray:(NSArray*)engines ofName:(NSString*)name
{
    NSEnumerator*   enumerator;
    NSDictionary*   engine;
    enumerator = [engines objectEnumerator];
    while (engine = [enumerator nextObject]) {
        // For folder
        NSArray*    childEngines;
        childEngines = [engine objectForKey:@"child"];
        if (childEngines) {
            if ([self _updateDefaultEngineFromArray:childEngines ofName:name]) {
                return YES;
            }
        }
        
        // Compare title
        if ([[engine objectForKey:@"title"] isEqualToString:name]) {
            [self updateCurrentEngine:engine];
            return YES;
        }
    }
    
    return NO;
}

- (void)_setDefaultEngine
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get search engines
    NSArray*    searchEngines;
    searchEngines = [[SRSearchEnginesManager sharedInstance] searchEngines];
    
    // Get default title
    NSString*   defaultTitle;
    defaultTitle = [defaults stringForKey:SRSearchEngineDefaultTitle];
    
    // Find default engine from engine list
    if (defaultTitle) {
        [self _updateDefaultEngineFromArray:searchEngines ofName:defaultTitle];
    }
    
    if (!_currentEngine) {
        // Make first one current engine
        if ([searchEngines count] > 0) {
            [self updateCurrentEngine:[searchEngines objectAtIndex:0]];
        }
    }
}

- (NSDictionary*)currentEngine
{
    return _currentEngine;
}

- (NSString*)currentEngineName
{
    if (!_currentEngine) {
        return nil;
    }
    return [_currentEngine objectForKey:@"title"];
}

- (void)updateCurrentEngine:(NSDictionary*)candidateEngine
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get search engines
    NSArray*    engines;
    engines = [[SRSearchEnginesManager sharedInstance] searchEngines];
    
    // Check availablity of specified engine
    if (candidateEngine) {
        if ([engines indexOfObjectRecursivelyIdenticalTo:candidateEngine] == NSNotFound) {
            // This engine is not contained in engine list
            candidateEngine = nil;
        }
        
        if (![[candidateEngine objectForKey:@"isUsing"] boolValue]) {
            // This engine is not avaialbe
            candidateEngine = nil;
        }
    }
    
    // If sepecified engine is not available, use first avaialbe engine
    if (!candidateEngine) {
        NSEnumerator*   enumerator;
        NSDictionary*   engine;
        enumerator = [engines objectEnumerator];
        while (engine = [enumerator nextObject]) {
            if ([[engine objectForKey:@"isUsing"] boolValue]) {
                candidateEngine = engine;
                break;
            } 
        }
    }
    
    // Set current engine
    [_currentEngine release];
    _currentEngine = [candidateEngine retain];
    
    // Update appearance
    [self _updateSearchMenu];
    [self _updatePlaceholderString];
    [self _updateSearchButton];
    
    // Set it default database
    NSString*   title = @"";
    if (_currentEngine) {
        title = [_currentEngine objectForKey:@"title"];
    }
    [defaults setObject:title forKey:SRSearchEngineDefaultTitle];
    
    // Notify engine change
    [[NSNotificationCenter defaultCenter] 
            postNotificationName:SRSearchEngineChanged
            object:self];
}

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

- (void)searchAction:(id)sender
{
    // Determine search engine
    NSDictionary*   engine = nil;
    if ([sender isKindOfClass:[NSDictionary class]]) {
        engine = sender;
    }
    else {
        engine = _currentEngine;
    }
    
    if (!engine) {
        return;
    }
    
    // Get search string
    NSString*   searchString;
    searchString = [_searchField stringValue];
    if (!searchString || [searchString length] == 0) {
        return;
    }
    
    // Craate search URL string
    NSString*   URLString;
    URLString = SRCreateSearchURLStringFromSearchEngine(searchString, engine);
    
    // Add to recent item
    [[SRSearchEnginesManager sharedInstance] addRecentWord:searchString 
            engineName:[engine objectForKey:@"title"] 
            URLString:URLString];
    
    // Set search string to find window
    [[[NSApp delegate] findWindowController] setStringValue:searchString];
    
    // Search string with current modifierFlags
    SROpenActionType    openAction;
    WebView*            webView;
    openAction = SROpenActionTypeFromModifierFlags([[NSApp currentEvent] modifierFlags]);
    if(openAction == SROpenOptionAction) {
        openAction = SROpenAction;
    }
    webView = [_mainWindowController openURLString:URLString withOpenAction:openAction];
    
    // Set template URL string
    [_mainWindowController setSearchTemplateURL:[engine objectForKey:@"templateURL"] requestURLString:URLString];
}

- (void)directSearchAction:(id)sender
{
    // Call searchAction: with specified search engine
    [self searchAction:[sender representedObject]];
}

- (void)searchRecentItemAction:(id)sender
{
    // Get serach information
    NSDictionary*   recentSearch;
    NSString*       searchString;
    NSString*       URLString;
    recentSearch = [sender representedObject];
    searchString = [recentSearch objectForKey:@"word"];
    URLString = [recentSearch objectForKey:@"url"];
    if (!searchString || !URLString) {
        return;
    }
    
    // Set search string to find window
    [[[NSApp delegate] findWindowController] setStringValue:searchString];
    
    // Search string
    [_mainWindowController openURLString:URLString];
}

- (void)selectEngineAction:(id)sender
{
    // Get represented object as engine
    NSDictionary*   engine;
    engine = [sender representedObject];
    
    [self updateCurrentEngine:engine];
}

- (void)clearRecentSearchesAction:(id)sender
{
    [[SRSearchEnginesManager sharedInstance] removeAllRecenentSeraches];
}

- (void)selectEngineAndSearchAction:(id)sender
{
    // Select engine
    [self selectEngineAction:sender];
    
    // Search with current engine
    [self searchAction:nil];
}

//--------------------------------------------------------------//
#pragma mark -- Notifications --
//--------------------------------------------------------------//

- (void)searchEnginesListChanged:(NSNotification*)notification
{
    // Update appearance
    [self _updateSearchMenu];
    [self updateCurrentEngine:_currentEngine];
    [self _updatePlaceholderString];
    [self _updateSearchButton];
}

- (void)faviconAdded:(NSNotification*)notification
{
    // Update appearance
    [self _updateSearchMenu];
    [self _updateSearchButton];
}

@end

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Utility --
//--------------------------------------------------------------//

NSString* SRCreateSearchURLStringFromSearchEngine(
        NSString* searchString, 
        NSDictionary* engine)
{
    // Get encoding and template URL
    NSStringEncoding    encoding;
    NSString*           templateURL;
    encoding = [[engine objectForKey:@"encoding"] unsignedIntValue];
    templateURL = [engine objectForKey:@"templateURL"];
    
    // Create escaped search string
    NSString*   escapedSearchString;
    escapedSearchString = (NSString*)CFURLCreateStringByAddingPercentEscapes(
            NULL, 
            (CFStringRef)searchString, 
            NULL, 
            CFSTR("+"), 
            CFStringConvertNSStringEncodingToEncoding(encoding));
    

    // Create URL string
    // Doing stringWithFormat: with the percent escapes in place can have weird effects, so just
    // do a manual replace instead
    NSString*   URLString;
    NSRange     keywordRange;
    keywordRange = [templateURL rangeOfString:@"%@"];
    
    if(keywordRange.length) {
        NSString* prefix;
        prefix = [templateURL substringWithRange:NSMakeRange(0,keywordRange.location)];
        
        NSString* suffix;
        
        if([templateURL length] > NSMaxRange(keywordRange)) {
            suffix = [templateURL substringWithRange:
                NSMakeRange(NSMaxRange(keywordRange),[templateURL length] - NSMaxRange(keywordRange))];
        }
        else {
            suffix = @"";
        }
        
        URLString = [NSString stringWithFormat:@"%@%@%@",prefix,escapedSearchString,suffix];
    }
    else {
        URLString = templateURL;
    }
    
    return URLString;
}
