/*
SRWebView.m

Author: Makoto Kinoshita

Copyright 2004-2006 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 <objc/objc-runtime.h>

#import "SRWebView.h"

@implementation SRGLTransitionView

- (void)dealloc
{
    [_context release];
    _context = nil;
    
    [super dealloc];
}

- (void)setFilter:(CIFilter*)filter
{
    // No retain
    _filter = filter;
}

- (void)setTransitionType:(int)transitionType
{
    _transitionType = transitionType;
}

- (void)setProgress:(NSAnimationProgress)progress
{
    _progress = progress;
    if (_progress < 0.0f) {
        _progress = 0.0f;
    }
    if (_progress > 1.0f) {
        _progress = 1.0f;
    }
}

+ (NSOpenGLPixelFormat*)defaultPixelFormat
{
    static NSOpenGLPixelFormat* _pixelFormat = nil;
    
    if (!_pixelFormat) {
        /* Making sure the context's pixel format doesn't have a recovery
         * renderer is important - otherwise CoreImage may not be able to
         * create deeper context's that share textures with this one. */
        static const NSOpenGLPixelFormatAttribute attr[] = {
            NSOpenGLPFAAccelerated,
            NSOpenGLPFANoRecovery,
            NSOpenGLPFAColorSize, 32,
            0
        };
        
        _pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:(void *)&attr];
    }
    
    return _pixelFormat;
}

- (void)prepareOpenGL
{
    long parm = 1;
    
    /* Enable beam-synced updates. */
    [[self openGLContext] setValues:&parm forParameter:NSOpenGLCPSwapInterval];
    
    /* Make sure that everything we don't need is disabled. Some of these
     * are enabled by default and can slow down rendering. */
    glDisable (GL_ALPHA_TEST);
    glDisable (GL_DEPTH_TEST);
    glDisable (GL_SCISSOR_TEST);
    glDisable (GL_BLEND);
    glDisable (GL_DITHER);
    glDisable (GL_CULL_FACE);
    glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glDepthMask (GL_FALSE);
    glStencilMask (0);
    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
    glHint (GL_TRANSFORM_HINT_APPLE, GL_FASTEST);
    
    _needsReshape = YES;
}

- (void)drawRect:(NSRect)frame
{
    // Make current context
    [[self openGLContext] makeCurrentContext];

    if(_needsReshape) {
        // reset the views coordinate system when the view has been resized or scrolled
        NSRect  visibleRect = [self visibleRect];
        NSRect  mappedVisibleRect = NSIntegralRect([self convertRect: visibleRect toView: [self enclosingScrollView]]);
        
        glViewport (0, 0, mappedVisibleRect.size.width, mappedVisibleRect.size.height);

        glMatrixMode (GL_PROJECTION);
        glLoadIdentity ();
        glOrtho(visibleRect.origin.x,
                        visibleRect.origin.x + visibleRect.size.width,
                        visibleRect.origin.y,
                        visibleRect.origin.y + visibleRect.size.height,
                        -1, 1);

        glMatrixMode (GL_MODELVIEW);
        glLoadIdentity ();
        _needsReshape = NO;
    }
    
    // Create CIContext
    if (_context == nil) {
        NSOpenGLPixelFormat*    pixelFormat;

        pixelFormat = [self pixelFormat];
        if (pixelFormat == nil) {
            pixelFormat = [[self class] defaultPixelFormat];
        }
        
        _context = [[CIContext contextWithCGLContext: CGLGetCurrentContext()
                 pixelFormat: [pixelFormat CGLPixelFormatObj] options: nil] retain];
    }
    
    // Fill the view black
// Do not fill by black?
//    glColor4f (0.0f, 0.0f, 0.0f, 0.0f);
//    glBegin(GL_POLYGON);
//        glVertex2f (frame.origin.x, frame.origin.y);
//        glVertex2f (frame.origin.x + frame.size.width, frame.origin.y);
//        glVertex2f (frame.origin.x + frame.size.width, frame.origin.y + frame.size.height);
//        glVertex2f (frame.origin.x, frame.origin.y + frame.size.height);
//    glEnd();
    
    // Calc currnt progress
    float   currentProgress;
    currentProgress = _progress;
    if (_transitionType == SRWebViewPareCurlBackTransition) {
        currentProgress = 1.0f - currentProgress;
    }
    
    // Get transition image
    CIImage*    image;
    [_filter setValue:[NSNumber numberWithFloat:currentProgress] 
            forKey:@"inputTime"];
    image = [_filter valueForKey:@"outputImage"];
    
    // Draw image
    [_context drawImage:image atPoint:CGPointZero fromRect:*(CGRect*)&frame];
    
    glFlush();
}

@end

#pragma mark -

static NSTimeInterval   SRWebViewDuration = 0.5;
static NSTimeInterval   SRWebViewSlowMotionDuration = 3.0;

extern NSString *_NSPathForSystemFramework(NSString *framework);

BOOL SRHasQuartzFramework()
{
    static BOOL _hasQuartzFramworkd = NO;
    static BOOL _initialized = NO;
    if (!_initialized) {
        // Try to load Quartz.framework
        NSString*   quartzPath;
        quartzPath = _NSPathForSystemFramework(@"Quartz.framework");
        if (quartzPath) {
            NSBundle*   bundle;
            bundle = [NSBundle bundleWithPath:quartzPath];
            if (bundle && [bundle load]) {
                _hasQuartzFramworkd = YES;
            }
        }
        
        _initialized = YES;
    }
    
    return _hasQuartzFramworkd;
}

#pragma mark -

@implementation SRWebView

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

//--------------------------------------------------------------//
#pragma mark -- Transition animation --
//--------------------------------------------------------------//

- (CIImage*)_CIImage
{
    // Get frame
    NSRect  frame;
    frame = [self frame];
    
    // Create CIImage
    NSBitmapImageRep*   bitmap;
    CIImage*            image;
    bitmap = [self bitmapImageRepForCachingDisplayInRect:frame];
    [self cacheDisplayInRect:frame toBitmapImageRep:bitmap];
    image = [[CIImage alloc] initWithBitmapImageRep:bitmap];
    [image autorelease];
    
    return image;
}

- (void)prepareForTransition
{
    // Release previous image
    if (_initialImage) {
        [_initialImage release];
        _initialImage = nil;
    }
    
    // Create CIImage
    _initialImage = [[self _CIImage] retain];
}

- (void)startTransitionOfType:(int)transitionType
{
    if (!SRHasQuartzFramework()) {
        return;
    }
    if (!_initialImage) {
        return;
    }
    if (![self window]) {
        return;
    }
    
    // Get frame
    NSRect  frame;
    frame = [self frame];
    
    // Create CIImage
    CIImage*    finalImage;
    finalImage = [self _CIImage];
    if (!finalImage) {
        return;
    }
    
    // Load shading image
    if (!_inputShadingImage) {
        NSData*             shadingBitmapData;
        NSBitmapImageRep*   shadingBitmapRep;
        shadingBitmapData = [NSData dataWithContentsOfFile:
                [[NSBundle mainBundle] pathForResource:@"restrictedShine" ofType:@"tiff"]];
        shadingBitmapRep = [[NSBitmapImageRep alloc] initWithData:shadingBitmapData];
        [shadingBitmapRep autorelease];
        _inputShadingImage = [[CIImage alloc] initWithBitmapImageRep:shadingBitmapRep];
    }
    
    // Create filter
    _transitionFilter = nil;
    switch (transitionType) {
    case SRWebViewPageCurlTransition: {
        _transitionFilter = [[CIFilter filterWithName:@"CIPageCurlTransition"] retain];
        [_transitionFilter setDefaults];
        //[_transitionFilter setValue:[NSNumber numberWithFloat:M_PI_4] forKey:@"inputAngle"];
        [_transitionFilter setValue:[NSNumber numberWithFloat:0.3] forKey:@"inputAngle"];
        [_transitionFilter setValue:_initialImage forKey:@"inputImage"];
        [_transitionFilter setValue:finalImage forKey:@"inputTargetImage"];
        [_transitionFilter setValue:_initialImage forKey:@"inputBacksideImage"];
        [_transitionFilter setValue:_inputShadingImage forKey:@"inputShadingImage"];
        [_transitionFilter setValue:
                [CIVector vectorWithX:frame.origin.x Y:frame.origin.y Z:frame.size.width W:frame.size.height] 
                forKey:@"inputExtent"];
        break;
    }
    case SRWebViewPareCurlBackTransition: {
        _transitionFilter = [[CIFilter filterWithName:@"CIPageCurlTransition"] retain];
        [_transitionFilter setDefaults];
        //[_transitionFilter setValue:[NSNumber numberWithFloat:M_PI_4] forKey:@"inputAngle"];
        [_transitionFilter setValue:[NSNumber numberWithFloat:0.3] forKey:@"inputAngle"];
        [_transitionFilter setValue:finalImage forKey:@"inputImage"];
        [_transitionFilter setValue:_initialImage forKey:@"inputTargetImage"];
        [_transitionFilter setValue:finalImage forKey:@"inputBacksideImage"];
        [_transitionFilter setValue:_inputShadingImage forKey:@"inputShadingImage"];
        [_transitionFilter setValue:
                [CIVector vectorWithX:frame.origin.x Y:frame.origin.y Z:frame.size.width W:frame.size.height] 
                forKey:@"inputExtent"];
        break;
    }
    case SRWebViewCopyMachineTransition: {
        _transitionFilter = [[CIFilter filterWithName:@"CICopyMachineTransition"] retain];
        [_transitionFilter setDefaults];
        [_transitionFilter setValue:_initialImage forKey:@"inputImage"];
        [_transitionFilter setValue:finalImage forKey:@"inputTargetImage"];
        [_transitionFilter setValue:
                [CIVector vectorWithX:frame.origin.x Y:frame.origin.y Z:frame.size.width W:frame.size.height] 
                forKey:@"inputExtent"];
        break;
    }
    }
    
    // Do transition
    if (_transitionFilter) {
        // Create OpenGL view
        _transitionView = [[SRGLTransitionView alloc] initWithFrame:[self frame]];
        [_transitionView setFilter:_transitionFilter];
        [_transitionView setTransitionType:transitionType];
        
        [self addSubview:_transitionView];
        
        // Decide duration
        float           duration = SRWebViewDuration;
        unsigned int    flags;
        flags = [[NSApp currentEvent] modifierFlags];
        if (!(flags & NSCommandKeyMask) && 
            (flags & NSShiftKeyMask) && 
            !(flags & NSAlternateKeyMask) && 
            !(flags & NSControlKeyMask))
        {
            duration = SRWebViewSlowMotionDuration;
        }
        
        // Start animation
        _transitionAnimation = [[NSAnimation alloc] initWithDuration:duration 
                animationCurve:NSAnimationLinear];
        [_transitionAnimation setAnimationBlockingMode:NSAnimationBlocking];
        [_transitionAnimation setDelegate:self];
#define FRAME_RATE 15
        NSMutableArray* marks;
        marks = [NSMutableArray array];
        int i, markNum;
        markNum = (int)FRAME_RATE * duration;
        for (i = 0; i < markNum; i++) {
            [marks addObject:[NSNumber numberWithFloat:i * 1.0f / markNum]];
        }
        [_transitionAnimation setProgressMarks:marks];
        [_transitionAnimation startAnimation];
        
        // Release OpenGL view
        [_transitionView removeFromSuperviewWithoutNeedingDisplay];
        [_transitionView release];
        
        [[self window] makeFirstResponder:self];
    }
    
    // Clean up
    [_transitionFilter release];
    _transitionFilter = nil;
    [_transitionAnimation release];
    _transitionAnimation = nil;
    [_initialImage release];
    _initialImage = nil;
}

- (int)backOrForward
{
    return _backOrForward;
}

- (void)setBackOrForward:(int)backOrForward
{
    _backOrForward = backOrForward;
}

//--------------------------------------------------------------//
#pragma mark -- Drawing --
//--------------------------------------------------------------//

- (void)drawRect:(NSRect)frame
{
    // Fill with background color
    [[NSColor whiteColor] set];
    NSRectFill(frame);
    
    [super drawRect:frame];
}

//--------------------------------------------------------------//
#pragma mark -- NSAnimation delegate --
//--------------------------------------------------------------//

- (void)animation:(NSAnimation*)animation didReachProgressMark:(NSAnimationProgress)progress
{
    [_transitionView setProgress:progress];
    [_transitionView display];
}

@end
