/*
 * Copyright (C) 2010 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS 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 "config.h"
#import "FindIndicatorWindow.h"

#import "FindIndicator.h"
#import "WKView.h"
#import <WebCore/GraphicsContext.h>

static const double bounceAnimationDuration = 0.12;
static const double timeBeforeFadeStarts = bounceAnimationDuration + 0.2;
static const double fadeOutAnimationDuration = 0.3;

using namespace WebCore;

@interface WebFindIndicatorView : NSView {
    RefPtr<WebKit::FindIndicator> _findIndicator;
}

- (id)_initWithFindIndicator:(PassRefPtr<WebKit::FindIndicator>)findIndicator;
@end

@implementation WebFindIndicatorView

- (id)_initWithFindIndicator:(PassRefPtr<WebKit::FindIndicator>)findIndicator
{
    if ((self = [super initWithFrame:NSZeroRect]))
        _findIndicator = findIndicator;

    return self;
}

- (void)drawRect:(NSRect)rect
{
    GraphicsContext graphicsContext(static_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]));

    _findIndicator->draw(graphicsContext, enclosingIntRect(rect));
}

- (BOOL)isFlipped
{
    return YES;
}

@end

@interface WebFindIndicatorWindowAnimation : NSAnimation<NSAnimationDelegate> {
    WebKit::FindIndicatorWindow* _findIndicatorWindow;
    void (WebKit::FindIndicatorWindow::*_animationProgressCallback)(double progress);
    void (WebKit::FindIndicatorWindow::*_animationDidEndCallback)();
}

- (id)_initWithFindIndicatorWindow:(WebKit::FindIndicatorWindow *)findIndicatorWindow 
                 animationDuration:(CFTimeInterval)duration
         animationProgressCallback:(void (WebKit::FindIndicatorWindow::*)(double progress))animationProgressCallback
           animationDidEndCallback:(void (WebKit::FindIndicatorWindow::*)())animationDidEndCallback;
@end

@implementation WebFindIndicatorWindowAnimation

- (id)_initWithFindIndicatorWindow:(WebKit::FindIndicatorWindow *)findIndicatorWindow 
                 animationDuration:(CFTimeInterval)animationDuration
         animationProgressCallback:(void (WebKit::FindIndicatorWindow::*)(double progress))animationProgressCallback
           animationDidEndCallback:(void (WebKit::FindIndicatorWindow::*)())animationDidEndCallback
{
    if ((self = [super initWithDuration:animationDuration animationCurve:NSAnimationEaseInOut])) {
        _findIndicatorWindow = findIndicatorWindow;
        _animationProgressCallback = animationProgressCallback;
        _animationDidEndCallback = animationDidEndCallback;
        [self setDelegate:self];
        [self setAnimationBlockingMode:NSAnimationNonblocking];
    }
    return self;
}

- (void)setCurrentProgress:(NSAnimationProgress)progress
{
    (_findIndicatorWindow->*_animationProgressCallback)(progress);
}

- (void)animationDidEnd:(NSAnimation *)animation
{
    ASSERT(animation == self);

    (_findIndicatorWindow->*_animationDidEndCallback)();
}

@end

namespace WebKit {

PassOwnPtr<FindIndicatorWindow> FindIndicatorWindow::create(WKView *wkView)
{
    return adoptPtr(new FindIndicatorWindow(wkView));
}

FindIndicatorWindow::FindIndicatorWindow(WKView *wkView)
    : m_wkView(wkView)
    , m_bounceAnimationContext(0)
    , m_startFadeOutTimer(RunLoop::main(), this, &FindIndicatorWindow::startFadeOutTimerFired)
{
}

FindIndicatorWindow::~FindIndicatorWindow()
{
    closeWindow();
}

void FindIndicatorWindow::setFindIndicator(PassRefPtr<FindIndicator> findIndicator, bool fadeOut)
{
    if (m_findIndicator == findIndicator)
        return;

    m_findIndicator = findIndicator;

    // Get rid of the old window.
    closeWindow();

    if (!m_findIndicator)
        return;

    NSRect contentRect = m_findIndicator->frameRect();
    NSRect windowFrameRect = NSIntegralRect([m_wkView convertRect:contentRect toView:nil]);
    windowFrameRect.origin = [[m_wkView window] convertBaseToScreen:windowFrameRect.origin];

    NSRect windowContentRect = [NSWindow contentRectForFrameRect:windowFrameRect styleMask:NSBorderlessWindowMask];
    
    m_findIndicatorWindow.adoptNS([[NSWindow alloc] initWithContentRect:windowContentRect 
                                                              styleMask:NSBorderlessWindowMask 
                                                                backing:NSBackingStoreBuffered
                                                                  defer:NO]);

    [m_findIndicatorWindow.get() setBackgroundColor:[NSColor clearColor]];
    [m_findIndicatorWindow.get() setOpaque:NO];
    [m_findIndicatorWindow.get() setIgnoresMouseEvents:YES];

    RetainPtr<WebFindIndicatorView> findIndicatorView(AdoptNS, [[WebFindIndicatorView alloc] _initWithFindIndicator:m_findIndicator]);
    [m_findIndicatorWindow.get() setContentView:findIndicatorView.get()];

    [[m_wkView window] addChildWindow:m_findIndicatorWindow.get() ordered:NSWindowAbove];
    [m_findIndicatorWindow.get() setReleasedWhenClosed:NO];

    // Start the bounce animation.
    m_bounceAnimationContext = WKWindowBounceAnimationContextCreate(m_findIndicatorWindow.get());
    m_bounceAnimation.adoptNS([[WebFindIndicatorWindowAnimation alloc] _initWithFindIndicatorWindow:this
                                                                                  animationDuration:bounceAnimationDuration
                                                                          animationProgressCallback:&FindIndicatorWindow::bounceAnimationCallback
                                                                            animationDidEndCallback:&FindIndicatorWindow::bounceAnimationDidEnd]);
    [m_bounceAnimation.get() startAnimation];

    if (fadeOut)
        m_startFadeOutTimer.startOneShot(timeBeforeFadeStarts);
}

void FindIndicatorWindow::closeWindow()
{
    if (!m_findIndicatorWindow)
        return;

    m_startFadeOutTimer.stop();

    if (m_fadeOutAnimation) {
        [m_fadeOutAnimation.get() stopAnimation];
        m_fadeOutAnimation = nullptr;
    }

    if (m_bounceAnimation) {
        [m_bounceAnimation.get() stopAnimation];
        m_bounceAnimation = nullptr;
    }

    if (m_bounceAnimationContext)
        WKWindowBounceAnimationContextDestroy(m_bounceAnimationContext);
    
    [[m_findIndicatorWindow.get() parentWindow] removeChildWindow:m_findIndicatorWindow.get()];
    [m_findIndicatorWindow.get() close];
    m_findIndicatorWindow = nullptr;
}

void FindIndicatorWindow::startFadeOutTimerFired()
{
    ASSERT(!m_fadeOutAnimation);
    
    m_fadeOutAnimation.adoptNS([[WebFindIndicatorWindowAnimation alloc] _initWithFindIndicatorWindow:this
                                                                                   animationDuration:fadeOutAnimationDuration
                                                                           animationProgressCallback:&FindIndicatorWindow::fadeOutAnimationCallback
                                                                             animationDidEndCallback:&FindIndicatorWindow::fadeOutAnimationDidEnd]);
    [m_fadeOutAnimation.get() startAnimation];
}
                        
void FindIndicatorWindow::fadeOutAnimationCallback(double progress)
{
    ASSERT(m_fadeOutAnimation);

    [m_findIndicatorWindow.get() setAlphaValue:1.0 - progress];
}

void FindIndicatorWindow::fadeOutAnimationDidEnd()
{
    ASSERT(m_fadeOutAnimation);
    ASSERT(m_findIndicatorWindow);

    closeWindow();
}

void FindIndicatorWindow::bounceAnimationCallback(double progress)
{
    ASSERT(m_bounceAnimation);
    ASSERT(m_bounceAnimationContext);

    WKWindowBounceAnimationSetAnimationProgress(m_bounceAnimationContext, progress);
}

void FindIndicatorWindow::bounceAnimationDidEnd()
{
    ASSERT(m_bounceAnimation);
    ASSERT(m_bounceAnimationContext);
    ASSERT(m_findIndicatorWindow);

    WKWindowBounceAnimationContextDestroy(m_bounceAnimationContext);
    m_bounceAnimationContext = 0;
}

} // namespace WebKit