/*
SRUtil.m

Author: 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 "SRMainWindowController.h"
#import "SRBookmark.h"
#import "SRDownloadHistoryItem.h"

#import "SRUtil.h"

//--------------------------------------------------------------//
#pragma mark -- String --
//--------------------------------------------------------------//

NSString* SRCreateUUID()
{
    // Create CFUUID
    CFUUIDRef   uuid;
    uuid = CFUUIDCreate(NULL);
    
    // Create UUID string
    NSString*   string;
    string = (NSString*)CFUUIDCreateString(NULL, uuid);
    [string autorelease];
    
    // Release CFUUID
    CFRelease(uuid);
    
    return string;
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- File operations --
//--------------------------------------------------------------//

BOOL SRCreateFile(NSString* filePath)
{
    return SRCreateDirectoryFile(filePath, NO);
}

BOOL SRCreateDirectoryFile(
        NSString* filePath, 
        BOOL directory)
{
    // Get path component
    NSArray*    components;
    components = [filePath pathComponents];
    
    NSFileManager*  fileMgr;
    fileMgr = [NSFileManager defaultManager];
    
    // Create directory and file
    NSEnumerator*   enumerator;
    NSString*       component;
    NSString*       path = @"";
    enumerator = [components objectEnumerator];
    while (component = [enumerator nextObject]) {
        // Create subpath
        path = [path stringByAppendingPathComponent:component];
        
        // Check existense
        if (![fileMgr fileExistsAtPath:path]) {
            // Create file
            if ([path isEqualToString:filePath] && !directory) {
                NSDictionary*   attr;
                attr = [NSDictionary dictionaryWithObjectsAndKeys:
                        [NSNumber numberWithInt:0644], NSFilePosixPermissions, nil];
                if (![fileMgr createFileAtPath:path contents:[NSData data] attributes:attr]) {
                    // Error
                    NSLog(@"Could not create file at %@", path);
                    return NO;
                }
            }
            // Create directory
            else {
                NSDictionary*   attr;
                attr = [NSDictionary dictionaryWithObjectsAndKeys:
                        [NSNumber numberWithInt:0755], NSFilePosixPermissions, nil];
                if (![fileMgr createDirectoryAtPath:path attributes:attr]) {
                    // Error
                    NSLog(@"Could not create directory at %@", path);
                    return NO;
                }
            }
        }
    }
    
    return YES;
}

NSString* SRCreateUniqueFileName(
        NSString* fileExtension)
{
    if (!fileExtension) {
        return SRCreateUUID();
    }
    else {
        return [NSString stringWithFormat:@"%@.%@", SRCreateUUID(), fileExtension];
    }
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Mouse operations --
//--------------------------------------------------------------//

BOOL SRShowContextMenuByMousePressHolding(
        NSView* view, 
        NSEvent* event)
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check preferences
    if (![defaults boolForKey:SRGeneralOneClickNavigation]) {
        return NO;
    }
    
    // Get mouse location
    NSPoint mouseLoc;
    mouseLoc = [view convertPoint:[event locationInWindow] fromView:nil];
    
    // Wait next event a moment
    NSEvent*    waitingEvent;
    NSRect      offsetRect;
    NSDate*     expireDate;
    offsetRect = NSMakeRect(mouseLoc.x - 1.0, mouseLoc.y - 1.0, 3.0, 3.0);
    expireDate = [NSDate dateWithTimeIntervalSinceNow:0.3];
    
    while (YES) {
        waitingEvent = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) 
                untilDate:expireDate 
                inMode:NSEventTrackingRunLoopMode 
                dequeue:YES];
        
        // If timer is expired, show context menu
        if (!waitingEvent) {
            // Make NSRightMouseDown event
            NSEvent*    rightMouseEvent;
            rightMouseEvent = [NSEvent mouseEventWithType:NSRightMouseDown 
                    location:[event locationInWindow] 
                    modifierFlags:[event modifierFlags] 
                    timestamp:[event timestamp] 
                    windowNumber:[event windowNumber] 
                    context:[event context] 
                    eventNumber:[event eventNumber] 
                    clickCount:[event clickCount] 
                    pressure:[event pressure]];
            
            // Show context menu
            [view rightMouseDown:rightMouseEvent];
            
            return YES;
        }
        // If mouse up is detected, do mouse up immediately
        else if ([waitingEvent type] == NSLeftMouseUp) {
            [NSApp postEvent:waitingEvent atStart:YES];
            break;
        }
        // Permit mouse movement up to 3 pixels
        else if ([waitingEvent type] == NSLeftMouseDragged) {
            NSPoint currentMouseLoc;
            currentMouseLoc = [view convertPoint:[waitingEvent locationInWindow] fromView:nil];
            if (!NSPointInRect(currentMouseLoc, offsetRect)) {
                [NSApp postEvent:waitingEvent atStart:YES];
                break;
            }
        }
    }
    
    return NO;
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- View operations --
//--------------------------------------------------------------//

NSView* SRCopyView(
        NSView* view)
{
    NSData* data;
    data = [NSArchiver archivedDataWithRootObject:view];
    return [NSUnarchiver unarchiveObjectWithData:data];
}

id SRViewWithClass(
        NSView* view, 
        Class viewClass)
{
    if ([view isKindOfClass:viewClass]) {
        return view;
    }
    
    NSEnumerator*   enumerator;
    NSView*         subview;
    enumerator = [[view subviews] objectEnumerator];
    while (subview = [enumerator nextObject]) {
        id  foundView;
        foundView = SRViewWithClass(subview, viewClass);
        if (foundView) {
            return foundView;
        }
    }
    
    return nil;
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Window operations --
//--------------------------------------------------------------//

NSWindow* SRGetFrontMainWindow()
{
    // Get window number list
    int     count;
    int*    list;
    NSCountWindowsForContext((int)NULL, &count);
    list = malloc(sizeof(int) * count);
    NSWindowListForContext((int)NULL, sizeof(int) * count, list);
    
    // Find browser window
    NSWindow*   window = nil;
    int         i;
    for (i = 0; i < count; i++) {
        // Get window
        window = [NSApp windowWithWindowNumber:list[i]];
        if (!window) {
            continue;
        }
        
        // Get window controller
        id  windowController;
        windowController = [window windowController];
        if ([windowController isKindOfClass:[SRMainWindowController class]]) {
            break;
        }
    }
    
    free(list);
    
    return window;
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Bookmark operations --
//--------------------------------------------------------------//

SRBookmark* SRCreateBookmarkFromDownloadHistoryItem(
        SRDownloadHistoryItem* item)
{
    if (!item) {
        return nil;
    }
    
    // Get file name and URL string
    NSString*   fileName;
    NSString*   URLString;
    fileName = [item fileName];
    URLString = [item URLString];
    if (!fileName || !URLString) {
        return nil;
    }
    
    // Create bookmark
    return [SRBookmark bookmarkWithTitle:fileName 
            URLString:URLString 
            originalBrowser:SRBrowserShiira];
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Sound --
//--------------------------------------------------------------//

NSArray* SoundFilePaths()
{
    static NSMutableArray*  _soundPaths = nil;
    if (!_soundPaths) {
        _soundPaths = [[NSMutableArray array] retain];
        
        // Get available file types
        NSArray*    fileTypes;
        fileTypes = [NSSound soundUnfilteredFileTypes];
        if (!fileTypes || [fileTypes count] == 0) {
            fileTypes = [NSArray arrayWithObject:@"aiff"];
        }
        
        // Collect sound files
        NSArray*        libraryPaths;
        NSEnumerator*   enumerator;
        NSString*       libraryPath;
        libraryPaths = NSSearchPathForDirectoriesInDomains(
                NSAllLibrariesDirectory, NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask, YES);
        enumerator = [libraryPaths objectEnumerator];
        while (libraryPath = [enumerator nextObject]) {
            // Append 'Sounds'
            NSString*   soundsPath;
            soundsPath = [libraryPath stringByAppendingPathComponent:@"Sounds"];
            
            // Collect aiff files
            NSDirectoryEnumerator*  dirEnumerator;
            NSString*               soundFileName;
            dirEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:soundsPath];
            while (soundFileName = [dirEnumerator nextObject]) {
                if ([fileTypes containsObject:[soundFileName pathExtension]]) {
                    [_soundPaths addObject:[soundsPath stringByAppendingPathComponent:soundFileName]];
                }
            }
        }
    }
    
    return _soundPaths;
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Bookmark operations --
//--------------------------------------------------------------//

NSString* SRCreateDataSizeString(
        long long dataSize)
{
    // Byte
    if (dataSize < 1024) {
        return [NSString stringWithFormat:@"%d B", (int)dataSize];
    }
    // KByte
    if (dataSize < 1024 * 1024) {
        return [NSString stringWithFormat:@"%.1f KB", dataSize / 1024.0];
    }
    // MByte
    if (dataSize < 1024 * 1024 * 1024) {
        return [NSString stringWithFormat:@"%.2f MB", dataSize / (1024.0 * 1024.0)];
    }
    // GByte
    return [NSString stringWithFormat:@"%f.2 GB", dataSize / (1024.0 * 1024.0 * 1024.0)];
}

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Error message --
//--------------------------------------------------------------//

void SRGetErrorMessage(
        NSError* error, 
        WebFrame* frame, 
        NSString** msgText, 
        NSString** infoText, 
        NSString** statusText)
{
    // Get code
    int code;
    code = [error code];
    
    // Check domain
    if ([[error domain] isEqualToString:NSURLErrorDomain]) {
        // Get URL string
        NSString*   URLString;
        URLString = [[error userInfo] objectForKey:NSErrorFailingURLStringKey];
        if (!URLString) {
            URLString = @"(Unknown URL)";
        }
        
        // Get host
        NSString*   host;
        host = [[NSURL URLWithString:URLString] host];
        if (!host) {
            host = URLString;
        }
        
        // Check code
        switch (code) {
        case NSURLErrorUnknown: {
            *msgText = NSLocalizedString(@"An unknown error occurred.", nil);
            *infoText = NSLocalizedString(@"An unknown error occurred.", nil);
            break;
        }
        case NSURLErrorCancelled: {
            *statusText = NSLocalizedString(@"Request is cancelled.", nil);
            break;
        }
        case NSURLErrorBadURL: {
            *msgText = NSLocalizedString(@"URL is not valid.", nil);
            *infoText = NSLocalizedString(@"URL is not valid.", nil);
            break;
        }
        case NSURLErrorTimedOut: {
            int timeout;
            timeout = [[[frame dataSource] request] timeoutInterval];
            *msgText = NSLocalizedString(@"Request is timed out.", nil);
            *infoText = [NSString stringWithFormat:NSLocalizedString(UTF8STR("Shiira can’t open the page “%@” because of timeout."), nil), URLString];
            break;
        }
        case NSURLErrorUnsupportedURL: {
            *msgText = NSLocalizedString(@"ErrorUnsupportedURL", nil);
            *infoText = NSLocalizedString(@"ErrorUnsupportedURLInfo", nil);
            break;
        }
        case NSURLErrorCannotFindHost: {
            *msgText = NSLocalizedString(UTF8STR("Can’t find the host."), nil);
            *infoText = [NSString stringWithFormat:NSLocalizedString(UTF8STR("Shiira can’t open the page “%@” because it can’t find the host “%@”."), nil), URLString, host];
            break;
        }
        case NSURLErrorCannotConnectToHost: {
            *msgText = NSLocalizedString(UTF8STR("Can’t connect to host."), nil);
            *infoText = [NSString stringWithFormat:NSLocalizedString(UTF8STR("Shiira can’t connect to the page “%@” becuase it can’t connect to the host “%@”."), nil), URLString, host];
            break;
        }
        case NSURLErrorNetworkConnectionLost: {
            *msgText = NSLocalizedString(@"Network connection is lost", nil);
            *infoText = NSLocalizedString(@"Network connection is lost", nil);
            break;
        }
        case NSURLErrorDNSLookupFailed: {
            *msgText = NSLocalizedString(@"DNS lookup failed", nil);
            *infoText = NSLocalizedString(@"DNS lookup failed", nil);
            break;
        }
        case NSURLErrorHTTPTooManyRedirects: {
            *msgText = NSLocalizedString(@"Too many HTTP redirects.", nil);
            *infoText = NSLocalizedString(@"Too many HTTP redirects.", nil);
            break;
        }
        case NSURLErrorResourceUnavailable: {
            *msgText = NSLocalizedString(@"Resource is unavailable.", nil);
            *infoText = NSLocalizedString(@"Resource is unavailable.", nil);
            break;
        }
        case NSURLErrorNotConnectedToInternet: {
            *msgText = NSLocalizedString(@"Not connected to Internet.", nil);
            *infoText = NSLocalizedString(@"Not connected to Internet.", nil);
            break;
        }
        case NSURLErrorRedirectToNonExistentLocation: {
            *msgText = NSLocalizedString(@"Redirect to non existent location.", nil);
            *infoText = NSLocalizedString(@"Redirect to non existent location.", nil);
            break;
        }
        case NSURLErrorBadServerResponse: {
            *msgText = NSLocalizedString(@"Server response is bad.", nil);
            *infoText = NSLocalizedString(@"Server response is bad.", nil);
            break;
        }
        case NSURLErrorUserCancelledAuthentication: {
            *msgText = NSLocalizedString(@"Authentication cancelled.", nil);
            *infoText = NSLocalizedString(@"Authentication cancelled.", nil);
            break;
        }
        case NSURLErrorUserAuthenticationRequired: {
            *msgText = NSLocalizedString(@"Authentication is required.", nil);
            *infoText = NSLocalizedString(@"Authentication is required.", nil);
            break;
        }
        case NSURLErrorZeroByteResource: {
            // Do nothing
            break;
        }
        case NSURLErrorFileDoesNotExist: {
            *msgText = NSLocalizedString(@"File does not exist.", nil);
            *infoText = NSLocalizedString(@"File does not exist.", nil);
            break;
        }
        case NSURLErrorFileIsDirectory: {
            *msgText = NSLocalizedString(@"File is a directory.", nil);
            *infoText = NSLocalizedString(@"File is a directory.", nil);
            break;
        }
        case NSURLErrorNoPermissionsToReadFile: {
            *msgText = NSLocalizedString(@"No permissions to read file.", nil);
            *infoText = NSLocalizedString(@"No permissions to read file.", nil);
            break;
        }
        case NSURLErrorSecureConnectionFailed: {
            *msgText = NSLocalizedString(@"ErrorUnsupportedURL", nil);
            *infoText = NSLocalizedString(@"ErrorUnsupportedURLInfo", nil);
            break;
        }
        case NSURLErrorServerCertificateHasBadDate: {
            *msgText = NSLocalizedString(@"The certificate for this server has expired or is not yet valid.", nil);
            *infoText = NSLocalizedString(@"The certificate for this server has expired or is not yet valid. You might be connecting to a server that is pretending which could put your confidential information at risk. Would you like to connect to the server anyway?", nil);
            break;
        }
        case NSURLErrorServerCertificateUntrusted: {
            *msgText = NSLocalizedString(@"The certificate for this server is invalid.", nil);
            *infoText = NSLocalizedString(@"The certificate for this server is invalid. You might be connecting to a server that is pretending which could put your confidential information at risk. Would you like to connect to the server anyway?", nil);
            break;
        }
        case NSURLErrorServerCertificateHasUnknownRoot: {
            *msgText = NSLocalizedString(@"The certificate for this server was signed by an unknown certifying authority.", nil);
            *infoText = NSLocalizedString(@"The certificate for this server was signed by an unknown certifying authority. You might be connecting to a server that is pretending which could put your confidential information at risk. Would you like to connect to the server anyway?", nil);
            break;
        }
        case NSURLErrorCannotLoadFromNetwork: {
            *msgText = NSLocalizedString(@"Could not load from network.", nil);
            *infoText = NSLocalizedString(@"Could not load from network.", nil);
            break;
        }
        default: {
#if 1
            // Ignore undefined error code
            *msgText = nil;
            *infoText = nil;
            *statusText = nil;
#else
            *msgText = NSLocalizedString(@"An error occurred.", nil);
            *infoText = NSLocalizedString(@"An error occurred.", nil);
#endif
            break;
        }
        }
    }
    else if ([[error domain] isEqualToString:WebKitErrorDomain]) {
        // Check code
        switch (code) {
        case WebKitErrorCannotShowMIMEType: {
            *msgText = NSLocalizedString(UTF8STR("Can’t show MIME type."), nil);
            *infoText = NSLocalizedString(UTF8STR("Can’t show MIME type."), nil);
            break;
        }
        case WebKitErrorCannotShowURL: {
            *msgText = NSLocalizedString(UTF8STR("Can’t show URL."), nil);
            *infoText = NSLocalizedString(UTF8STR("Can’t show URL."), nil);
            break;
        }
        case WebKitErrorFrameLoadInterruptedByPolicyChange: {
            // Do nothing
            break;
        }
        case WebKitErrorCannotFindPlugIn: {
            *msgText = NSLocalizedString(UTF8STR("Can’t find plug-in."), nil);
            *infoText = NSLocalizedString(UTF8STR("Can’t find plug-in."), nil);
            break;
        }
        case WebKitErrorCannotLoadPlugIn: {
            *msgText = NSLocalizedString(UTF8STR("Can’t load plug-in."), nil);
            *infoText = NSLocalizedString(UTF8STR("Can’t load plug-in."), nil);
            break;
        }
        case WebKitErrorJavaUnavailable: {
            *msgText = NSLocalizedString(@"Java is unavailable.", nil);
            *infoText = NSLocalizedString(@"Java is unavailable.", nil);
            break;
        }
        
        default: {
#if 1
            // Ignore undefined error code
            *msgText = nil;
            *infoText = nil;
            *statusText = nil;
#else
            *msgText = NSLocalizedString(@"An error occurred.", nil);
            *infoText = NSLocalizedString(@"An error occurred.", nil);
            break;
#endif
        }
        }
    }
    else {
#if 1
        // Ignore undefined error domain
        *msgText = nil;
        *infoText = nil;
        *statusText = nil;
#else
        // Not NSURLError
        *msgText = NSLocalizedString(@"An error occurred.", nil);
        *infoText = NSLocalizedString(@"An error occurred.", nil);
#endif
    }
}
