Pods/lottie-ios/lottie-ios/Classes/RenderSystem/RenderNodes/LOTRenderGroup.m
//
//  LOTRenderGroup.m
//  Lottie
//
//  Created by brandon_withrow on 6/27/17.
//  Copyright © 2017 Airbnb. All rights reserved.
//

#import "LOTRenderGroup.h"
#import "LOTModels.h"
#import "LOTPathAnimator.h"
#import "LOTFillRenderer.h"
#import "LOTStrokeRenderer.h"
#import "LOTNumberInterpolator.h"
#import "LOTTransformInterpolator.h"
#import "LOTCircleAnimator.h"
#import "LOTRoundedRectAnimator.h"
#import "LOTTrimPathNode.h"
#import "LOTShapeStar.h"
#import "LOTPolygonAnimator.h"
#import "LOTPolystarAnimator.h"
#import "LOTShapeGradientFill.h"
#import "LOTGradientFillRender.h"
#import "LOTRepeaterRenderer.h"
#import "LOTShapeRepeater.h"

@implementation LOTRenderGroup {
  LOTAnimatorNode *_rootNode;
  LOTBezierPath *_outputPath;
  LOTBezierPath *_localPath;
  BOOL _rootNodeHasUpdate;
  LOTNumberInterpolator *_opacityInterpolator;
  LOTTransformInterpolator *_transformInterpolator;
}

- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode * _Nullable)inputNode
                                   contents:(NSArray * _Nonnull)contents
                                    keyname:(NSString * _Nullable)keyname {
  self = [super initWithInputNode:inputNode keyName:keyname];
  if (self) {
    _containerLayer = [CALayer layer];
    _containerLayer.actions = @{@"transform": [NSNull null],
                                @"opacity": [NSNull null]};
    [self buildContents:contents];
  }
  return self;
}

- (NSDictionary *)valueInterpolators {
  if (_opacityInterpolator && _transformInterpolator) {
    return @{@"Opacity" : _opacityInterpolator,
             @"Position" : _transformInterpolator.positionInterpolator,
             @"Scale" : _transformInterpolator.scaleInterpolator,
             @"Rotation" : _transformInterpolator.scaleInterpolator,
             @"Anchor Point" : _transformInterpolator.anchorInterpolator,
             // Deprecated
             @"Transform.Opacity" : _opacityInterpolator,
             @"Transform.Position" : _transformInterpolator.positionInterpolator,
             @"Transform.Scale" : _transformInterpolator.scaleInterpolator,
             @"Transform.Rotation" : _transformInterpolator.scaleInterpolator,
             @"Transform.Anchor Point" : _transformInterpolator.anchorInterpolator
             };
  }
  return nil;
}

- (void)buildContents:(NSArray *)contents {
  LOTAnimatorNode *previousNode = nil;
  LOTShapeTransform *transform;
  for (id item in contents) {
    if ([item isKindOfClass:[LOTShapeFill class]]) {
      LOTFillRenderer *fillRenderer = [[LOTFillRenderer alloc] initWithInputNode:previousNode
                                                                       shapeFill:(LOTShapeFill *)item];
      [self.containerLayer insertSublayer:fillRenderer.outputLayer atIndex:0];
      previousNode = fillRenderer;
    } else if ([item isKindOfClass:[LOTShapeStroke class]]) {
      LOTStrokeRenderer *strokRenderer = [[LOTStrokeRenderer alloc] initWithInputNode:previousNode
                                                                          shapeStroke:(LOTShapeStroke *)item];
      [self.containerLayer insertSublayer:strokRenderer.outputLayer atIndex:0];
      previousNode = strokRenderer;
    } else if ([item isKindOfClass:[LOTShapePath class]]) {
      LOTPathAnimator *pathAnimator = [[LOTPathAnimator alloc] initWithInputNode:previousNode
                                                                       shapePath:(LOTShapePath *)item];
      previousNode = pathAnimator;
    } else if ([item isKindOfClass:[LOTShapeRectangle class]]) {
      LOTRoundedRectAnimator *rectAnimator = [[LOTRoundedRectAnimator alloc] initWithInputNode:previousNode
                                                                                shapeRectangle:(LOTShapeRectangle *)item];
      previousNode = rectAnimator;
    } else if ([item isKindOfClass:[LOTShapeCircle class]]) {
      LOTCircleAnimator *circleAnimator = [[LOTCircleAnimator alloc] initWithInputNode:previousNode
                                                                           shapeCircle:(LOTShapeCircle *)item];
      previousNode = circleAnimator;
    } else if ([item isKindOfClass:[LOTShapeGroup class]]) {
      LOTShapeGroup *shapeGroup = (LOTShapeGroup *)item;
      LOTRenderGroup *renderGroup = [[LOTRenderGroup alloc] initWithInputNode:previousNode contents:shapeGroup.items keyname:shapeGroup.keyname];
      [self.containerLayer insertSublayer:renderGroup.containerLayer atIndex:0];
      previousNode = renderGroup;
    } else if ([item isKindOfClass:[LOTShapeTransform class]]) {
      transform = (LOTShapeTransform *)item;
    } else if ([item isKindOfClass:[LOTShapeTrimPath class]]) {
      LOTTrimPathNode *trim = [[LOTTrimPathNode alloc] initWithInputNode:previousNode trimPath:(LOTShapeTrimPath *)item];
      previousNode = trim;
    } else if ([item isKindOfClass:[LOTShapeStar class]]) {
      LOTShapeStar *star = (LOTShapeStar *)item;
      if (star.type == LOTPolystarShapeStar) {
        LOTPolystarAnimator *starAnimator = [[LOTPolystarAnimator alloc] initWithInputNode:previousNode shapeStar:star];
        previousNode = starAnimator;
      }
      if (star.type == LOTPolystarShapePolygon) {
        LOTPolygonAnimator *polygonAnimator = [[LOTPolygonAnimator alloc] initWithInputNode:previousNode shapePolygon:star];
        previousNode = polygonAnimator;
      }
    } else if ([item isKindOfClass:[LOTShapeGradientFill class]]) {
      LOTGradientFillRender *gradientFill = [[LOTGradientFillRender alloc] initWithInputNode:previousNode shapeGradientFill:(LOTShapeGradientFill *)item];
      previousNode = gradientFill;
      [self.containerLayer insertSublayer:gradientFill.outputLayer atIndex:0];
    } else if ([item isKindOfClass:[LOTShapeRepeater class]]) {
      LOTRepeaterRenderer *repeater = [[LOTRepeaterRenderer alloc] initWithInputNode:previousNode shapeRepeater:(LOTShapeRepeater *)item];
      previousNode = repeater;
      [self.containerLayer insertSublayer:repeater.outputLayer atIndex:0];
    }
  }
  if (transform) {
    _opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:transform.opacity.keyframes];
    _transformInterpolator = [[LOTTransformInterpolator alloc] initWithPosition:transform.position.keyframes
                                                                      rotation:transform.rotation.keyframes
                                                                        anchor:transform.anchor.keyframes
                                                                         scale:transform.scale.keyframes];
  }
  _rootNode = previousNode;
}

- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
  return ([_opacityInterpolator hasUpdateForFrame:frame] ||
          [_transformInterpolator hasUpdateForFrame:frame] ||
          _rootNodeHasUpdate);

}

- (BOOL)updateWithFrame:(NSNumber *)frame withModifierBlock:(void (^ _Nullable)(LOTAnimatorNode * _Nonnull))modifier forceLocalUpdate:(BOOL)forceUpdate {
  indentation_level = indentation_level + 1;
  _rootNodeHasUpdate = [_rootNode updateWithFrame:frame withModifierBlock:modifier forceLocalUpdate:forceUpdate];
  indentation_level = indentation_level - 1;
  BOOL update = [super updateWithFrame:frame withModifierBlock:modifier forceLocalUpdate:forceUpdate];
  return update;
}

- (void)performLocalUpdate {
  if (_opacityInterpolator) {
    self.containerLayer.opacity = [_opacityInterpolator floatValueForFrame:self.currentFrame];
  }
  if (_transformInterpolator) {
    CATransform3D xform = [_transformInterpolator transformForFrame:self.currentFrame];
    self.containerLayer.transform = xform;
    
    CGAffineTransform appliedXform = CATransform3DGetAffineTransform(xform);
    _localPath = [_rootNode.outputPath copy];
    [_localPath LOT_applyTransform:appliedXform];
  } else {
    _localPath = [_rootNode.outputPath copy];
  }
}

- (void)rebuildOutputs {
  if (self.inputNode) {
    _outputPath = [self.inputNode.outputPath copy];
    [_outputPath LOT_appendPath:self.localPath];
  } else {
    _outputPath = self.localPath;
  }
}

- (void)setPathShouldCacheLengths:(BOOL)pathShouldCacheLengths {
  [super setPathShouldCacheLengths:pathShouldCacheLengths];
  _rootNode.pathShouldCacheLengths = pathShouldCacheLengths;
}

- (LOTBezierPath *)localPath {
  return _localPath;
}

- (LOTBezierPath *)outputPath {
  return _outputPath;
}

- (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath {
  [self.inputNode searchNodesForKeypath:keypath];
  if ([keypath pushKey:self.keyname]) {
    // Matches self. Dig deeper.
    // Check interpolators

    if ([keypath pushKey:@"Transform"]) {
      // Matches a Transform interpolator!
      if (self.valueInterpolators[keypath.currentKey] != nil) {
        [keypath pushKey:keypath.currentKey];
        [keypath addSearchResultForCurrentPath:self];
        [keypath popKey];
      }
      [keypath popKey];
    }

    if (keypath.endOfKeypath) {
      // We have a match!
      [keypath addSearchResultForCurrentPath:self];
    }
    // Check child nodes
    [_rootNode searchNodesForKeypath:keypath];
    [keypath popKey];
  }
}

- (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
              forKeypath:(LOTKeypath * _Nonnull)keypath {
  if ([keypath pushKey:self.keyname]) {
    // Matches self. Dig deeper.
    // Check interpolators
    if ([keypath pushKey:@"Transform"]) {
      // Matches a Transform interpolator!
      LOTValueInterpolator *interpolator = self.valueInterpolators[keypath.currentKey];
      if (interpolator) {
        // We have a match!
        [interpolator setValueDelegate:delegate];
      }
      [keypath popKey];
    }

    // Check child nodes
    [_rootNode setValueDelegate:delegate forKeypath:keypath];

    [keypath popKey];
  }

  // Check upstream
  [self.inputNode setValueDelegate:delegate forKeypath:keypath];
}

@end
