//
//  InputSource.m
//  CocoaSplit
//
//  Created by Zakk on 7/17/14.
//  Copyright (c) 2014 Zakk. All rights reserved.
//

#import "InputSource.h"
#import "CSCaptureSourceProtocol.h"
#import "SourceLayout.h"
#import "InputPopupControllerViewController.h"
#import <objc/runtime.h>

static NSArray *_sourceTypes = nil;


@class Foobar;


@implementation InputSource



@synthesize selectedVideoType = _selectedVideoType;
@synthesize name = _name;
@synthesize transitionFilterName = _transitionFilterName;
@synthesize is_selected = _is_selected;
@synthesize active = _active;
@synthesize is_live = _is_live;
@synthesize crop_left = _crop_left;
@synthesize crop_right = _crop_right;
@synthesize crop_top = _crop_top;
@synthesize crop_bottom = _crop_bottom;
@synthesize opacity = _opacity;
@synthesize rotationAngle = _rotationAngle;
@synthesize rotationAngleX = _rotationAngleX;
@synthesize rotationAngleY = _rotationAngleY;

@synthesize scrollXSpeed = _scrollXSpeed;
@synthesize scrollYSpeed = _scrollYSpeed;
@synthesize doChromaKey = _doChromaKey;
@synthesize chromaKeyColor = _chromaKeyColor;
@synthesize chromaKeySmoothing = _chromaKeySmoothing;
@synthesize chromaKeyThreshold = _chromaKeyThreshold;
@synthesize clonedFromInput = _clonedFromInput;
@synthesize transitionEnabled = _transitionEnabled;
@synthesize x_pos = _x_pos;
@synthesize y_pos = _y_pos;
@synthesize width = _width;
@synthesize height = _height;
@synthesize compositingFilterName = _compositingFilterName;


-(instancetype)copyWithZone:(NSZone *)zone
{
    [CATransaction begin];
    InputSource *newSource = [[InputSource allocWithZone:zone] init];
    newSource.name = _editedName;

    newSource.videoInput = self.videoInput;
    [newSource registerVideoInput:self.videoInput];
    newSource->_currentLayer = [self.videoInput layerForInput:newSource];
    newSource.layer.sourceLayer = newSource->_currentLayer;
    
    newSource.rotationAngle = self.rotationAngle;
    newSource.rotationAngleY = self.rotationAngleY;
    newSource.rotationAngleX = self.rotationAngleX;
    newSource.opacity =  self.opacity;
    
    newSource.depth = self.depth;
    newSource.crop_top = self.crop_top;
    newSource.crop_bottom = self.crop_bottom;
    newSource.crop_left = self.crop_left;
    newSource.crop_right = self.crop_right;
    newSource.scrollXSpeed = self.scrollXSpeed;
    newSource.scrollYSpeed = self.scrollYSpeed;
    newSource.rotateStyle = self.rotateStyle;
    newSource.doChromaKey = self.doChromaKey;
    
    newSource.chromaKeyColor = self.chromaKeyColor;
    newSource.chromaKeyThreshold = self.chromaKeyThreshold;
    newSource.chromaKeySmoothing = self.chromaKeySmoothing;
    newSource.videoSources = self.videoSources;
    for(NSObject <CSCaptureSourceProtocol> *vsrc in newSource.videoSources)
    {
        [newSource registerVideoInput:vsrc];
    }
    
    newSource.transitionDuration = self.transitionDuration;
    newSource.transitionFilterName = self.transitionFilterName;
    newSource.transitionDirection = self.transitionDirection;
    newSource.advancedTransition = self.advancedTransition;
    
    

    
    newSource.changeInterval = self.changeInterval;
    newSource.layer.position = self.layer.position;
    newSource.layer.bounds = self.layer.bounds;
    newSource.borderWidth = self.borderWidth;
    newSource.borderColor = self.borderColor;
    newSource.cornerRadius  = self.cornerRadius;
    newSource.backgroundColor = self.backgroundColor;
    
    
    newSource.startColor = self.startColor;
    newSource.stopColor = self.stopColor;
    newSource.gradientStartX = self.gradientStartX;
    newSource.gradientStartY = self.gradientStartY;
    newSource.gradientStopX = self.gradientStopX;
    newSource.gradientStopY = self.gradientStopY;

    [CATransaction commit];
    return newSource;
}




-(void) encodeWithCoder:(NSCoder *)aCoder
{
    
    [aCoder encodeFloat:self.rotationAngle forKey:@"rotationAngle"];
    [aCoder encodeFloat:self.rotationAngleX forKey:@"rotationAngleX"];
    [aCoder encodeFloat:self.rotationAngleY forKey:@"rotationAngleY"];

    [aCoder encodeFloat:self.opacity forKey:@"opacity"];
    
    [aCoder encodeObject:_editedName forKey:@"name"];
    [aCoder encodeFloat:self.depth forKey:@"CAdepth"];
    [aCoder encodeFloat:self.crop_top forKey:@"CAcrop_top"];
    [aCoder encodeFloat:self.crop_bottom forKey:@"CAcrop_bottom"];
    [aCoder encodeFloat:self.crop_left forKey:@"CAcrop_left"];
    [aCoder encodeFloat:self.crop_right forKey:@"CAcrop_right"];
    
    [aCoder encodeObject:self.selectedVideoType forKey:@"selectedVideoType"];
    [aCoder encodeObject:self.uuid forKey:@"uuid"];

    [aCoder encodeFloat:self.scrollXSpeed forKey:@"scrollXSpeed"];
    [aCoder encodeFloat:self.scrollYSpeed forKey:@"scrollYSpeed"];
    
    [aCoder encodeInt:self.rotateStyle forKey:@"rotateStyle"];
    if (self.videoInput)
    {
        [aCoder encodeObject:self.videoInput forKey:@"videoInput"];
    }
    
    if (self.sourceLayout)
    {
        [aCoder encodeFloat:self.canvas_width forKey:@"topLevelWidth"];
        [aCoder encodeFloat:self.canvas_height forKey:@"topLevelHeight"];
    } else {
        [aCoder encodeFloat:_topLevelWidth forKey:@"topLevelWidth"];
        [aCoder encodeFloat:_topLevelHeight forKey:@"topLevelHeight"];
    }
    
    
    [aCoder encodeBool:self.doChromaKey forKey:@"doChromaKey"];
    [aCoder encodeObject:self.chromaKeyColor forKey:@"chromaKeyColor"];
    [aCoder encodeFloat:self.chromaKeyThreshold forKey:@"chromaKeyThreshold"];
    [aCoder encodeFloat:self.chromaKeySmoothing forKey:@"chromaKeySmoothing"];
    [aCoder encodeObject:self.videoSources forKey:@"videoSources"];
    [aCoder encodeObject:self.currentEffects forKey:@"currentEffects"];
    [aCoder encodeFloat:self.changeInterval forKey:@"changeInterval"];

    [aCoder encodeFloat:self.layer.position.x forKey:@"CAx_pos"];
    [aCoder encodeFloat:self.layer.position.y forKey:@"CAy_pos"];
    
    [aCoder encodeFloat:self.layer.bounds.size.width forKey:@"CAdisplay_width"];
    [aCoder encodeFloat:self.layer.bounds.size.height forKey:@"CAdisplay_height"];
    
    [aCoder encodeFloat:self.borderWidth forKey:@"borderWidth"];
    [aCoder encodeObject:self.borderColor forKey:@"borderColor"];
    [aCoder encodeFloat:self.cornerRadius forKey:@"cornerRadius"];
    [aCoder encodeBool:_userBackground forKey:@"userBackground"];
    [aCoder encodeObject:self.transitionFilterName forKey:@"transitionFilterName"];
    [aCoder encodeObject:self.transitionDirection forKey:@"transitionDirection"];
    [aCoder encodeFloat:self.transitionDuration forKey:@"transitionDuration"];
    [aCoder encodeObject:self.advancedTransition forKey:@"advancedTransition"];
    
    [aCoder encodeObject:self.parentInput forKey:@"parentInput"];
    
    
    //if we directly encode constraintMap the resulting NSData is not equal to an 'equal' InputSource, so double encode?
    
    NSData *constraintData = [NSKeyedArchiver archivedDataWithRootObject:self.constraintMap];

    [aCoder encodeObject:constraintData forKey:@"constraintMapData"];
    
    //[aCoder encodeObject:self.constraintMap forKey:@"constraintMap"];
    
    [aCoder encodeObject:self.startColor forKey:@"gradientStartColor"];
    [aCoder encodeObject:self.stopColor forKey:@"gradientStopColor"];
    [aCoder encodeFloat: self.gradientStartX forKey:@"gradientStartPointX"];
    [aCoder encodeFloat: self.gradientStartY forKey:@"gradientStartPointY"];
    [aCoder encodeFloat: self.gradientStopX forKey:@"gradientEndPointX"];
    [aCoder encodeFloat: self.gradientStopY forKey:@"gradientEndPointY"];


    
    [aCoder encodeObject:self.layer.filters forKey:@"layerFilters"];
    if (_userBackground)
    {
        [aCoder encodeObject:self.backgroundColor forKey:@"backgroundColor"];
    }
    
    [aCoder encodeObject:self.compositingFilterName forKey:@"compositingFilterName"];
    
    [aCoder encodeBool:self.alwaysDisplay forKey:@"alwaysDisplay"];
    [aCoder encodeBool:self.transitionEnabled forKey:@"transitionEnabled"];
    
}


-(id) initWithCoder:(NSCoder *)aDecoder
{
    /*
    There's some 'legacy' stuff in here to support loading older CocoaSplit save formats. Mostly related to the change from CoreImage to CoreAnimation. some types changed, some stuff like x/y position changed names. It's probably a bit slower when you're saving->going live but meh. Problematic variables are encoded with a prefix of 'CA' to avoid excessive use of try/catch
     */
    
    if (self = [super init])
    {
        
        [CATransaction begin];
        [self commonInit];
        
        
        _userBackground = [aDecoder decodeBoolForKey:@"userBackground"];
        if (_userBackground)
        {
            self.backgroundColor = [aDecoder decodeObjectForKey:@"backgroundColor"];
        }

        
        self.videoInput = [aDecoder decodeObjectForKey:@"videoInput"];

        if (self.videoInput)
        {
            
            [self registerVideoInput:self.videoInput];

            _currentLayer = [self.videoInput layerForInput:self];
            //self.layer.sourceLayer = _currentLayer;
            if (!_userBackground)
            {
                self.backgroundColor = nil;
                _userBackground = NO;
            }
        }
        
        


        
        float x_pos,y_pos,width,height;
        
        if ([aDecoder containsValueForKey:@"CAx_pos"])
        {
            x_pos = [aDecoder decodeFloatForKey:@"CAx_pos"];
            y_pos = [aDecoder decodeFloatForKey:@"CAy_pos"];

        } else {
        //at one point [xy]_pos was an integer, so if the float fails try integer. otherwise die.
            @try {
                x_pos = [aDecoder decodeFloatForKey:@"x_pos"];
                y_pos = [aDecoder decodeFloatForKey:@"y_pos"];
            } @catch (NSException *e) {
                x_pos = [aDecoder decodeIntForKey:@"x_pos"];
                y_pos = [aDecoder decodeIntForKey:@"y_pos"];
                
            }
        }
        
        if ([aDecoder containsValueForKey:@"CAdisplay_width"])
        {
            width = [aDecoder decodeFloatForKey:@"CAdisplay_width"];
            height = [aDecoder decodeFloatForKey:@"CAdisplay_height"];

            
        } else {
            width = [aDecoder decodeIntForKey:@"display_width"];
            height = [aDecoder decodeIntForKey:@"display_height"];
            
        }


        NSRect tmpRect = NSIntegralRect(NSMakeRect(x_pos, y_pos, width, height));
        self.layer.position = CGPointMake(tmpRect.origin.x, tmpRect.origin.y);
        self.layer.bounds = CGRectMake(0, 0, tmpRect.size.width, tmpRect.size.height);
        



        id constraintData = [aDecoder decodeObjectForKey:@"constraintMap"];
        if (constraintData)
        {
            self.constraintMap = constraintData;
        } else if ((constraintData = [aDecoder decodeObjectForKey:@"constraintMapData"])) {
            self.constraintMap = [NSKeyedUnarchiver unarchiveObjectWithData:constraintData];
        }
        
        
        
        
        _rotationAngle = [aDecoder decodeFloatForKey:@"rotationAngle"];
        _rotationAngleX = [aDecoder decodeFloatForKey:@"rotationAngleX"];
        _rotationAngleY = [aDecoder decodeFloatForKey:@"rotationAngleY"];
        [self updateRotationTransform];


        self.layoutPosition = self.layer.frame;
        

        _selectedVideoType = [aDecoder decodeObjectForKey:@"selectedVideoType"];


        self.opacity = [aDecoder decodeFloatForKey:@"opacity"];
        self.name = [aDecoder decodeObjectForKey:@"name"];
        //Old Cocoasplit encoded this as an integer. CoreAnimation wants a float.
        if ([aDecoder containsValueForKey:@"CAdepth"])
        {
            self.depth = [aDecoder decodeFloatForKey:@"CAdepth"];

        } else {
            self.depth = [aDecoder decodeIntForKey:@"depth"];
        }
        
        if ([aDecoder containsValueForKey:@"CAcrop_top"])
        {
            _crop_top = [aDecoder decodeFloatForKey:@"CAcrop_top"];
            _crop_bottom = [aDecoder decodeFloatForKey:@"CAcrop_bottom"];
            _crop_left = [aDecoder decodeFloatForKey:@"CAcrop_left"];
            _crop_right = [aDecoder decodeFloatForKey:@"CAcrop_right"];
            [self setCropRectWithoutResize];
        } else {
            _crop_top = [aDecoder decodeIntForKey:@"crop_top"];
            _crop_bottom = [aDecoder decodeIntForKey:@"crop_bottom"];
            _crop_left = [aDecoder decodeIntForKey:@"crop_left"];
            _crop_right = [aDecoder decodeIntForKey:@"crop_right"];
            [self setCropRectWithoutResize];
        }

        
        
        self.uuid = [aDecoder decodeObjectForKey:@"uuid"];
        
        self.videoSources = [aDecoder decodeObjectForKey:@"videoSources"];
        if (!self.videoSources)
        {
            self.videoSources = [[NSMutableArray alloc] init];
        }
        
        
        
        
        self.rotateStyle = [aDecoder decodeIntForKey:@"rotateStyle"];
        
        self.currentEffects = [aDecoder decodeObjectForKey:@"currentEffects"];
        if (!self.currentEffects)
        {
            self.currentEffects = [[NSMutableArray alloc] init];
        }
        
        if ([aDecoder containsValueForKey:@"doChromaKey"])
        {
            self.doChromaKey = [aDecoder decodeBoolForKey:@"doChromaKey"];
        }
        
        if ([aDecoder containsValueForKey:@"chromaKeyColor"])
        {

            self.chromaKeyColor = [aDecoder decodeObjectForKey:@"chromaKeyColor"];
        }
        
        if ([aDecoder containsValueForKey:@"chromaKeyThreshold"])
        {

            self.chromaKeyThreshold = [aDecoder decodeFloatForKey:@"chromaKeyThreshold"];
        }
        
        if ([aDecoder containsValueForKey:@"chromaKeySmoothing"])
        {
            
            self.chromaKeySmoothing = [aDecoder decodeFloatForKey:@"chromaKeySmoothing"];
        }

        
        self.transitionDirection = [aDecoder decodeObjectForKey:@"transitionDirection"];
        self.transitionFilterName = [aDecoder decodeObjectForKey:@"transitionFilterName"];
        self.transitionDuration = [aDecoder decodeFloatForKey:@"transitionDuration"];
        self.advancedTransition = [aDecoder decodeObjectForKey:@"advancedTransition"];
        
        
        for(id vInput in self.videoSources)
        {
            [self registerVideoInput:vInput];
        }
        
        if ([aDecoder containsValueForKey:@"changeInterval"])
        {
            self.changeInterval = [aDecoder decodeFloatForKey:@"changeInterval"];
        }
        
        
        self.scrollXSpeed = [aDecoder decodeFloatForKey:@"scrollXSpeed"];
        self.scrollYSpeed = [aDecoder decodeFloatForKey:@"scrollYSpeed"];
        

        self.borderWidth = [aDecoder decodeFloatForKey:@"borderWidth"];
        self.borderColor = [aDecoder decodeObjectForKey:@"borderColor"];
        self.cornerRadius = [aDecoder decodeFloatForKey:@"cornerRadius"];
        self.layoutPosition = self.layer.frame;
        

        
        self.startColor = [aDecoder decodeObjectForKey:@"gradientStartColor"];
        self.stopColor = [aDecoder decodeObjectForKey:@"gradientStopColor"];
        
        
        self.gradientStartX = [aDecoder decodeFloatForKey:@"gradientStartPointX"];
        self.gradientStartY = [aDecoder decodeFloatForKey:@"gradientStartPointY"];
        
        self.gradientStopX = [aDecoder decodeFloatForKey:@"gradientEndPointX"];
        self.gradientStopY = [aDecoder decodeFloatForKey:@"gradientEndPointY"];
        
        
        if ([aDecoder containsValueForKey:@"topLevelWidth"])
        {
            _topLevelWidth = [aDecoder decodeFloatForKey:@"topLevelWidth"];
        }
        
        if ([aDecoder containsValueForKey:@"topLevelHeight"])
        {
            _topLevelHeight = [aDecoder decodeFloatForKey:@"topLevelHeight"];
        }
        
        
        self.layer.filters = [aDecoder decodeObjectForKey:@"layerFilters"];
        self.compositingFilterName = [aDecoder decodeObjectForKey:@"compositingFilterName"];
        if ([aDecoder containsValueForKey:@"alwaysDisplay"])
        {
            self.alwaysDisplay = [aDecoder decodeBoolForKey:@"alwaysDisplay"];
        }

        if ([aDecoder containsValueForKey:@"transitionEnabled"])
        {
            self.transitionEnabled = [aDecoder decodeBoolForKey:@"transitionEnabled"];
        }
        
        [CATransaction commit];

        InputSource *parentInput = [aDecoder decodeObjectForKey:@"parentInput"];
        self.parentInput = parentInput;
        
        if (self.parentInput)
        {
            
            [self.parentInput.layer addSublayer:self.layer];
            [self.parentInput.attachedInputs addObject:self];
        }

        
    }
    
    return self;
}



-(void)addedToLayout
{
    if (self.parentInput)
    {
        
        [self.parentInput.layer addSublayer:self.layer];
        [self.parentInput.attachedInputs addObject:self];
    }

}
-(CGRect)globalLayoutPosition
{
    return [self.sourceLayout.rootLayer convertRect:self.layoutPosition fromLayer:self.layer.superlayer];
}

-(void) registerVideoInput:(NSObject<CSCaptureSourceProtocol> *)forInput
{
    forInput.inputSource = self;
    forInput.isLive = self.is_live;
    [forInput addObserver:self forKeyPath:@"captureName" options:NSKeyValueChangeNewKey context:NULL];
    [forInput createNewLayerForInput:self];

}

-(void)deregisterVideoInput:(NSObject<CSCaptureSourceProtocol> *)forInput
{
    if (!forInput)
    {
        return;
    }
    
    forInput.isLive = NO;
    [forInput removeLayerForInput:self];
    [forInput removeObserver:self forKeyPath:@"captureName"];
    
}



-(void)restoreConstraints
{
    self.constraintMap = _restoredConstraintMap;
    _restoredConstraintMap = nil;
}


-(void)resetConstraints
{
    [self initDictionaryForConstraints:self.constraintMap];
}

-(id)init
{
    if (self = [super init])
    {
        [self commonInit];
    }
    return self;
}


-(void)commonInit
{

    [CATransaction begin];
    self.name = nil;
    
    _nextImageTime = 0.0f;
    _currentSourceIdx = 0;
    _needsAdjustment = NO;
    _needsAdjustPosition = NO;
    _topLevelHeight = 0;
    _topLevelWidth = 0;
    
    self.changeInterval = 20.0f;
    
    
    self.attachedInputs = [NSMutableArray array];
    
    self.scrollXSpeed = 0.0f;
    self.scrollYSpeed = 0.0f;
    
    self.lockSize = YES;
    
    self.rotationAngle =  0.0f;
    self.depth = 0;
    self.opacity = 1.0f;
    self.crop_bottom = 0;
    self.crop_top = 0;
    self.crop_left = 0;
    self.crop_right = 0;
    self.videoSources = [[NSMutableArray alloc] init];
    _refCount = 0;
    
    self.constraintMap = [NSMutableDictionary dictionary];

    
    _constraintAttributeMap = @{@"LeftEdge": @(kCAConstraintMinX),
                                @"RightEdge": @(kCAConstraintMaxX),
                                @"TopEdge": @(kCAConstraintMaxY),
                                @"BottomEdge": @(kCAConstraintMinY),
                                @"HorizontalCenter": @(kCAConstraintMidX),
                                @"VerticalCenter": @(kCAConstraintMidY),
                                @"Width": @(kCAConstraintWidth),
                                @"Height": @(kCAConstraintHeight),
                                };

    [self initDictionaryForConstraints:self.constraintMap];
    
    
    NSMutableArray *tmpArr = [NSMutableArray array];
    
    for (NSString *base in self.constraintMap.allKeys)
    {
        [tmpArr addObject:[NSString stringWithFormat:@"constraintMap.%@.attr", base]];
        [tmpArr addObject:[NSString stringWithFormat:@"constraintMap.%@.offset", base]];
    }
    
    
    _constraintObserveKeys = tmpArr;
    
    
    self.transitionFilterName = @"fade";
    self.currentEffects = [[NSMutableArray alloc] init];
    
    self.unlock_aspect = NO;
    self.resizeType = kResizeNone;
    
    
    self.layer = [CSInputLayer layer];
    self.layer.contentsGravity = kCAGravityTopRight;
    
    self.layer.sourceInput = self;
    
    //self.layer.anchorPoint = CGPointMake(0.0, 0.0);
    
    //self.layer.backgroundColor = CGColorCreateGenericRGB(0, 0, 1, 1);

    self.layer.bounds = CGRectMake(0.0, 0.0, 200, 200);
    [self positionOrigin:0.0 y:0.0];

    
    
    CIFilter *cFilter = [CIFilter filterWithName:@"CSChromaKeyFilter"];
    [cFilter setDefaults];
    cFilter.name = @"Chromakey";
    cFilter.enabled = NO;
    
    self.layer.sourceLayer.filters = @[cFilter];
    
    _multiTransition = [CATransition animation];
    _multiTransition.type = kCATransitionPush;
    _multiTransition.subtype = kCATransitionFromRight;
    _multiTransition.duration = 2.0;
    _multiTransition.removedOnCompletion = YES;
    
    
    [self createUUID];
    
   
    self.layoutPosition = self.layer.frame;
    self.active = YES;
    self.transitionNames = @[kCATransitionFade, kCATransitionPush, kCATransitionMoveIn, kCATransitionReveal, @"cube", @"alignedCube", @"flip", @"alignedFlip"];
    self.transitionDirections = @[kCATransitionFromTop, kCATransitionFromRight, kCATransitionFromBottom, kCATransitionFromLeft];
    self.transitionDirection = kCATransitionFromRight;
    self.transitionDuration = 2.0f;
    
    self.doChromaKey = NO;
    self.chromaKeyThreshold = 0.1005f;
    self.chromaKeySmoothing = 0.1344f;
    
    self.chromaKeyColor = [NSColor greenColor];
    _userBackground = NO;
    self.layer.backgroundColor = CGColorCreateGenericRGB(0, 0, 1, 1);
    _currentInput = self;
    

    [self observeConstraintKeys];
    [CATransaction commit];
    _undoActionMap = @{@"name": @"Set Name",
                       @"crop_top": @"Crop Top",
                       @"changeInterval": @"Change Interval",
                       @"gradientStartX": @"Gradient Start Color X",
                       @"gradientStartY": @"Gradient Start Color Y",
                       @"gradientStopX": @"Gradient Stop Color X",
                       @"gradientStopY": @"Gradient Stop Color Y",
                       @"startColor": @"Gradient Start Color",
                       @"stopColor": @"Gradient Stop Color",
                       @"backgroundColor": @"Background Color",
                       @"borderColor": @"Border Color",
                       @"cornerRadius": @"Border Corner Radius",
                       @"borderWidth": @"Border Width",
                       @"compositingFilterName": @"Composition Filter",
                       @"rotationAngle": @"Rotation",
                       @"rotationAngleX": @"X Rotation",
                       @"rotationAngleY": @"Y Rotation",
                       @"transitionDuration": @"Effect Duration",
                       @"transitionDirection": @"Transition Direction",
                       @"transitionEnabled": @"Enable Transitions",
                       @"alwaysDisplay": @"Always Show",
                       @"transitionFilterName": @"Transition Effect",
                       @"width": @"Width",
                       @"height": @"Height",
                       @"opacity": @"Opacity",
                       @"scrollXSpeed": @"Horizontal Scroll Speed",
                       @"scrollYSpeed": @"Vertical Scroll Speed",
                       @"crop_left": @"Crop Left",
                       @"crop_right": @"Crop Right",
                       @"crop_bottom": @"Crop Bottom",
                       @"chromaKeyColor": @"Chroma Key Color",
                       @"chromaKeySmoothing": @"CK Smoothing",
                       @"chromaKeyThreshold": @"CK Threshold",
                       @"rotateStyle": @"Order",
                       };
    
 }



-(void)createUUID
{
    CFUUIDRef tmpUUID = CFUUIDCreate(NULL);
    self.uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, tmpUUID);
    CFRelease(tmpUUID);

}

-(void)setRotateStyle:(input_rotate_style)rotateStyle
{
    _rotateStyle = rotateStyle;
}

-(input_rotate_style)rotateStyle
{
    return _rotateStyle;
}


-(void)setChangeInterval:(float)changeInterval
{
    
    _changeInterval = changeInterval;
}

-(float)changeInterval
{
    return _changeInterval;
}


-(void)initDictionaryForConstraints:(NSMutableDictionary *)dict
{
    NSArray *baseKeys = @[@"LeftEdge", @"RightEdge", @"TopEdge", @"BottomEdge", @"HorizontalCenter", @"VerticalCenter", @"Width", @"Height"];
    
    
    for (NSString *base in baseKeys)
    {
        NSMutableDictionary *valDict = [[NSMutableDictionary alloc] init];
        [valDict setObject:[NSNumber numberWithInt:0] forKey:@"attr"];
        [valDict setObject:[NSNumber numberWithInt:0] forKey:@"offset"];
        [dict setObject:valDict forKey:base];
    }
}

-(float)display_height
{
    return self.layoutPosition.size.height;
}

-(float)display_width
{
    return self.layoutPosition.size.width;
}


-(float)depth
{
    return self.layer.zPosition;
}

-(void)setDepth:(float)depth
{
    [CATransaction begin];
    self.layer.zPosition = depth;
    [CATransaction commit];
}




-(void)setGradientStartX:(CGFloat)gradientStartX
{
    self.layer.gradientStartX = gradientStartX;
}

-(CGFloat)gradientStartX
{
    return self.layer.gradientStartX;
}

-(void)setGradientStartY:(CGFloat)gradientStartY
{
    self.layer.gradientStartY = gradientStartY;
}

-(CGFloat)gradientStartY
{
    return self.layer.gradientStartY;
}

-(void)setGradientStopX:(CGFloat)gradientStopX
{
    self.layer.gradientStopX = gradientStopX;
}

-(CGFloat)gradientStopX
{
    return self.layer.gradientStopX;
}

-(void)setGradientStopY:(CGFloat)gradientStopY
{
    self.layer.gradientStopY = gradientStopY;
}

-(CGFloat)gradientStopY
{
    return self.layer.gradientStopY;
}

-(NSColor *)startColor
{
    return self.layer.startColor;
}


-(void)setStartColor:(NSColor *)startColor
{

    self.layer.startColor = startColor;
}

-(NSColor *)stopColor
{
    return self.layer.stopColor;
}


-(void)setStopColor:(NSColor *)stopColor
{
    
    self.layer.stopColor = stopColor;
}


-(void)clearBackground
{
    self.backgroundColor = nil;
    _userBackground = YES;
}


-(void)setBackgroundColor:(NSColor *)backgroundColor
{
    

    [CATransaction begin];
    _userBackground = YES;

    if (backgroundColor)
    {
        self.layer.backgroundColor = [backgroundColor CGColor];
    } else {
        self.layer.backgroundColor = NULL;
    }
    [CATransaction commit];
}

-(NSColor *)backgroundColor
{
    if (self.layer.backgroundColor)
    {
        return [NSColor colorWithCGColor:self.layer.backgroundColor];
    } else {
        return nil;
    }
}


-(void)setBorderColor:(NSColor *)borderColor
{

    [CATransaction begin];
    self.layer.borderColor = [borderColor CGColor];
    [CATransaction commit];
}

-(NSColor *)borderColor
{
    if (self.layer.borderColor)
    {
        return [NSColor colorWithCGColor:self.layer.borderColor];
    } else {
        return nil;
    }
}

-(void)setCornerRadius:(CGFloat)cornerRadius
{

    [CATransaction begin];
    self.layer.cornerRadius = cornerRadius;
    [CATransaction commit];
}

-(CGFloat)cornerRadius
{
    return self.layer.cornerRadius;
}


-(void)setBorderWidth:(CGFloat)borderWidth
{
    
    
    
    [CATransaction begin];
    self.layer.borderWidth = borderWidth;
    [CATransaction commit];
    
}

-(CGFloat)borderWidth
{
    return self.layer.borderWidth;
}


-(NSString *)compositingFilterName
{
    return _compositingFilterName;
}

-(void)setCompositingFilterName:(NSString *)compositingFilterName
{
    
    CIFilter *newFilter = nil;
    if (compositingFilterName)
    {
        newFilter = [CIFilter filterWithName:compositingFilterName];
    }
    [CATransaction begin];
    self.layer.compositingFilter = newFilter;
    [CATransaction commit];
    _compositingFilterName = compositingFilterName;
}


-(void)setIsMaskLayer:(bool)isMaskLayer
{
    

    [self.sourceLayout.undoManager disableUndoRegistration];
    if (isMaskLayer)
    {
        self.compositingFilterName = @"CIMinimumCompositing";
    } else if ([self.compositingFilterName isEqualToString:@"CIMinimumCompositing"]) {
        self.compositingFilterName = nil;
    }
    [self.sourceLayout.undoManager enableUndoRegistration];
    
}


-(bool)isMaskLayer
{
    return [self.compositingFilterName isEqualToString:@"CIMinimumCompositing"];
}



-(CIFilter *) filter:(NSString *)filterUUID fromArray:(NSArray *)fromArray
{
    
    CIFilter *ret = nil;
    NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
        CIFilter *obj = (CIFilter *)evaluatedObject;
        return [obj.name isEqualToString:filterUUID];
    }];
    
    NSArray *results = [fromArray filteredArrayUsingPredicate:predicate];
    if (results)
    {
        ret = results.firstObject;
    }
    
    return ret;
}


-(NSMutableArray *)newFilterArray:(NSArray *)filters withoutName:(NSString *)withoutName
{
    NSMutableArray *ret = [NSMutableArray array];
    
    for (CIFilter *filter in filters)
    {
        if ([filter.name isEqualToString:withoutName])
        {
            continue;
        }
        
        [ret addObject:filter];
    }
    
    return ret;
}


-(void)deleteLayerFilter:(NSString *)filteruuid
{
    CIFilter *toDelete = [self filter:filteruuid fromArray:self.layer.filters];
    if (toDelete)
    {
        NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:toDelete];
        __weak InputSource *weakSelf = self;
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            CIFilter *newFilter = [NSKeyedUnarchiver unarchiveObjectWithData:saveData];
            NSMutableArray *cFilters = weakSelf.layer.filters.mutableCopy;
            if (!cFilters)
            {
                cFilters = [NSMutableArray array];
            }
            [cFilters addObject:newFilter];
            [CATransaction begin];
            weakSelf.layer.filters = cFilters;
            [CATransaction commit];
            [[self.sourceLayout.undoManager prepareWithInvocationTarget:weakSelf.sourceLayout] modifyUUID:weakSelf.uuid withBlock:^(InputSource *input) {
                [input deleteLayerFilter:newFilter.name];
            }];
            [self.sourceLayout.undoManager setActionName:@"Add Input Filter"];
            
            
            
        }];
        [self.sourceLayout.undoManager setActionName:@"Delete Input Filter"];
        
    }

    [CATransaction begin];
    self.layer.filters = [self newFilterArray:self.layer.filters withoutName:filteruuid];
    [CATransaction commit];
}

-(void)deleteSourceFilter:(NSString *)filteruuid
{
    CIFilter *toDelete = [self filter:filteruuid fromArray:self.layer.sourceLayer.filters];
    if (toDelete)
    {
        NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:toDelete];
        __weak InputSource *weakSelf = self;
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            CIFilter *newFilter = [NSKeyedUnarchiver unarchiveObjectWithData:saveData];
            NSMutableArray *cFilters = weakSelf.layer.sourceLayer.filters.mutableCopy;
            if (!cFilters)
            {
                cFilters = [NSMutableArray array];
            }
            [cFilters addObject:newFilter];
            [CATransaction begin];
            weakSelf.layer.sourceLayer.filters = cFilters;
            [CATransaction commit];
            [[self.sourceLayout.undoManager prepareWithInvocationTarget:weakSelf.sourceLayout] modifyUUID:weakSelf.uuid withBlock:^(InputSource *input) {
                [input deleteSourceFilter:newFilter.name];
            }];
            [self.sourceLayout.undoManager setActionName:@"Add Source Filter"];
            
            
            
        }];
        [self.sourceLayout.undoManager setActionName:@"Delete Source Filter"];
        
    }

    [CATransaction begin];
    self.layer.sourceLayer.filters = [self newFilterArray:self.layer.sourceLayer.filters withoutName:filteruuid];
    [CATransaction commit];
}

-(void)deleteBackgroundFilter:(NSString *)filteruuid
{
    CIFilter *toDelete = [self filter:filteruuid fromArray:self.layer.backgroundFilters];
    if (toDelete)
    {
        NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:toDelete];
        __weak InputSource *weakSelf = self;
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            CIFilter *newFilter = [NSKeyedUnarchiver unarchiveObjectWithData:saveData];
            NSMutableArray *cFilters = weakSelf.layer.backgroundFilters.mutableCopy;
            if (!cFilters)
            {
                cFilters = [NSMutableArray array];
            }
            [cFilters addObject:newFilter];
            [CATransaction begin];
            weakSelf.layer.backgroundFilters = cFilters;
            [CATransaction commit];
            [[self.sourceLayout.undoManager prepareWithInvocationTarget:weakSelf.sourceLayout] modifyUUID:weakSelf.uuid withBlock:^(InputSource *input) {
                [input deleteBackgroundFilter:newFilter.name];
            }];
            [self.sourceLayout.undoManager setActionName:@"Add Background Filter"];



        }];
        [self.sourceLayout.undoManager setActionName:@"Delete Background Filter"];
        
    }
    
    [CATransaction begin];
    self.layer.backgroundFilters = [self newFilterArray:self.layer.backgroundFilters withoutName:filteruuid];
    [CATransaction commit];
}



-(void)addLayerFilter:(NSString *)filterName
{

    CIFilter *newFilter = [CIFilter filterWithName:filterName];
    if (newFilter)
    {
        [newFilter setDefaults];
        CFUUIDRef tmpUUID = CFUUIDCreate(NULL);
        NSString *filterID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, tmpUUID);
        CFRelease(tmpUUID);
        newFilter.name = filterID;
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            [input deleteLayerFilter:filterID];
        }];

        NSMutableArray *currentFilters = self.layer.filters.mutableCopy;
        if (!currentFilters)
        {
            currentFilters = [NSMutableArray array];
        }

        [currentFilters addObject:newFilter];
        [CATransaction begin];
        self.layer.filters = currentFilters;
        [CATransaction commit];
    }
}

-(void)addSourceFilter:(NSString *)filterName
{
    
    CIFilter *newFilter = [CIFilter filterWithName:filterName];
    if (newFilter)
    {
        [newFilter setDefaults];
        CFUUIDRef tmpUUID = CFUUIDCreate(NULL);
        NSString *filterID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, tmpUUID);
        CFRelease(tmpUUID);
        newFilter.name = filterID;
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            [input deleteSourceFilter:filterID];
        }];

        NSMutableArray *currentFilters = self.layer.sourceLayer.filters.mutableCopy;
        if (!currentFilters)
        {
            currentFilters = [NSMutableArray array];
        }

        [currentFilters addObject:newFilter];
        [CATransaction begin];
        self.layer.sourceLayer.filters = currentFilters;
        [CATransaction commit];
    }
}

-(void)addBackgroundFilter:(NSString *)filterName
{
    
    CIFilter *newFilter = [CIFilter filterWithName:filterName];
    if (newFilter)
    {
        [newFilter setDefaults];
        CFUUIDRef tmpUUID = CFUUIDCreate(NULL);
        NSString *filterID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, tmpUUID);
        CFRelease(tmpUUID);
        newFilter.name = filterID;
        
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            [input deleteBackgroundFilter:filterID];
        }];
        [self.sourceLayout.undoManager setActionName:@"Add Background Filter"];

        NSMutableArray *currentFilters = self.layer.backgroundFilters.mutableCopy;
        if (!currentFilters)
        {
            currentFilters = [NSMutableArray array];
        }
        [currentFilters addObject:newFilter];
        [CATransaction begin];
        self.layer.backgroundFilters = currentFilters;
        [CATransaction commit];
    }
}


-(void)rebuildUserFilter
{
    _filterGenerator = [[CIFilterGenerator alloc] init];
    
    CIFilter *lastFilter = nil;
    CIFilter *firstFilter = nil;
    
    if (self.currentEffects.count == 0)
    {
        self.userFilter = nil;
        return;
    }
    
    
    if (self.currentEffects.count == 1)
    {
        self.userFilter = self.currentEffects.firstObject;
    } else {
        for (CIFilter *cur in self.currentEffects)
        {
            if (!lastFilter)
            {
                lastFilter = cur;
                firstFilter = cur;
                continue;
            }
            
            [_filterGenerator connectObject:lastFilter withKey:kCIOutputImageKey toObject:cur withKey:kCIInputImageKey];
            for (NSString *inputName in lastFilter.inputKeys)
            {
                if ([inputName isEqualToString:kCIInputImageKey])
                {
                    continue;
                }
                NSString *keyPrefix = lastFilter.className;
                
                [_filterGenerator exportKey:inputName fromObject:lastFilter withName:[NSString stringWithFormat:@"params.%@:%@", keyPrefix, inputName]];
            }
            
            
            lastFilter = cur;
        }
        
        
        [_filterGenerator exportKey:kCIInputImageKey fromObject:firstFilter withName:kCIInputImageKey];
        [_filterGenerator exportKey:kCIOutputImageKey fromObject:lastFilter withName:nil];
        
        for (NSString *inputName in lastFilter.inputKeys)
        {
            if ([inputName isEqualToString:kCIInputImageKey])
            {
                continue;
            }

            NSString *keyPrefix = lastFilter.className;

            [_filterGenerator exportKey:inputName fromObject:lastFilter withName:[NSString stringWithFormat:@"params.%@:%@", keyPrefix, inputName]];
        }

        
        
        CIFilter *newFilter = [_filterGenerator filter];
        if (self.userFilter)
        {
            [self.userFilter removeObserver:self forKeyPath:@"params"];
        }
        self.userFilter = newFilter;
        
        [self.userFilter addObserver:self forKeyPath:@"params" options:NSKeyValueObservingOptionNew context:NULL];
    }
}




-(void)removeUserEffects:(NSIndexSet *)filterIndexes
{
    
    [self.currentEffects removeObjectsAtIndexes:filterIndexes];
    [self rebuildUserFilter];
    
}



-(void)addUserEffect:(NSIndexSet *)filterIndexes
{
    
    
    
    [filterIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
    
        NSString *filterName = [self.availableEffectNames objectAtIndex:idx];
        
        CIFilter *newFilter = [CIFilter filterWithName:filterName];
        
        if (newFilter)
        {
            [newFilter setDefaults];
            [self.currentEffects addObject:newFilter];
        }
    }];
    
    [self rebuildUserFilter];

}



-(NSArray *)sourceTypes
{
    
    NSMutableDictionary *pluginMap = [[CSPluginLoader sharedPluginLoader] sourcePlugins];
    
    return [pluginMap.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}



-(void)dealloc
{
    
    [self deregisterVideoInput:self.videoInput];
    for(id vInput in self.videoSources)
    {
        [self deregisterVideoInput:vInput];
    }
    
    [self stopObservingConstraintKeys];
    
    
}

-(void)setSettingsTab:(NSString *)settingsTab
{
    return;
}


-(NSString *)settingsTab
{
    if (self.videoInput)
    {
        return @"Settings";
    }
    
    return @"Source";
}


/*
-(NSString *)description
{
    return [NSString stringWithFormat:@"Name: %@ Depth %f", self.name, self.depth];
}
 */


-(void)setName:(NSString *)name
{
    
    
    _name = name;


    _editedName = name;
    
    if (!_name)
    {
        if (self.videoInput)
        {
            _name = self.videoInput.captureName;
            _editedName = nil;
        }
    }
    
    if (!_name)
    {
        _name = @"No Name";
        _editedName = nil;
    }

    [CATransaction begin];
    self.layer.name = name;
    [CATransaction commit];
}


-(NSString *)name
{
    return _name;
}


//This is a dummy layer the animation scripts use to keep track of geometry changes without messing up the presentation/model layers
-(CALayer *)animationLayer
{
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    CALayer *nLayer = [CALayer layer];
    
    
    nLayer.position = self.layer.position;
    nLayer.bounds = self.layer.bounds;
    nLayer.transform = self.layer.transform;
    nLayer.hidden = self.layer.hidden;
    [nLayer setValue:@(self.layer.bounds.size.width) forKey:@"fakeWidth"];
    
    [CATransaction commit];

    return nLayer;
}


-(void)updateRotationTransform
{
    CATransform3D transform = CATransform3DMakeRotation(self.rotationAngle * M_PI / 180.0, 0.0, 0.0, 1.0);
    transform = CATransform3DRotate(transform, self.rotationAngleX * M_PI / 180.0, 1.0, 0.0, 0.0);
    transform = CATransform3DRotate(transform, self.rotationAngleY * M_PI / 180.0, 0.0, 1.0, 0.0);
    
    [CATransaction begin];
    self.layer.disableAnimation = YES;

    
    self.layer.transform = transform;

    
    self.layer.disableAnimation  = NO;
    [CATransaction commit];
}


-(void)setRotationAngle:(float)rotationAngle
{

    
    _rotationAngle = rotationAngle;
    
    [self updateRotationTransform];
}


-(float)rotationAngle
{
    return _rotationAngle;
}

-(void)setRotationAngleX:(float)rotationAngleX
{

    _rotationAngleX = rotationAngleX;
    
    [self updateRotationTransform];
}


-(float)rotationAngleX
{
    return _rotationAngleX;
}

-(void)setRotationAngleY:(float)rotationAngleY
{

    _rotationAngleY = rotationAngleY;
    
    [self updateRotationTransform];
}


-(float)rotationAngleY
{
    return _rotationAngleY;
}



-(void)setCropRectWithoutResize
{
    CGRect contentsRect = self.layer.sourceLayer.contentsRect;
    contentsRect.origin.x = self.crop_left;
    contentsRect.origin.y = self.crop_bottom;
    contentsRect.size.width = 1.0 - self.crop_right - self.crop_left;
    contentsRect.size.height = 1.0 - self.crop_top - self.crop_bottom;
    [CATransaction begin];
    self.layer.cropRect = contentsRect;
    [CATransaction commit];
}


-(void)setCropRect
{
    CGRect contentsRect = self.layer.sourceLayer.contentsRect;
    CGRect oldRect = contentsRect;
    
    contentsRect.origin.x = self.crop_left;
    contentsRect.origin.y = self.crop_bottom;
    contentsRect.size.width = 1.0 - self.crop_right - self.crop_left;
    contentsRect.size.height = 1.0 - self.crop_top - self.crop_bottom;

    
    CGFloat delta_w = (contentsRect.size.width - oldRect.size.width);
    CGFloat delta_h = (contentsRect.size.height - oldRect.size.height);
    CGRect currentRect = self.layer.frame;

    CGFloat new_width = currentRect.size.width;
    CGFloat new_height = currentRect.size.height;

    if (!NSEqualSizes(NSZeroSize,self.videoInput.captureSize))
    {
        
        
        CGFloat layer_full_width = currentRect.size.width/oldRect.size.width;
        CGFloat layer_full_height = currentRect.size.height/oldRect.size.height;
        
        new_width = layer_full_width * contentsRect.size.width;
        new_height = layer_full_height * contentsRect.size.height;
        

    
    }
    [CATransaction begin];
    self.layer.cropRect = contentsRect;
    if (delta_w || delta_h)
    {
        resize_style saveResize = self.resizeType;
        self.resizeType &= ~kResizeCrop;
        self.resizeType |= kResizeFree;

        [self updateSize:new_width height:new_height];
        
        self.resizeType = saveResize;
        
    }

    [CATransaction commit];
    
}




-(void)setTransitionDuration:(float)transitionDuration
{
    _transitionDuration = transitionDuration;
}


-(float)transitionDuration
{
    return _transitionDuration;
}
-(void)setTransitionDirection:(NSString *)transitionDirection
{
    _transitionDirection = transitionDirection;
}

-(NSString *)transitionDirection
{
    return _transitionDirection;
}

-(void)setTransitionEnabled:(bool)transitionEnabled
{
    [CATransaction begin];
    _transitionEnabled = transitionEnabled;
    
    if (transitionEnabled)
    {
        for (InputSource *inp in self.attachedInputs)
        {
            if (!inp.alwaysDisplay)
            {
                inp.layer.hidden = YES;
            }
        }
    } else {
        for (InputSource *inp in self.attachedInputs)
        {
            inp.layer.hidden = NO;
        }
        [self.layer transitionsDisabled];
    }
    [CATransaction commit];
}

-(bool)alwaysDisplay
{
    return _alwaysDisplay;
}

-(void)setAlwaysDisplay:(bool)alwaysDisplay
{
    
    
    if (alwaysDisplay)
    {
        [CATransaction begin];
        self.layer.hidden = NO;
        [CATransaction commit];
    }
    
    _alwaysDisplay = alwaysDisplay;
}


-(bool)transitionEnabled
{
    return _transitionEnabled;
}


-(void) setAdvancedTransitionName:(NSString *)advancedTransitionName
{
    CIFilter *newTransition = [CIFilter filterWithName:advancedTransitionName];
    [newTransition setDefaults];
    self.advancedTransition = newTransition;
    
}


-(NSString *)advancedTransitionName
{
    if (self.advancedTransition)
    {
        return self.advancedTransition.className;
    }
    
    return nil;
}


-(void) setTransitionFilterName:(NSString *)transitionFilterName
{
    _transitionFilterName = transitionFilterName;
    if ([transitionFilterName hasPrefix:@"CI"])
    {
        CIFilter *newFilter = [CIFilter filterWithName:transitionFilterName];
        [newFilter setDefaults];
        self.advancedTransition = newFilter;
    } else {
        self.advancedTransition = nil;
    }
}




-(NSString *)transitionFilterName
{
    return _transitionFilterName;
}




-(void)multiChange
{
    
    if (!self.transitionEnabled)
    {
        return;
    }
    CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();

    if ((currentTime >= _nextImageTime) && (self.changeInterval > 0))
    {
        [self multiChangeForce];
    }
    
}

    
-(void)multiChangeForce
{
    CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();

    NSPredicate *inputFilter = [NSPredicate predicateWithFormat:@"alwaysDisplay == NO"];
    NSMutableArray *chooseInputs = [NSMutableArray arrayWithArray:self.attachedInputs];
    [chooseInputs filterUsingPredicate:inputFilter];
    if (!self.alwaysDisplay)
    {
        [chooseInputs addObject:self];
    }
    
    
    
    InputSource *_nextInput;
    
    
    
    if (chooseInputs.count > 1)
    {
        switch (self.rotateStyle)
        {
            case kRotateNormal:
                _currentSourceIdx++;
                if (_currentSourceIdx == chooseInputs.count)
                {
                    _currentSourceIdx = 0;
                }
                break;
            case kRotateRandom:
                _currentSourceIdx = (unsigned int)arc4random_uniform((unsigned int)chooseInputs.count);
            case kRotateReverse:
                _currentSourceIdx--;
                if (_currentSourceIdx < 0)
                {
                    _currentSourceIdx = (int)chooseInputs.count-1;
                }
                break;
            default:
                break;
        }
        _nextImageTime = currentTime + self.changeInterval;
        
        @try {
            _nextInput = [chooseInputs objectAtIndex:_currentSourceIdx];
        }
        @catch (NSException *exception) {
            _nextInput = nil;
        }
        
        if (_nextInput)
        {
            _multiTransition.type = self.transitionFilterName;
            _multiTransition.duration = self.transitionDuration;
            _multiTransition.subtype = self.transitionDirection;
            _multiTransition.filter = self.advancedTransition.copy;
        
            CALayer *fromLayer = nil;
            if (!_currentInput.alwaysDisplay)
            {
                fromLayer = _currentInput.layer;
            }
        
            [self.layer transitionToLayer:_nextInput.layer fromLayer:fromLayer withTransition:_multiTransition];
            _currentInput = _nextInput;
        }
    }
}


-(void)setWidth:(float)width
{
    _width = width;
    [self directSize:_width height:_height];
}

-(float)width
{
    return _width;
}

-(void)setHeight:(float)height
{
    _height = height;
    [self directSize:_width height:_height];
}

-(float)height
{
    return _height;
}


-(void)setX_pos:(float)x_pos
{
    //[self registerUndoForProperty:@"x_pos" withAction:@"Position X"];
    if (x_pos > 0 && x_pos <= 1.0)
    {
        CALayer *sLayer = self.layer.superlayer;
        x_pos = sLayer.bounds.origin.x + (sLayer.bounds.size.width * x_pos);
        x_pos = roundf(x_pos);
    }
    _x_pos = x_pos;
    
    [self positionOrigin:_x_pos y:_y_pos];
}

-(float)x_pos
{
    return _x_pos;
}

-(void)setY_pos:(float)y_pos
{
    //[self registerUndoForProperty:@"y_pos" withAction:@"Position Y"];
    if (y_pos > 0 && y_pos <= 1.0)
    {
        CALayer *sLayer = self.layer.superlayer;
        y_pos = sLayer.bounds.origin.y + (sLayer.bounds.size.height * y_pos);
        y_pos = roundf(y_pos);
    }

    _y_pos = y_pos;
    [self positionOrigin:_x_pos y:_y_pos];
}



-(float)y_pos
{
    return _y_pos;
}



-(void) adjustInputSize: (bool)doPosition
{

    if (self.topLevelHeight > 0 && self.topLevelWidth > 0)
    {
        float wRatio = self.canvas_width/self.topLevelWidth;
        float hRatio = self.canvas_height/self.topLevelHeight;
        float old_x = self.x_pos;
        float old_y = self.y_pos;
        float new_width = self.layer.bounds.size.width * wRatio;
        float new_height = self.layer.bounds.size.height * hRatio;
        [self directSize:new_width height:new_height];
        if (doPosition)
        {
            self.x_pos = old_x*wRatio;
            self.y_pos = old_y*hRatio;
        }
    }
    
    self.topLevelWidth = self.canvas_width;
    self.topLevelHeight = self.canvas_height;
    
}

-(void)frameTick
{
    
    
    self.layoutPosition = self.layer.frame;
    _x_pos = self.layer.frame.origin.x;
    _y_pos = self.layer.frame.origin.y;
    _width = self.layer.frame.size.width;
    _height = self.layer.frame.size.height;
    
    [self multiChange];

    
    
    if (!self.videoInput)
    {
        return;
    }

    
    
    if (self.layer.sourceLayer != _currentLayer)
    {
        if (!_userBackground)
        {
            self.backgroundColor = nil;
            _userBackground = NO;
        }
        
        self.layer.sourceLayer = _currentLayer;
    }
    
    
    [self.videoInput frameTickFromInput:self];
    
    [self.layer frameTick];
    if (self.needsAdjustment)
    {
        [self adjustInputSize:self.needsAdjustPosition];
        self.needsAdjustment = NO;
    }
}



-(void)autoSize
{
    if (!self.videoInput)
    {
        return;
    }
    
    NSSize videoSize = [self.videoInput captureSize];
    
    if (!NSEqualSizes(videoSize, NSZeroSize))
    {
        [self directSize:videoSize.width height:videoSize.height];
    }
}

-(void)autoCenter:(NSRect)containerRect
{
    NSRect myRect = self.layer.bounds;
    
    
    if (NSContainsRect(containerRect, myRect))
    {
        [self autoFit];
    } else {
        //We're bigger than the container! Set ourselves to the exact same size and origin
        [self directSize:containerRect.size.width height:containerRect.size.height];
        [self positionOrigin:0 y:0];
    }
}


-(void)autoFit
{

    
    float wr = self.size.width / self.canvas_width;
    float hr = self.size.height / self.canvas_height;
    float ratio = (hr < wr ? wr : hr);
    [self directSize:self.size.width / ratio height:self.size.height / ratio];
    CGFloat newX = (self.canvas_width/2) - self.layer.frame.size.width/2;
    CGFloat newY = (self.canvas_height/2) - self.layer.frame.size.height/2;
    [self positionOrigin:newX y:newY];
}




-(float) calculateScale:(CGSize)forSize sourceSize:(CGSize)sourceSize
{
    
    
    CGFloat source_width, source_height;
    
    source_width = fabs(sourceSize.width);
    source_height = fabs(sourceSize.height);
    
    float wr = forSize.width / source_width;
    float hr = forSize.height / source_height;
    
    float ratio;
    ratio = (hr < wr ? hr : wr);
    return ratio;
}





-(void) directSize:(CGFloat)width height:(CGFloat)height
{
    NSRect newLayout = self.layoutPosition;
    
    newLayout.size.width = width;
    newLayout.size.height = height;
    
    NSRect iRect = NSIntegralRect(newLayout);
    
    [CATransaction begin];
    self.layer.frame = newLayout;
    [CATransaction commit];
    
    
}


-(void)resetAspectRatio
{
    

    if (!self.videoInput || NSEqualSizes(NSZeroSize, self.videoInput.captureSize))
    {
        return;
    }
    
    CGFloat height = self.height;
    CGFloat width = self.width;
    
    CGFloat inputAR = self.videoInput.captureSize.width / self.videoInput.captureSize.height;
    if (height > width)
    {
        width = inputAR * height;
    } else {
        height = width/inputAR;
    }

    resize_style resizeSave = self.resizeType;
    self.resizeType = kResizeTop | kResizeRight | kResizeFree;
    [self updateSize:width height:height];
    self.resizeType = resizeSave;
}


-(void) updateSize:(CGFloat)width height:(CGFloat)height
{
    
    [CATransaction begin];
    NSRect oldLayout = self.layer.frame;
    NSRect newLayout = self.layer.frame;
    
    
    CGFloat delta_w, delta_h;
    delta_w = width - oldLayout.size.width;
    delta_h = height - oldLayout.size.height;
    //Preserve aspect ratio on resize. Take the largest dimension and figure out hte other one
    
    if (self.videoInput && !NSEqualSizes(self.videoInput.captureSize, NSZeroSize) && !(self.resizeType & kResizeFree) && !(self.resizeType & kResizeCrop))
    {
        CGFloat inputAR = oldLayout.size.width / oldLayout.size.height;
        if (height > width)
        {
            width = inputAR * height;
            delta_w = width - oldLayout.size.width;
        } else {
            height = width/inputAR;
            delta_h = height - oldLayout.size.height;
        }
    }
    
    if (self.layer)
    {
        
        
        newLayout.size.width = width;
        newLayout.size.height = height;
        
        
        if (self.resizeType & kResizeCrop)
        {
            //calculate the crop left/right/top/bottom values and let setCropRect handle the resize
            
            CGFloat full_width = oldLayout.size.width/self.layer.sourceLayer.contentsRect.size.width;
            CGFloat delta_wp = delta_w/full_width;
            CGFloat full_height = oldLayout.size.height/self.layer.sourceLayer.contentsRect.size.height;
            CGFloat delta_hp = delta_h/full_height;
            
            
            if (self.resizeType & kResizeLeft)
            {
                
                self.crop_left -= delta_wp;
            }
            
            if (self.resizeType & kResizeRight)
            {
                self.crop_right -= delta_wp;
            }

            if (self.resizeType & kResizeTop)
            {
                self.crop_top -= delta_hp;
            }
            
            if (self.resizeType & kResizeBottom)
            {
                self.crop_bottom -= delta_hp;
            }
        } else {
            if (self.resizeType & kResizeCenter)
            {
                newLayout.origin.x -= delta_w/2;
                newLayout.origin.y -= delta_h/2;
            } else {
                if (self.resizeType & kResizeLeft)
                {
                    //Where does the origin need to be to keep the right side in the same place?
                    newLayout.origin.x -= delta_w;
                }
                
                if (self.resizeType & kResizeBottom)
                {
                    newLayout.origin.y -= delta_h;
                }
            }

            self.layer.frame = newLayout;

        }
    }
    [CATransaction commit];
}



-(void)detachAllInputs
{
    NSArray *aCopy = self.attachedInputs.copy;
    
    for (InputSource *inp in aCopy)
    {
        [self detachInput:inp];
    }
}


-(void)detachInput:(InputSource *)toDetach
{
    if (!toDetach.parentInput)
    {
        return;
    }
    
    if (toDetach.parentInput != self)
    {
        return;
    }

    [CATransaction begin];
    [toDetach resetConstraints];
    
    toDetach.parentInput = nil;
    toDetach.alwaysDisplay = NO;
    
    [self.sourceLayout.rootLayer addSublayer:toDetach.layer];
    toDetach.layer.hidden = NO;
    
    //translate the position to the new sublayers coordinates
    
    
    NSPoint newPosition = [self.sourceLayout.rootLayer convertPoint:toDetach.layer.position fromLayer:self.layer];
    toDetach.layer.position = newPosition;
    [CATransaction commit];
    
    [[self mutableArrayValueForKey:@"attachedInputs"] removeObject:toDetach];
    [[NSNotificationCenter defaultCenter] postNotificationName:CSNotificationInputDetached object:toDetach userInfo:nil];

}



-(void)attachInput:(InputSource *)toAttach
{
    
    
    if (toAttach.parentInput)
    {
        if (toAttach.parentInput == self)
        {
            return;
        }
        
        [toAttach.parentInput detachInput:toAttach];
    }
    
    [toAttach makeSublayerOfLayer:self.layer];
    [[self mutableArrayValueForKey:@"attachedInputs"] addObject:toAttach];
    toAttach.parentInput = self;
    [[NSNotificationCenter defaultCenter] postNotificationName:CSNotificationInputAttached object:toAttach userInfo:nil];

}




-(NSArray *)getLayersToCanvas:(CALayer *)startLayer
{
    NSMutableArray *ret = [NSMutableArray array];
    
    CALayer *curr = startLayer;
    while (curr)
    {
        [ret addObject:curr];
        curr = curr.superlayer;
        if (curr == self.sourceLayout.rootLayer)
        {
            break;
        }
    }
    
    return ret;
}


-(void)makeSublayerOfLayer:(CALayer *)parentLayer
{
    
    [CATransaction begin];
    
    [parentLayer addSublayer:self.layer];
    //translate the position to the new sublayers coordinates
    
    NSArray *layers = [self getLayersToCanvas:parentLayer];
    NSPoint newPosition = self.layer.frame.origin;
    
    for (CALayer *curr in [layers reverseObjectEnumerator])
    {
      //We start at the layer just before the canvas layer and the point we are converting is in canvas coordinate space
        newPosition = [curr convertPoint:newPosition fromLayer:curr.superlayer];
    }
    
    NSRect oldFrame = self.layer.frame;
    oldFrame.origin = newPosition;
    self.layer.frame = oldFrame;
    [CATransaction commit];

}


-(void) positionOrigin:(CGFloat)x y:(CGFloat)y
{
    
    if (self.layer)
    {
        
        /*
        NSRect newFrame = self.layer.frame;
        newFrame.origin.x = x;
        newFrame.origin.y = y;
        [CATransaction begin];
        self.layer.frame = newFrame;
        [CATransaction commit];
         */
        [self updateOrigin:x-self.layer.frame.origin.x y:y-self.layer.frame.origin.y];
        
    }

}
-(void) updateOrigin:(CGFloat)x y:(CGFloat)y
{
    
    if (isnan(x))
    {
        return;
    }
    
    
    if (self.layer)
    {
        
        NSPoint newOrigin = self.layer.position;
        newOrigin.x += x;
        newOrigin.y += y;
        
        
        [CATransaction begin];
            //[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
        NSRect tmpRect = NSIntegralRect(NSMakeRect(newOrigin.x, newOrigin.y, 100, 100));
        
        self.layer.disableAnimation = YES;
        self.layer.position = tmpRect.origin;
        
        self.layer.disableAnimation = NO;
            
        [CATransaction commit];

    }
    
}


-(NSSize)size
{
    return self.layoutPosition.size;
}

-(NSPoint)origin
{
    return self.layoutPosition.origin;
}


-(float)opacity
{
    return _opacity;
}


-(void)setOpacity:(float)opacity
{

    _opacity = opacity;
    [CATransaction begin];
    self.layer.opacity = _opacity;
    [CATransaction commit];
}




-(NSString *) selectedVideoType
{
    return _selectedVideoType;
}

-(void) setSelectedVideoType:(NSString *)selectedVideoType
{
    NSMutableDictionary *pluginMap = [[CSPluginLoader sharedPluginLoader] sourcePlugins];
    
    _currentInputViewController = nil;
    if (self.videoInput)
    {
        [self deregisterVideoInput:self.videoInput];
    }
    
    NSObject <CSCaptureSourceProtocol> *newCaptureSession;
    
    Class captureClass = [pluginMap objectForKey:selectedVideoType];
    newCaptureSession = [[captureClass alloc] init];
    
    [self registerVideoInput:newCaptureSession];
    CALayer *newLayer = [newCaptureSession layerForInput:self];
    
    _currentLayer = newLayer;
    self.videoInput = newCaptureSession;

 
    _selectedVideoType = selectedVideoType;
    self.name = _editedName;
    
 }

-(NSData *)saveData
{
    NSMutableData *saveData = [NSMutableData data];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:saveData];
    
    [archiver encodeObject:self forKey:@"root"];
    [archiver finishEncoding];
    return saveData;
}

-(bool)isDifferentInput:(InputSource *)from
{
    if (![from.uuid isEqualToString:self.uuid])
    {
        return YES;
    }
    
    NSData *myData = [self saveData];
    NSData *fromData = [from saveData];
    
    
    if ([myData isEqualToData:fromData])
    {
        return NO;
    }
    
    return YES;
}


-(NSViewController *)sourceConfigurationView
{
    
    NSViewController *vcont = nil;
    if (self.videoInput && [self.videoInput respondsToSelector:@selector(configurationView)])
    {
        vcont = [self.videoInput configurationView];
    }
    
    if (vcont)
    {
        [vcont setValue:self.videoInput forKey:@"captureObj"];
    }
    
    return vcont;
}

-(void) editorPopoverDidClose
{
    return;
}




-(void)setScrollXSpeed:(float)scrollXSpeed
{

    [CATransaction begin];
    self.layer.scrollXSpeed = scrollXSpeed;
    [CATransaction commit];
    
}

-(float)scrollXSpeed
{
    return self.layer.scrollXSpeed;
}


-(void)setScrollYSpeed:(float)scrollYSpeed
{

    [CATransaction begin];
    self.layer.scrollYSpeed = scrollYSpeed;
    [CATransaction commit];
}


-(float)scrollYSpeed
{
    return self.layer.scrollYSpeed;
}


-(size_t)canvas_width
{
    return self.sourceLayout.canvas_width;
}

-(size_t)canvas_height
{
    return self.sourceLayout.canvas_height;
}

-(NSString *)MIDIShortIdentifier
{
    return nil;
}


-(NSString *)MIDIIdentifier
{
    return [NSString stringWithFormat:@"Input:%@", self.uuid];
}

-(NSArray *)commandIdentifiers
{
    return @[@"Opacity", @"Rotate", @"RotateX", @"RotateY", @"Active", @"AutoFit",
             @"HScroll", @"VScroll", @"CropLeft", @"CropRight", @"CropTop", @"CropBottom",
             @"CKEnable", @"CKThresh", @"CKSmooth", @"BorderWidth", @"CornerRadius",
             @"GradientStartX", @"GradientStartY", @"GradientStopX", @"GradientStopY",
             @"ChangeInterval", @"EffectDuration", @"MultiTransition",
             @"PositionX", @"PositionY", @"Freeze"];
}

-(MIKMIDIResponderType)MIDIResponderTypeForCommandIdentifier:(NSString *)commandID
{
    MIKMIDIResponderType ret = MIKMIDIResponderTypeAbsoluteSliderOrKnob;

    if ([@[@"Opacity",@"Rotate",@"RotateX",@"RotateY"] containsObject:commandID])
    {
        ret |= MIKMIDIResponderTypeButton;
    }
    
    if ([@[@"Active", @"AutoFit", @"CKEnable", @"MultiTransition", @"Freeze"] containsObject:commandID])
    {
        ret = MIKMIDIResponderTypeButton;
    }
    
    
    return ret;
}


-(BOOL)respondsToMIDICommand:(MIKMIDICommand *)command
{
    return YES;
}

-(float)convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:(float)minvalue maxValue:(float)maxvalue
{
    NSUInteger midiValue = command.value;
    
    float midifract = midiValue/127.0;
    
    float valRange = maxvalue - minvalue;
    
    float valFract = valRange * midifract;
    
    return minvalue + valFract;
}

-(void)handleMIDICommandChangeInterval:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:60];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.changeInterval = newVal;
    });
    
}

-(void)handleMIDICommandEffectDuration:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:10];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.transitionDuration = newVal;
    });
    
}

-(void)handleMIDICommandPositionX:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.x_pos = newVal;
    });
    
}

-(void)handleMIDICommandPositionY:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.y_pos = newVal;
    });
    
}




-(void)handleMIDICommandCKThresh:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:0.5];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.chromaKeyThreshold = newVal;
    });
    
}


-(void)handleMIDICommandGradientStartX:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.layer.gradientStartX = newVal;
    });

}

-(void)handleMIDICommandGradientStartY:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.layer.gradientStartY = newVal;
    });
    
}

-(void)handleMIDICommandGradientStopX:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.layer.gradientStopX = newVal;
    });
    
}

-(void)handleMIDICommandGradientStopY:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.layer.gradientStopY = newVal;
    });
    
}



-(void)handleMIDICommandCKSmooth:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:0.5];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.chromaKeySmoothing = newVal;
    });
    
}


-(void)handleMIDICommandMultiTransition:(MIKMIDICommand *)command
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self multiChangeForce];
    });
}


-(void)handleMIDICommandCKEnable:(MIKMIDICommand *)command
{
    dispatch_async(dispatch_get_main_queue(), ^{
        self.doChromaKey = !self.doChromaKey;
    });
}



-(void)handleMIDICommandBorderWidth:(MIKMIDICommand *)command
{
    float newVal = ((MIKMIDIChannelVoiceCommand *)command).value;
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.borderWidth = newVal;
    });
    
}

-(void)handleMIDICommandCornerRadius:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:360];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.cornerRadius = newVal;
    });
    
}


-(void)handleMIDICommandCropLeft:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.crop_left = newVal;
    });
}

-(void)handleMIDICommandCropRight:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.crop_right = newVal;
    });
    
}

-(void)handleMIDICommandCropTop:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.crop_top = newVal;
    });
    
}

-(void)handleMIDICommandCropBottom:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0.0 maxValue:1.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.crop_bottom = newVal;
    });
    
}



-(void)handleMIDICommandFreeze:(MIKMIDICommand *)command
{
    dispatch_async(dispatch_get_main_queue(), ^{
        self.isFrozen = !self.isFrozen;
    });
}


-(void)handleMIDICommandAutoFit:(MIKMIDICommand *)command
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self autoFit];
    });
}


-(void)handleMIDICommandHScroll:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:-10.0 maxValue:10.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.scrollXSpeed = newVal;
    });
}

-(void)handleMIDICommandVScroll:(MIKMIDICommand *)command
{
    float newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:-10.0 maxValue:10.0];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.scrollYSpeed = newVal;
    });
}


-(void)handleMIDICommandOpacity:(MIKMIDICommand *)command
{
    float newVal;
    if (command.commandType == MIKMIDICommandTypeNoteOn)
    {
        if (self.opacity != 0)
        {
            newVal = 0;
        } else {
            newVal = 1;
        }
    } else {
        newVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0 maxValue:1.0];
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.opacity = newVal;
    });
}

-(void)handleMIDICommandActive:(MIKMIDICommand *)command
{
    dispatch_async(dispatch_get_main_queue(), ^{
        self.active = !self.active;
    });

}

-(float)handleGenericRotateForMidi:(float)currentValue forCommand:(MIKMIDICommand *)command
{
    float retVal;
    if (command.commandType == MIKMIDICommandTypeNoteOn)
    {
        retVal = currentValue + 90;
        if (retVal >= 360)
        {
            retVal = 0;
        }
    } else {
       retVal = [self convertMidiValueForRange:(MIKMIDIChannelVoiceCommand *)command minValue:0 maxValue:360];
    }
    
    return retVal;
}


-(void)handleMIDICommandRotate:(MIKMIDICommand *)command
{
    float newVal = [self handleGenericRotateForMidi:self.rotationAngle forCommand:command];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.rotationAngle = newVal;
    });
}

-(void)handleMIDICommandRotateX:(MIKMIDICommand *)command
{
    float newVal = [self handleGenericRotateForMidi:self.rotationAngleX forCommand:command];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.rotationAngleX = newVal;
    });
}

-(void)handleMIDICommandRotateY:(MIKMIDICommand *)command
{
    float newVal = [self handleGenericRotateForMidi:self.rotationAngleY forCommand:command];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.rotationAngleY = newVal;
    });
}



-(void)layerUpdated
{
    if (self.autoPlaceOnFrameUpdate)
    {
        [self autoSize];
        [self autoCenter:NSMakeRect(0, 0, self.canvas_width, self.canvas_height)];
        self.autoPlaceOnFrameUpdate = NO;
    }
}


-(void) setActive:(bool)active
{
    
    _active = active;
    if (self.videoInput)
    {
        self.videoInput.isActive = active;
    }
    [CATransaction begin];
    self.layer.hidden = !active;
    [CATransaction commit];
    
}


-(bool)active
{
    
    return _active;
}


-(void) setIs_live:(bool)is_live
{
    _is_live = is_live;
    if (self.videoInput)
    {
        self.videoInput.isLive = is_live;
    }
}

-(bool)is_live
{
    return _is_live;
}


-(void) setIs_selected:(bool)is_selected
{
    _is_selected = is_selected;
    if (self.videoInput)
    {
        self.videoInput.isSelected = is_selected;
    }
    
    if (is_selected)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:CSNotificationInputSelected object:self userInfo:nil];

    }
    
}


-(bool)is_selected
{
    return _is_selected;
}

-(CGFloat) crop_left
{
    return _crop_left;
}


-(void) setCrop_left:(CGFloat)crop_left
{
    if (crop_left < 0)
    {
        _crop_left = 0;
    } else {
        _crop_left = crop_left;
    }
    
    [self setCropRect];
    
    
}

-(CGFloat) crop_right
{
    return _crop_right;
}


-(void) setCrop_right:(CGFloat)crop_right
{
    if (crop_right < 0)
    {
        _crop_right = 0;
    } else {
        _crop_right = crop_right;
    }
    [self setCropRect];

}

-(CGFloat) crop_top
{
    return _crop_top;
}


-(void) setCrop_top:(CGFloat)crop_top
{
    if (crop_top < 0)
    {
        _crop_top = 0;
    } else {
        _crop_top = crop_top;
    }
    [self setCropRect];

}

-(CGFloat) crop_bottom
{
    return _crop_bottom;
}


-(void)setValue:(id)value forKeyPath:(NSString *)keyPath
{
    
    NSString *actionName = _undoActionMap[keyPath];
    if (!actionName)
    {
        if ([keyPath isEqualToString:@"isMaskLayer"])
        {
            actionName = value ? @"Set As Mask" : @"Unset As Mask";
        } else if ([keyPath isEqualToString: @"active"]) {
            actionName = value ? @"Set Active" : @"Unset Active";
        } else if ([keyPath isEqualToString:@"doChromaKey"]) {
            actionName = value ? @"Set Chroma Key" : @"Unset Chroma Key";
        } else {
            actionName = [NSString stringWithFormat:@"Change %@", keyPath];
        }
    }
    
    [self registerUndoForProperty:keyPath withAction:actionName];
    
    [super setValue:value forKeyPath:keyPath];
}
-(void) setCrop_bottom:(CGFloat)crop_bottom
{
    if (crop_bottom < 0)
    {
        _crop_bottom = 0;
    } else {
        _crop_bottom = crop_bottom;
    }
    [self setCropRect];

}


-(void)registerUndoForProperty:(NSString *)propName withAction:(NSString *)action
{
    id propertyValue = [self valueForKeyPath:propName];
    
    [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
        [input setValue:propertyValue forKeyPath:propName];
        
    }];
    [self.sourceLayout.undoManager setActionName:action];
}



-(void)setChromaKeyColor:(NSColor *)chromaKeyColor
{


    _chromaKeyColor = chromaKeyColor;
    [CATransaction begin];
    [self.layer.sourceLayer setValue:[[CIColor alloc] initWithColor:chromaKeyColor] forKeyPath:@"filters.Chromakey.inputColor"];
    [CATransaction commit];
}

-(NSColor *)chromaKeyColor
{
    return _chromaKeyColor;
}

-(void)setChromaKeySmoothing:(float)chromaKeySmoothing
{

    _chromaKeySmoothing = chromaKeySmoothing;
    [CATransaction begin];
    [self.layer.sourceLayer setValue:@(chromaKeySmoothing) forKeyPath:@"filters.Chromakey.inputSmoothing"];
    [CATransaction commit];
}

-(float)chromaKeySmoothing
{
    return _chromaKeySmoothing;
}

-(void)setChromaKeyThreshold:(float)chromaKeyThreshold
{
    _chromaKeyThreshold = chromaKeyThreshold;
    [CATransaction begin];
    [self.layer.sourceLayer setValue:@(chromaKeyThreshold) forKeyPath:@"filters.Chromakey.inputThreshold"];
    [CATransaction commit];
}

-(float)chromaKeyThreshold
{
    return _chromaKeyThreshold;
}



-(void)setDoChromaKey:(bool)doChromaKey
{
    
    _doChromaKey = doChromaKey;

    [CATransaction begin];
    [self.layer.sourceLayer setValue:[NSNumber numberWithBool:doChromaKey] forKeyPath:@"filters.Chromakey.enabled" ];
    if (doChromaKey)
    {
                
        [self.layer.sourceLayer setValue:[[CIColor alloc] initWithColor:self.chromaKeyColor] forKeyPath:@"filters.Chromakey.inputColor"];

        [self.layer.sourceLayer setValue:@(self.chromaKeySmoothing) forKeyPath:@"filters.Chromakey.inputSmoothing"];

        [self.layer.sourceLayer setValue:@(self.chromaKeyThreshold) forKeyPath:@"filters.Chromakey.inputThreshold"];

    }
    [CATransaction commit];
    
}

-(bool)doChromaKey
{
    return _doChromaKey;
}



-(void) removeObjectFromVideoSourcesAtIndex:(NSUInteger)index
{
    id removedSource = [self.videoSources objectAtIndex:index];
    [self deregisterVideoInput:removedSource];
    [self.videoSources removeObjectAtIndex:index];
}



-(void)willDelete
{
    [self detachAllInputs];
    if (self.parentInput)
    {
        [self.parentInput detachInput:self];
    }
    
    
    _currentInput = nil;
}


-(void) buildLayerConstraints
{
    
    NSMutableArray *constraints = [NSMutableArray array];
    
    for (NSString *key in self.constraintMap)
    {
        
        
        NSNumber *constraintVal;
        constraintVal = _constraintAttributeMap[key];
        
        if (!constraintVal)
        {
            continue;
        }
        
        CAConstraintAttribute toConstrain = [constraintVal intValue];
        NSDictionary *valMap = self.constraintMap[key];
        if (!valMap)
        {
            continue;
        }
        NSNumber *parentVal = valMap[@"attr"];
        NSNumber *offsetVal = valMap[@"offset"];
        
        if (!parentVal || (id)parentVal == [NSNull null])
        {
            continue;
        }
        
        CGFloat offsetFloat = 0.0;
        
        if (offsetVal)
        {
            offsetFloat = [offsetVal floatValue];
        }
        
        CAConstraintAttribute parentAttrib = [parentVal intValue];
        if (!parentAttrib)
        {
            continue;
        }
        [constraints addObject:[CAConstraint constraintWithAttribute:toConstrain relativeTo:@"superlayer" attribute:parentAttrib scale:1 offset:offsetFloat]];
        
    }
    
    [CATransaction begin];
    self.layer.constraints = constraints;
    [CATransaction commit];
    
    
}


//I should probably use contexts...
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{

    
    if ([keyPath hasPrefix:@"constraintMap"])
    {
        id oldValue = change[NSKeyValueChangeOldKey];
        NSRect oldFrame = self.layoutPosition;
        
        [[self.sourceLayout.undoManager prepareWithInvocationTarget:self.sourceLayout] modifyUUID:self.uuid withBlock:^(InputSource *input) {
            [input setValue:oldValue forKeyPath:keyPath];
            [input updateSize:oldFrame.size.width height:oldFrame.size.height];
            [input positionOrigin:oldFrame.origin.x y:oldFrame.origin.y];
            
        }];
        [self.sourceLayout.undoManager setActionName:@"Constraint Change"];

        [self buildLayerConstraints];
    } else if ([keyPath isEqualToString:@"captureName"]) {
        self.name = _editedName;
    }
}



-(void)setClonedFromInput:(InputSource *)clonedFromInput
{
    NSObject <CSCaptureSourceProtocol>*fromInput = clonedFromInput.videoInput;
    
    if (self.videoInput)
    {
        [self deregisterVideoInput:fromInput];
        _currentLayer = nil;
    }
    
    
    if (fromInput)
    {
        self.videoInput = fromInput;
        [self registerVideoInput:fromInput];
        _currentLayer = [fromInput layerForInput:self];
    }
    
    
    _clonedFromInput = clonedFromInput;
}


-(InputSource *)clonedFromInput
{
    return _clonedFromInput;
}



-(void)stopObservingConstraintKeys
{
    for (NSString *key in _constraintObserveKeys)
    {
        [self removeObserver:self forKeyPath:key];
    }
}



-(void)observeConstraintKeys
{
    for (NSString *key in _constraintObserveKeys)
    {
        [self addObserver:self forKeyPath:key options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
    }
}




@end
