//
//  LayerContainer.swift
//  lottie-swift
//
//  Created by Brandon Withrow on 1/22/19.
//

import Foundation
import QuartzCore

// MARK: - CompositionLayer

/// The base class for a child layer of CompositionContainer
class CompositionLayer: CALayer, KeypathSearchable {

  // MARK: Lifecycle

  init(layer: LayerModel, size: CGSize) {
    transformNode = LayerTransformNode(transform: layer.transform)
    if let masks = layer.masks {
      maskLayer = MaskContainerLayer(masks: masks)
    } else {
      maskLayer = nil
    }
    matteType = layer.matte
    inFrame = layer.inFrame.cgFloat
    outFrame = layer.outFrame.cgFloat
    timeStretch = layer.timeStretch.cgFloat
    startFrame = layer.startTime.cgFloat
    keypathName = layer.name
    childKeypaths = [transformNode.transformProperties]
    super.init()
    anchorPoint = .zero
    actions = [
      "opacity" : NSNull(),
      "transform" : NSNull(),
      "bounds" : NSNull(),
      "anchorPoint" : NSNull(),
      "sublayerTransform" : NSNull(),
    ]

    contentsLayer.anchorPoint = .zero
    contentsLayer.bounds = CGRect(origin: .zero, size: size)
    contentsLayer.actions = [
      "opacity" : NSNull(),
      "transform" : NSNull(),
      "bounds" : NSNull(),
      "anchorPoint" : NSNull(),
      "sublayerTransform" : NSNull(),
      "hidden" : NSNull(),
    ]
    compositingFilter = layer.blendMode.filterName
    addSublayer(contentsLayer)

    if let maskLayer = maskLayer {
      contentsLayer.mask = maskLayer
    }

    name = layer.name
  }

  override init(layer: Any) {
    /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
    guard let layer = layer as? CompositionLayer else {
      fatalError("Wrong Layer Class")
    }
    transformNode = layer.transformNode
    matteType = layer.matteType
    inFrame = layer.inFrame
    outFrame = layer.outFrame
    timeStretch = layer.timeStretch
    startFrame = layer.startFrame
    keypathName = layer.keypathName
    childKeypaths = [transformNode.transformProperties]
    maskLayer = nil
    super.init(layer: layer)
  }

  required init?(coder _: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  // MARK: Internal

  weak var layerDelegate: CompositionLayerDelegate?

  let transformNode: LayerTransformNode

  let contentsLayer = CALayer()

  let maskLayer: MaskContainerLayer?

  let matteType: MatteType?

  let inFrame: CGFloat
  let outFrame: CGFloat
  let startFrame: CGFloat
  let timeStretch: CGFloat

  // MARK: Keypath Searchable

  let keypathName: String

  final var childKeypaths: [KeypathSearchable]

  var renderScale: CGFloat = 1 {
    didSet {
      updateRenderScale()
    }
  }

  var matteLayer: CompositionLayer? {
    didSet {
      if let matte = matteLayer {
        if let type = matteType, type == .invert {
          mask = InvertedMatteLayer(inputMatte: matte)
        } else {
          mask = matte
        }
      } else {
        mask = nil
      }
    }
  }

  var keypathProperties: [String: AnyNodeProperty] {
    [:]
  }

  var keypathLayer: CALayer? {
    contentsLayer
  }

  final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) {
    transformNode.updateTree(frame, forceUpdates: forceUpdates)
    let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame)
    /// Only update contents if current time is within the layers time bounds.
    if layerVisible {
      displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates)
      maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates)
    }
    contentsLayer.transform = transformNode.globalTransform
    contentsLayer.opacity = transformNode.opacity
    contentsLayer.isHidden = !layerVisible
    layerDelegate?.frameUpdated(frame: frame)
  }

  func displayContentsWithFrame(frame _: CGFloat, forceUpdates _: Bool) {
    /// To be overridden by subclass
  }

  func updateRenderScale() {
    contentsScale = renderScale
  }
}

// MARK: - CompositionLayerDelegate

protocol CompositionLayerDelegate: AnyObject {
  func frameUpdated(frame: CGFloat)
}