// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/animatable_image.h"

#include "base/logging.h"
#import "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"

@interface AnimatableImage (Private)
- (void)setLayerContents:(CALayer*)layer;
@end

@implementation AnimatableImage

@synthesize startFrame = startFrame_;
@synthesize endFrame = endFrame_;
@synthesize startOpacity = startOpacity_;
@synthesize endOpacity = endOpacity_;
@synthesize duration = duration_;

- (id)initWithImage:(NSImage*)image
     animationFrame:(NSRect)animationFrame {
  if ((self = [super initWithContentRect:animationFrame
                               styleMask:NSBorderlessWindowMask
                                 backing:NSBackingStoreBuffered
                                   defer:NO])) {
    DCHECK(image);
    image_.reset([image retain]);
    duration_ = 1.0;
    startOpacity_ = 1.0;
    endOpacity_ = 1.0;

    [self setOpaque:NO];
    [self setBackgroundColor:[NSColor clearColor]];
    [self setIgnoresMouseEvents:YES];

    // Must be set or else self will be leaked.
    [self setReleasedWhenClosed:YES];
  }
  return self;
}

- (void)startAnimation {
  // Set up the root layer. By calling -setLayer: followed by -setWantsLayer:
  // the view becomes a layer hosting view as opposed to a layer backed view.
  NSView* view = [self contentView];
  CALayer* rootLayer = [CALayer layer];
  [view setLayer:rootLayer];
  [view setWantsLayer:YES];

  // Create the layer that will be animated.
  CALayer* layer = [CALayer layer];
  [self setLayerContents:layer];
  [layer setAnchorPoint:CGPointMake(0, 1)];
  [layer setFrame:[self startFrame]];
  [layer setNeedsDisplayOnBoundsChange:YES];
  [rootLayer addSublayer:layer];

  // Common timing function for all animations.
  CAMediaTimingFunction* mediaFunction =
      [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

  // Animate the bounds only if the image is resized.
  CABasicAnimation* boundsAnimation = nil;
  if (CGRectGetWidth([self startFrame]) != CGRectGetWidth([self endFrame]) ||
      CGRectGetHeight([self startFrame]) != CGRectGetHeight([self endFrame])) {
    boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
    NSRect startRect = NSMakeRect(0, 0,
                                  CGRectGetWidth([self startFrame]),
                                  CGRectGetHeight([self startFrame]));
    [boundsAnimation setFromValue:[NSValue valueWithRect:startRect]];
    NSRect endRect = NSMakeRect(0, 0,
                                CGRectGetWidth([self endFrame]),
                                CGRectGetHeight([self endFrame]));
    [boundsAnimation setToValue:[NSValue valueWithRect:endRect]];
    [boundsAnimation gtm_setDuration:[self duration]
                           eventMask:NSLeftMouseUpMask];
    [boundsAnimation setTimingFunction:mediaFunction];
  }

  // Positional animation.
  CABasicAnimation* positionAnimation =
      [CABasicAnimation animationWithKeyPath:@"position"];
  [positionAnimation setFromValue:
      [NSValue valueWithPoint:NSPointFromCGPoint([self startFrame].origin)]];
  [positionAnimation setToValue:
      [NSValue valueWithPoint:NSPointFromCGPoint([self endFrame].origin)]];
  [positionAnimation gtm_setDuration:[self duration]
                           eventMask:NSLeftMouseUpMask];
  [positionAnimation setTimingFunction:mediaFunction];

  // Opacity animation.
  CABasicAnimation* opacityAnimation =
      [CABasicAnimation animationWithKeyPath:@"opacity"];
  [opacityAnimation setFromValue:
      [NSNumber numberWithFloat:[self startOpacity]]];
  [opacityAnimation setToValue:[NSNumber numberWithFloat:[self endOpacity]]];
  [opacityAnimation gtm_setDuration:[self duration]
                          eventMask:NSLeftMouseUpMask];
  [opacityAnimation setTimingFunction:mediaFunction];
  // Set the delegate just for one of the animations so that this window can
  // be closed upon completion.
  [opacityAnimation setDelegate:self];

  // The CAAnimations only affect the presentational value of a layer, not the
  // model value. This means that after the animation is done, it can flicker
  // back to the original values. To avoid this, create an implicit animation of
  // the values, which are then overridden with the CABasicAnimations.
  //
  // Ideally, a call to |-setBounds:| should be here, but, for reasons that
  // are not understood, doing so causes the animation to break.
  [layer setPosition:[self endFrame].origin];
  [layer setOpacity:[self endOpacity]];

  // Start the animations.
  [CATransaction begin];
  [CATransaction setValue:[NSNumber numberWithFloat:[self duration]]
                   forKey:kCATransactionAnimationDuration];
  if (boundsAnimation) {
    [layer addAnimation:boundsAnimation forKey:@"bounds"];
  }
  [layer addAnimation:positionAnimation forKey:@"position"];
  [layer addAnimation:opacityAnimation forKey:@"opacity"];
  [CATransaction commit];
}

// Sets the layer contents by converting the NSImage to a CGImageRef.  This will
// rasterize PDF resources.
- (void)setLayerContents:(CALayer*)layer {
  base::mac::ScopedCFTypeRef<CGImageRef> image(
      base::mac::CopyNSImageToCGImage(image_.get()));
  // Create the layer that will be animated.
  [layer setContents:(id)image.get()];
}

// CAAnimation delegate method called when the animation is complete.
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag {
  // Close the window, releasing self.
  [self close];
}

@end