//
//  ECTransitionEditorPanelController.m
//  Etokicho
//
//  Created by 二鏡 on 11/11/25.
//  Copyright 2011年 二鏡庵. All rights reserved.
//

#import "ECTransitionEditorPanelController.h"
#import "ECTransitionSampleView.h"

static NSString *oFlipHorizontalContext = @"flipHorizontal";
static NSString *oTransitionContext = @"transition";

static id _si;

@interface ECTransitionEditorPanelController ()
- (id)destinationImage;
- (void)loadComposition:(QCComposition*)composition
             parameters:(NSDictionary*)aParams;
@end

@implementation ECTransitionEditorPanelController
@synthesize duration, transition, name, compositionID, parameters, flipHorizontal, simulating;
+ (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    if([key isEqualToString: @"hasName"])
        return [NSSet setWithObject: @"name"];
    return [super keyPathsForValuesAffectingValueForKey: key];
}

+ (id)sharedTransitionEditor
{
    if(_si == nil)
        _si = [[self alloc] initWithWindowNibName: @"TransitionEditor"];
    [_si window];
    return _si;
}

- (void)awakeFromNib
{
    self.duration = 1500;
    [[self window] useOptimizedDrawing: YES];

    [pickerView setCompositionsFromRepositoryWithProtocol: QCCompositionProtocolGraphicTransition
                                            andAttributes: nil];

    id imgSrc = [NSImage imageNamed: @"Sample1"];
    id imgDst = [NSImage imageNamed: @"Sample2"];
    
    NSSize size = [imgSrc size];
    [pickerView setCompositionAspectRatio: size];
    [pickerView setShowsCompositionNames: YES];
    [pickerView setAllowsEmptySelection: YES];
    [pickerView setSelectedComposition: nil];
    [pickerView resetDefaultInputValues];
    [pickerView setDefaultValue: imgSrc
                    forInputKey: QCCompositionInputSourceImageKey];
    [pickerView setDefaultValue: imgDst
                    forInputKey: QCCompositionInputDestinationImageKey];
    rendererSize = [renderView recommendSize: size];
    
    id center = [NSNotificationCenter defaultCenter];
    [center addObserver: self
               selector: @selector(compositionDidChange:)
                   name: QCCompositionPickerViewDidSelectCompositionNotification
                 object: pickerView];

    popover.behavior = NSPopoverBehaviorTransient;
    [self addObserver: self
           forKeyPath: oFlipHorizontalContext
              options: 0
              context: oFlipHorizontalContext];
    [self addObserver: self
           forKeyPath: oTransitionContext
              options: 0
              context: oTransitionContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if(context == oFlipHorizontalContext)
    {
        id imgDst = [self destinationImage];
        [renderer setValue: imgDst forInputKey: QCCompositionInputDestinationImageKey];
        [renderView setNeedsDisplay: YES];
        return;
    }
    if(context == oTransitionContext)
    {
        renderView.transition = transition;
        [renderView setNeedsDisplay: YES];
        return;
    }
    [super observeValueForKeyPath: keyPath ofObject: object change:change context: context];
}

#pragma mark Property
- (BOOL)hasComposition
{
    return renderer != nil;
}

- (BOOL)hasName
{
    return [name length] != 0;
}

- (CGImageRef)customSource
{
    return customSource;
}

- (void)setCustomSource:(CGImageRef)image
{
    if(customSource == image)
        return;
    CGImageRelease(customSource);
    customSource = CGImageRetain(image);
    
    CGSize size;
    if(customSource == nil)
    {
        size = [[NSImage imageNamed: @"Sample1"] size];
    }
    else
    {
        size.width = CGImageGetWidth(image);
        size.height = CGImageGetHeight(image);
    }
    rendererSize = [renderView recommendSize: size];
}

- (CGImageRef)customDestination
{
    return customDestination;
}

- (void)setCustomDestination:(CGImageRef)image
{
    if(customDestination == image)
        return;
    CGImageRelease(customDestination);
    customDestination = CGImageRetain(image);
}

#pragma mark internal
- (void)_clearRenderer
{
    self.transition = 0.0;
    [parameterView setCompositionRenderer: nil];
    renderView.renderer = nil;
    [renderer release];
    renderer = nil;
}

- (NSDictionary*)_readRendererParameters
{
    id ret = [[[renderer propertyListFromInputValues] mutableCopy] autorelease];

    [ret removeObjectForKey: QCCompositionInputSourceImageKey];
    [ret removeObjectForKey: QCCompositionInputDestinationImageKey];
    
    return [[ret copy] autorelease];
}

- (void)_time:(NSTimer*)timer
{
    transition += simulation.step;
    transition = MIN(transition,1.0);
    self.transition = transition;
    
    simulation.passed += [timer timeInterval];
    if(simulation.passed >= (CGFloat)duration/1000.0)
    {
        [timer invalidate];
        timer = nil;
        [self willChangeValueForKey: @"simulating"];
        simulating = NO;
        [self didChangeValueForKey: @"simulating"];
    }
}

#pragma mark state initializer
- (void)loadInitialState
{
    // パラメータを破棄
    self.name = @"";
    self.compositionID = @"";
    self.parameters = nil;
    self.duration = 500;
    self.customSource = nil;
    self.customDestination = nil;
}

- (void)loadCompositionWithParameters
{
    // 現在のcompositionID/parametersを用いてcompositionをrepositoryからload
    id repository = [QCCompositionRepository sharedCompositionRepository];
    id composition = [repository compositionWithIdentifier: compositionID];
    if(composition == nil)
    {
        // 生成に失敗する場合。compositionの構成が変更された？
        [self loadInitialState];
    }
    [self loadComposition: composition
               parameters: parameters];    
}

#pragma mark Image source
- (id)sourceImage
{
    if(customSource == nil)
        return [NSImage imageNamed: @"Sample1"];
    else
        return (id)customSource;
}

- (id)destinationImage
{
    if(flipHorizontal == NO)
    {
        if(customSource == nil)
            return [NSImage imageNamed: @"Sample2"];
        else
            return (id)customDestination;
    }

    // 左右を反転した画像を用意
    id inputImage;
    if(customDestination == nil)
    {
        id bundle = [NSBundle mainBundle];
        id url = [bundle URLForResource: @"Sample2" withExtension: @"jpg"];
        inputImage = [CIImage imageWithContentsOfURL:url];
    }
    else
    {
        inputImage = [CIImage imageWithCGImage: customDestination];
    }
    id filter = [CIFilter filterWithName: @"CIAffineTransform"];
    [filter setDefaults];
    
    id trans = [NSAffineTransform transform];
    [trans scaleXBy: -1.0 yBy: 1.0];
    [filter setValue: trans forKey: @"inputTransform"];
    [filter setValue: inputImage forKey: @"inputImage"];
    id dst = [filter valueForKey: @"outputImage"];
    return dst;
}

- (NSInteger)startModal
{
    id window = [self window];
    [window center];
    return [NSApp runModalForWindow: window];
}

- (IBAction)showComposition:(id)sender
{
    [popover showRelativeToRect: [sender bounds]
                         ofView: sender
                  preferredEdge: NSMinYEdge];
}

- (IBAction)ok:(id)sender
{
    self.compositionID = [[renderer composition] identifier];
    self.parameters = [self _readRendererParameters];
    
    [[self window] close];
    [pickerView setSelectedComposition: nil];
    [NSApp stopModalWithCode: NSOKButton];
}

- (IBAction)cancel:(id)sender
{
    [[self window] close];
    [self loadInitialState];
    [pickerView setSelectedComposition: nil];
    [NSApp stopModalWithCode: NSCancelButton];
}

- (IBAction)simulate:(id)sender
{
    self.transition = 0;
    NSUInteger fps = 30; // テストは30fps固定とする
    simulation.step = 1/((CGFloat)duration/1000.0*fps);
    NSTimeInterval interval = 1.0/fps;
    NSTimer *timer = [NSTimer timerWithTimeInterval: interval
                                             target: self
                                           selector: @selector(_time:) 
                                           userInfo: nil
                                            repeats: YES];
    simulation.passed = 0.0;
    [self willChangeValueForKey: @"simulating"];
    simulating = YES;
    [self didChangeValueForKey: @"simulating"];
    [[NSRunLoop currentRunLoop] addTimer: timer
                                 forMode: NSModalPanelRunLoopMode];
    [timer fire];
}

- (void)loadComposition:(QCComposition*)composition
             parameters:(NSDictionary*)aParams
{
    [self _clearRenderer];
    
    [self willChangeValueForKey: @"hasComposition"];
    if(composition != nil)
    {
        renderer = [[QCRenderer alloc] initOffScreenWithSize: rendererSize
                                                  colorSpace: nil
                                                 composition: composition];
        
        [renderer setInputValuesWithPropertyList: aParams];
        // setup sample image
        id imgSrc = [self sourceImage];
        id imgDst = [self destinationImage];
        [renderer setValue: imgSrc forInputKey: QCCompositionInputSourceImageKey];
        [renderer setValue: imgDst forInputKey: QCCompositionInputDestinationImageKey];
                
        // connet to parameter view
        [parameterView setCompositionRenderer: renderer];
        renderView.renderer = renderer;
    }
    [self didChangeValueForKey: @"hasComposition"];
    self.transition = 0.0;
    [renderView setNeedsDisplay: YES];    
}

- (void)compositionDidChange:(id)sender
{
    [popover close];
    id composition = [pickerView selectedComposition];
    [self loadComposition: composition
               parameters: nil];
}

- (BOOL)compositionParameterView:(QCCompositionParameterView *)parameterView
   shouldDisplayParameterWithKey:(NSString *)portKey
                      attributes:(NSDictionary *)portAttributes
{
    if([portKey isEqualToString: QCCompositionInputSourceImageKey])
        return NO;
    if([portKey isEqualToString: QCCompositionInputDestinationImageKey])
        return NO;
    return YES;
}

- (void)compositionParameterView:(QCCompositionParameterView*)parameterView 
        didChangeParameterWithKey:(NSString*)portKey
{
    [renderView setNeedsDisplay: YES];
}

- (BOOL)windowShouldClose:(id)sender
{
    return simulating != NO;
}
@end
