/*
SRTabExpose.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 "SRTabExpose.h"

#import "SRTabView.h"

#import "AppKitEx.h"
#import "CGSPrivate.h"

@interface SRDummyWebView : NSView
{
}
@end

@implementation SRDummyWebView

- (BOOL)canGoBack
{
    return NO;
}

- (BOOL)canGoForward
{
    return NO;
}

- (BOOL)canMakeTextLarger
{
    return NO;
}

- (BOOL)canMakeTextSmaller
{
    return NO;
}

- (WebFrame*)mainFrame
{
    return nil;
}

@end

#pragma mark -

//#define SR_TABEXPOSE_USE_TIMER

@implementation SRTabExposeView

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

// Initialize
- (id)initWithFrame:(NSRect)frame type:(int)type
{
    self = [super initWithFrame:frame];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    _type = type;
    _opacity = 0.0;
    
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Controller --
//--------------------------------------------------------------//

- (SRTabExposeController*)tabExposeController
{
    return _tabExposeController;
}

- (void)setTabExposeController:(SRTabExposeController*)tabExposeController
{
    _tabExposeController = tabExposeController;
}

//--------------------------------------------------------------//
#pragma mark -- Opacity --
//--------------------------------------------------------------//

- (float)opacity
{
    return _opacity;
}

- (void)setOpacity:(float)opacity
{
    _opacity = opacity;
}

//--------------------------------------------------------------//
#pragma mark -- NSView overrides --
//--------------------------------------------------------------//

- (void)mouseDown:(NSEvent*)event
{
    // Select web view
    if (_type == SRTabExposeViewAbove) {
        // Get mouse location
        NSPoint mouseLoc;
        mouseLoc = [[event window] convertBaseToScreen:[event locationInWindow]];
        
        // Find selected web view
        NSArray*    frames;
        int i;
        frames = [_tabExposeController currentFrames];
        for (i = 0; i < [frames count]; i++) {
            NSRect  frame;
            frame = [[frames objectAtIndex:i] rectValue];
            frame.origin.y = [[[event window] screen] frame].size.height - (frame.origin.y + frame.size.height);
            if (NSPointInRect(mouseLoc, frame)) {
                // Select web view
                [_tabExposeController deexposeAndSelectWebViewAtIndex:i];
                return;
            }
        }
    }
    
    // Deexpose
    [_tabExposeController deexpose];
}

- (void)mouseMoved:(NSEvent*)event
{
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)frame
{
    // Set color
    [[NSColor colorWithDeviceWhite:0.0 alpha:_opacity] set];
    
    // Fill frame
    NSRectFill(frame);
    
    // Draw web view title
    if (_type == SRTabExposeViewAbove) {
        //[[NSColor blackColor] set];
    }
}

@end

#pragma mark -

@implementation SRTabExposeWindow

//--------------------------------------------------------------//
#pragma mark -- Event handling --
//--------------------------------------------------------------//

- (void)sendEvent:(NSEvent*)event
{
    // Wheel scroll
    if ([event type] == NSScrollWheel) {
        NSView* view;
        view = [[self contentView] hitTest:[event locationInWindow]];
        if (view) {
            [view scrollWheel:event];
        }
        return;
    }
    
    [super sendEvent:event];
}

@end

#pragma mark -

@interface SRTabExposeController (private)
- (void)_setWindowsAutodisplay:(BOOL)flag;
- (void)_updateWindows;
- (void)deexposed;
@end

@implementation SRTabExposeController

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

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize meber variables
    _exposeState = SRTabExposeDeexpose;
    _duration = 0.025;
    _value = 0.0;
    _rate = 0.2;
    
    return self;
}

//--------------------------------------------------------------//
#pragma mark -- Web View --
//--------------------------------------------------------------//

- (WebView*)_webViewOnWindowAtIndex:(int)index
{
    // Get window
    if ([_windows count] < index) {
        return nil;
    }
    NSWindow*   window;
    window = [_windows objectAtIndex:index];
    
    // Get web view
    NSArray*    subviews;
    subviews = [[window contentView] subviews];
    if ([subviews count] > 0) {
        int i;
        for (i = 0; i < [subviews count]; i++) {
            id  view;
            view = [subviews objectAtIndex:i];
            if ([view isKindOfClass:[WebView class]]) {
                return view;
            }
        }
    }
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Expose --
//--------------------------------------------------------------//

static BOOL _isExposing = NO;

+ (BOOL)isExposing
{
    return _isExposing;
}

- (void)_moveWebViewsToWindow
{
    int i;
    for (i = 0; i < [_windows count]; i++) {
        WebView*    webView;
        NSWindow*   window;
        webView = (WebView*)[_tabView viewAtIndex:i];
        window = [_windows objectAtIndex:i];
        
        // Remove web view from tab view
        [webView retain]; // Do not forget release it
        
        SRDummyWebView* dummyView;
        dummyView = [[SRDummyWebView alloc] initWithFrame:NSZeroRect];
        [[[_tabView tabView] tabViewItemAtIndex:i] setView:dummyView];
        [dummyView release];
        
        [[window contentView] addSubview:webView];
        [webView setHostWindow:window];
        [webView release];
        
        // Remove image view
        NSArray*        subviews;
        NSEnumerator*   enumerator;
        NSView*         view;
        subviews = [[window contentView] subviews];
        enumerator = [subviews objectEnumerator];
        while (view = [enumerator nextObject]) {
            if ([view isKindOfClass:[NSImageView class]]) {
                [view removeFromSuperview];
            }
        }
//        if (topImageView) {
//            [topImageView removeFromSuperview];
//        }
    }
}

- (void)startExposingIn
{
    // Configure animation
    _exposeState = SRTabExposeExposingIn;
    
    // Stop window displaying
    [self _setWindowsAutodisplay:YES];
    
#ifdef SR_TABEXPOSE_USE_TIMER
    if (!_timer) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:_duration 
                target:self 
                selector:@selector(_updateWindows) 
                userInfo:nil 
                repeats:YES];
    }
#else
    while (_value < 1.0) {
        [self _updateWindows];
        
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:_duration]];
    }
#endif
}

- (void)startExposingOut
{
    // Configure animation
    _exposeState = SRTabExposeExposingOut;
    
    // Stop window displaying
    [self _setWindowsAutodisplay:YES];
    
#ifdef SR_TABEXPOSE_USE_TIMER
    if (!_timer) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:_duration 
                target:self 
                selector:@selector(_updateWindows) 
                userInfo:nil 
                repeats:YES];
    }
#else
    while (_value > 0.0) {
        [self _updateWindows];
        
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:_duration]];
    }
#endif
}

- (float)value
{
    return _value;
}

- (NSArray*)currentFrames
{
    return _currentFrames;
}

- (void)_setWindowsAutodisplay:(BOOL)flag
{
    NSEnumerator*   enumerator;
    NSWindow*       window;
    enumerator = [_windows objectEnumerator];
    while (window = [enumerator nextObject]) {
        [window setAutodisplay:flag];
    }
}

- (void)_updateWindows
{
    // Update value
    if (_exposeState == SRTabExposeExposingIn) {
        _value += _rate;
        if (_value >= 1.0) {
            _value = 1.0;
        }
    }
    if (_exposeState == SRTabExposeExposingOut) {
        _value -= _rate;
        if (_value <= 0.0) {
            _value = 0.0;
        }
    }
    
    // Get screen frame
    NSRect  screenFrame;
    screenFrame = [[[_windows objectAtIndex:0] screen] frame];
    
    // Get CGConnection
    CGSConnection   connection;
    connection = _CGSDefaultConnection();
    
    // Update window frame
    CGSWindow*          windows;
    CGAffineTransform*  transforms;
    windows = malloc(sizeof(CGSWindow) * [_windows count]);
    transforms = malloc(sizeof(CGAffineTransform) * [_windows count]);
    
    [_currentFrames removeAllObjects];
    
    int i;
    for (i = 0; i < [_windows count]; i++) {
        // Get window and final frame
        NSWindow*   window;
        NSRect      finalFrame;
        window = [_windows objectAtIndex:i];
        finalFrame = [[_finalFrames objectAtIndex:i] rectValue];
        
        // Decide current frame
        NSRect  frame;
        frame.origin.x = _origFrame.origin.x + (finalFrame.origin.x - _origFrame.origin.x) * _value;
        frame.origin.y = _origFrame.origin.y + (finalFrame.origin.y - _origFrame.origin.y) * _value;
        frame.size.width = _origFrame.size.width + (finalFrame.size.width - _origFrame.size.width) * _value;
        frame.size.height = _origFrame.size.height + (finalFrame.size.height - _origFrame.size.height) * _value;
        
        frame.origin.y = screenFrame.size.height - (frame.origin.y + frame.size.height);
        
        // Create affine transform
        CGAffineTransform   transform;
        transform = CGAffineTransformIdentity;
        transform = CGAffineTransformScale(
                transform, _origFrame.size.width / frame.size.width, _origFrame.size.height / frame.size.height);
        transform = CGAffineTransformTranslate(transform, -1 * frame.origin.x, -1 * frame.origin.y);
        
        // Set window number and affine transform
        *(windows + i) = [window windowNumber];
        *(transforms + i) = transform;
        
        [_currentFrames addObject:[NSValue valueWithRect:frame]];
    }
    
    // Apply affine transforms
    CGSSetWindowTransforms(connection, windows, transforms, [_windows count]);
    
    // Stop timer
    if (_value >= 1.0) {
        _exposeState = SRTabExposeExposing;
#ifdef SR_TABEXPOSE_USE_TIMER
        [_timer invalidate];
        _timer = nil;
#endif
        
        // Move web views
        [self _moveWebViewsToWindow];
        
        // Start window displaying
        [self _setWindowsAutodisplay:YES];
        
        // Show windows
        [_viewAbove setOpacity:0.05];
        [_viewAbove setNeedsDisplay:YES];
        [_viewBelow setOpacity:0.6];
        [_viewBelow setNeedsDisplay:YES];
    }
    if (_value <= 0.0) {
        _exposeState = SRTabExposeDeexpose;
#ifdef SR_TABEXPOSE_USE_TIMER
        [_timer invalidate];
        _timer = nil;
#endif
        
        [self deexposed];
    }
}

- (int)divideNumberOfNumber:(int)number
{
    if (number < 2) {
        return 1;
    }
    
    int divideNumber = 1;
    while (1) {
        if ((number / divideNumber <= divideNumber) || 
            ((number / divideNumber == divideNumber + 1) && 
             (number % divideNumber == 0)))
        {
            return divideNumber;
        }
        
        divideNumber++;
    }
    
    // Do not reach here
    return 1;
}

- (void)decideFinalWindowFrames
{
    static int  _margin = 20;
    static int  _padding = 5;
    
    // Get number of windows
    int numberOfWindows;
    numberOfWindows = [_windows count];
    if (numberOfWindows < 2) {
        return;
    }
    
    // Get divide number
    int divideNumber;
    divideNumber = [self divideNumberOfNumber:numberOfWindows];
    
    // Get screen size
    NSScreen*   screen;
    NSRect      screenFrame, visibleScreenFrame;
    screen = [[_tabView window] screen];
    screenFrame = [screen frame];
    visibleScreenFrame = [screen visibleFrame];
    
    // Decide window size
    NSRect  frame;
    NSSize  origSize;
    int     height;
    float   aspectRatio = 1.0f;
    _origFrame = [[_windows objectAtIndex:0] frame];
    origSize = _origFrame.size;
    height = origSize.height * divideNumber + _padding * (divideNumber - 1) + _margin * 2;
    if (height > visibleScreenFrame.size.height) {
        aspectRatio = visibleScreenFrame.size.height / height;
    }
    frame.size.width = origSize.width * aspectRatio;
    frame.size.height = origSize.height * aspectRatio;
    
    // Decide window position
    [_finalFrames release];
    _finalFrames = [[NSMutableArray array] retain];
    [_currentFrames release];
    _currentFrames = [[NSMutableArray array] retain];
    
    int i, index = 0;
    for (i = 0; i < divideNumber; i++) {
        int count = 1, remainRow;
        remainRow = divideNumber - (i + 1);
        while (count < divideNumber + 1 && index + count < numberOfWindows) {
            if (remainRow == 0) {
                count++;
                continue;
            }
            
            int remain;
            remain = numberOfWindows - index - count;
            if ((remain / remainRow < count) || 
                ((remain / remainRow == count) && 
                 (remain % remainRow == 0)))
            {
                break;
            }
            count++;
        }
        
        // Calc window position
        float   totalWidth;
        totalWidth = frame.size.width * count + _padding * (count - 1) + _margin * 2;
        
        if (totalWidth > visibleScreenFrame.size.width) {
            // Shrink size
            float   width;
            width = origSize.width * count + _padding * (count - 1) + _margin * 2;
            aspectRatio = visibleScreenFrame.size.width / width;
            frame.size.width = origSize.width * aspectRatio;
            frame.size.height = origSize.height * aspectRatio;
            totalWidth = frame.size.width * count + _padding * (count - 1) + _margin * 2;
        }
        
        float   totalHeight;
        totalHeight = frame.size.height * divideNumber + _padding * (divideNumber - 1) + _margin * 2;
        frame.origin.y = (visibleScreenFrame.origin.y - screenFrame.origin.y) + 
                visibleScreenFrame.size.height - _margin - 
                (visibleScreenFrame.size.height / 2 - totalHeight / 2) - 
                frame.size.height * (i + 1) - _padding * i;
        
        int j;
        for (j = 0; j < count; j++) {
            frame.origin.x = (visibleScreenFrame.origin.x - screenFrame.origin.x) + 
                    _margin + visibleScreenFrame.size.width / 2 - totalWidth / 2 + 
                    (frame.size.width + _padding) * j;
            
            // Add frame
            [_finalFrames addObject:[NSValue valueWithRect:frame]];
            [_currentFrames addObject:[NSValue valueWithRect:_origFrame]];
            
            index++;
        }
    }
}

- (NSWindow*)createAndOrederWindowAtIndex:(int)index inFrame:(NSRect)frame topWindow:(NSWindow*)topWindow
{
    // Get web view
    WebView*    webView;
    webView = (WebView*)[_tabView viewAtIndex:index];
    
    // Get tab frame
    NSRect  tabFrame;
    tabFrame = [[_tabView tabView] frame];
    
#if 1
    // Check web view size
    NSRect  webFrame;
    webFrame = [webView frame];
    if (!NSEqualSizes(tabFrame.size, webFrame.size)) {
        [webView setFrameSize:tabFrame.size];
    }
#endif
    
    // Create window
    NSWindow*   window;
    window = [[NSWindow alloc] initWithContentRect:frame 
            styleMask:NSBorderlessWindowMask 
            backing:NSBackingStoreBuffered 
            defer:YES];
    [window autorelease];
    [window setLevel:NSFloatingWindowLevel];
    
#if 1
    // Create image view of web view
    NSImageView*    imageView;
    imageView = [webView viewImageView];
    if (imageView) {
        [[window contentView] addSubview:imageView];
    }
    
    // Order window
    if (!topWindow) {
        [window orderFront:self];
    }
    else {
        [window orderWindow:NSWindowBelow relativeTo:[topWindow windowNumber]];
    }
#else
    NSImageView*    topImageView = nil;
    if (!topWindow) {
        topImageView = [webView viewImageView];
        
        [[window contentView] addSubview:topImageView];
        [window orderFront:self];
    }
    else {
        [window orderWindow:NSWindowBelow relativeTo:[topWindow windowNumber]];
    }
#endif
    
    return window;
}

- (void)exposeWithTabView:(SRTabView*)tabView
{
    _tabView = tabView;
    
    // Get frames
    NSRect  srTabFrame, tabFrame, screenFrame, windowFrame;
    srTabFrame = [tabView frame];
    tabFrame = [[tabView tabView] frame];
    screenFrame = [[[tabView window] screen] frame];
    windowFrame = [[tabView window] frame];
    
    // Decide tab expose frame
    NSRect  frame;
    frame.origin.x = windowFrame.origin.x + srTabFrame.origin.x;
    frame.origin.y = windowFrame.origin.y + srTabFrame.origin.y;
    frame.size = tabFrame.size;
    
    // Create expose fade out window and view
    _windowBelow = [[SRTabExposeWindow alloc] initWithContentRect:screenFrame 
            styleMask:NSBorderlessWindowMask 
            backing:NSBackingStoreBuffered 
            defer:YES];
    _viewBelow = [[SRTabExposeView alloc] 
            initWithFrame:NSMakeRect(0, 0, screenFrame.size.width, screenFrame.size.height) 
            type:SRTabExposeViewBelow];
    [_viewBelow autorelease];
    [_viewBelow setOpacity:0.0];
    [[_windowBelow contentView] addSubview:_viewBelow];
    [_viewBelow setTabExposeController:self];
    [_windowBelow makeFirstResponder:_viewBelow];
    [_windowBelow setOpaque:NO];
    [_windowBelow setLevel:NSFloatingWindowLevel];
    [_windowBelow orderFront:self];
    
    // Create windows
    [_windows release];
    _windows = [[NSMutableArray array] retain];
    
    NSWindow*   topWindow;
    int         topIndex;
    topIndex = [_tabView selectedIndex];
    topWindow = [self createAndOrederWindowAtIndex:topIndex inFrame:frame topWindow:nil];
    
    int i;
    for (i = 0; i < [tabView numberOfItems]; i++) {
        if (i != topIndex) {
            NSWindow*   window;
            window = [self createAndOrederWindowAtIndex:i inFrame:frame topWindow:topWindow];
            [_windows addObject:window];
        }
    }
    [_windows insertObject:topWindow atIndex:topIndex];
    
    // Create expose above window and view
    _windowAbove = [[SRTabExposeWindow alloc] initWithContentRect:screenFrame 
            styleMask:NSBorderlessWindowMask 
            backing:NSBackingStoreBuffered 
            defer:YES];
    _viewAbove = [[SRTabExposeView alloc] 
            initWithFrame:NSMakeRect(0, 0, screenFrame.size.width, screenFrame.size.height) 
            type:SRTabExposeViewAbove];
    [_viewAbove autorelease];
    [_viewAbove setOpacity:0.0];
    [[_windowAbove contentView] addSubview:_viewAbove];
    [_viewAbove setTabExposeController:self];
    [_windowAbove makeFirstResponder:_viewAbove];
    [_windowAbove setOpaque:NO];
    [_windowAbove setLevel:NSFloatingWindowLevel];
    //[_windowAbove setAcceptsMouseMovedEvents:YES];
    [_windowAbove makeKeyAndOrderFront:self];
    
    // Expose windows
    _isExposing = YES;
    _selectedWebView = nil;
    [self decideFinalWindowFrames];
    [self startExposingIn];
}

- (void)deexpose
{
    [self deexposeAndSelectWebView:nil];
}

- (void)deexposeAndSelectWebViewAtIndex:(int)index
{
    if ([_windows count] < index) {
        [self deexposeAndSelectWebView:nil];
        return;
    }
    
    // Get selected web view
    NSWindow*   window;
    NSArray*    subviews;
    WebView*    webView = nil;
    window = [_windows objectAtIndex:index];
    subviews = [[window contentView] subviews];
    if ([subviews count] > 0) {
        webView = [subviews objectAtIndex:0];
    }
    [self deexposeAndSelectWebView:webView];
}

- (void)deexposeAndSelectWebView:(WebView*)selectedWebView
{
    // Set selected web view
    _selectedWebView = selectedWebView;
    
    // Order selected web view front
    [[_selectedWebView window] orderFront:self];
    
    // Start exposing out
    [self startExposingOut];
}

- (void)deexposed
{
    // Close window
    [_windowAbove orderOut:self];
    
    // Get frame of tab view
    NSRect  frame;
    frame = [[_tabView tabView] frame];
    frame.origin = NSZeroPoint;
    
    // Decide selected web view
    WebView*    selectedWebView;
    selectedWebView = _selectedWebView;
    if (!_selectedWebView) {
        selectedWebView = [self _webViewOnWindowAtIndex:[_tabView selectedIndex]];
    }
    
    // Restore tab views
    NSWindow*   topWindow = nil;
    int i;
    for (i = 0; i < [_windows count]; i++) {
        NSWindow*   window;
        window = [_windows objectAtIndex:i];
        
        // Get web view
        WebView*    webView;
        webView = [self _webViewOnWindowAtIndex:i];
        
        if (webView) {
            // For top wev view
            if (webView == selectedWebView) {
                // Set image view
                NSImageView*    imageView;
                imageView = [webView viewImageView];
                [[window contentView] addSubview:imageView];
            }
            
            // Move web view
            [webView retain]; // Do not forget release it
            [webView removeFromSuperview];
            [webView setHostWindow:[_tabView window]];
            
            [webView setFrame:frame];
            [webView setBounds:frame];
            [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
            
            [[[_tabView tabView] tabViewItemAtIndex:i] setView:webView];
            [webView release];
        }
        
        // Close window
        if (webView == selectedWebView) {
            topWindow = window;
        }
        else {
            [window orderOut:self];
        }
    }
    
    // Select web view
    if (_selectedWebView) {
        int index;
        index = [_tabView indexOfView:_selectedWebView];
        if (index >= 0 && index < [_tabView numberOfItems]) {
            [_tabView selectItemAtIndex:index];
        }
    }
    
    // Change first responder
    [[_tabView window] makeFirstResponder:[_tabView viewOfSelectedTabViewItem]];
    
#if 0
    // Close top window
    if (topWindow) {
        [topWindow orderOut:self];
    }
#endif
    
    // Release windows
    [_windows release];
    _windows = nil;
    
    // Close window
    [_windowBelow orderOut:self];
    
    // Release window
    [_windowAbove release];
    _windowAbove = nil;
    [_windowBelow release];
    _windowBelow = nil;
    _viewAbove = nil;
    _viewBelow = nil;
    
    _isExposing = NO;
    _value = 0.0;
}

@end

