// The MIT License (MIT)
//
// Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import UIKit

#if os(iOS)
public class HeroDebugPlugin: HeroPlugin {
  public static var showOnTop: Bool = false

  var debugView: HeroDebugView?
  var zPositionMap = [UIView: CGFloat]()
  var addedLayers: [CALayer] = []
  var updating = false

  override public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
    if hero.forceNotInteractive { return 0 }
    var hasArc = false
    for v in context.fromViews + context.toViews where context[v]?.arc != nil && context[v]?.position != nil {
      hasArc = true
      break
    }
    let debugView = HeroDebugView(initialProcess: hero.isPresenting ? 0.0 : 1.0, showCurveButton: hasArc, showOnTop: HeroDebugPlugin.showOnTop)
    debugView.frame = hero.container.bounds
    debugView.delegate = self
    hero.container.window?.addSubview(debugView)

    debugView.layoutSubviews()
    self.debugView = debugView

    UIView.animate(withDuration: 0.4) {
      debugView.showControls = true
    }

    return .infinity
  }

  public override func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
    guard let debugView = debugView else { return 0.4 }
    debugView.delegate = nil

    UIView.animate(withDuration: 0.4) {
      debugView.showControls = false
      debugView.debugSlider.setValue(roundf(debugView.progress), animated: true)
    }

    on3D(wants3D: false)
    return 0.4
  }

  public override func clean() {
    debugView?.removeFromSuperview()
    debugView = nil
  }
}

extension HeroDebugPlugin: HeroDebugViewDelegate {
  public func onDone() {
    guard let debugView = debugView else { return }
    let seekValue = hero.isPresenting ? debugView.progress : 1.0 - debugView.progress
    if seekValue > 0.5 {
      hero.finish()
    } else {
      hero.cancel()
    }
  }

  public func onProcessSliderChanged(progress: Float) {
    let seekValue = hero.isPresenting ? progress : 1.0 - progress
    hero.update(CGFloat(seekValue))
  }

  func onPerspectiveChanged(translation: CGPoint, rotation: CGFloat, scale: CGFloat) {
    var t = CATransform3DIdentity
    t.m34 = -1 / 4000
    t = CATransform3DTranslate(t, translation.x, translation.y, 0)
    t = CATransform3DScale(t, scale, scale, 1)
    t = CATransform3DRotate(t, rotation, 0, 1, 0)
    hero.container.layer.sublayerTransform = t
  }

  func animateZPosition(view: UIView, to: CGFloat) {
    let a = CABasicAnimation(keyPath: "zPosition")
    a.fromValue = view.layer.value(forKeyPath: "zPosition")
    a.toValue = NSNumber(value: Double(to))
    a.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
    a.duration = 0.4
    view.layer.add(a, forKey: "zPosition")
    view.layer.zPosition = to
  }

  func onDisplayArcCurve(wantsCurve: Bool) {
    for layer in addedLayers {
      layer.removeFromSuperlayer()
      addedLayers.removeAll()
    }
    if wantsCurve {
      for layer in hero.container.layer.sublayers! {
        for (_, anim) in layer.animations {
          if let keyframeAnim = anim as? CAKeyframeAnimation, let path = keyframeAnim.path {
            let s = CAShapeLayer()
            s.zPosition = layer.zPosition + 10
            s.path = path
            s.strokeColor = UIColor.blue.cgColor
            s.fillColor = UIColor.clear.cgColor
            hero.container.layer.addSublayer(s)
            addedLayers.append(s)
          }
        }
      }
    }
  }

  func on3D(wants3D: Bool) {
    var t = CATransform3DIdentity
    if wants3D {
      var viewsWithZPosition = Set<UIView>()
      for view in hero.container.subviews where view.layer.zPosition != 0 {
        viewsWithZPosition.insert(view)
        zPositionMap[view] = view.layer.zPosition
      }

      let viewsWithoutZPosition = hero.container.subviews.filter { return !viewsWithZPosition.contains($0) }
      let viewsWithPositiveZPosition = viewsWithZPosition.filter { return $0.layer.zPosition > 0 }

      for (i, v) in viewsWithoutZPosition.enumerated() {
        animateZPosition(view: v, to: CGFloat(i * 10))
      }

      var maxZPosition: CGFloat = 0
      for v in viewsWithPositiveZPosition {
        maxZPosition = max(maxZPosition, v.layer.zPosition)
        animateZPosition(view: v, to: v.layer.zPosition + CGFloat(viewsWithoutZPosition.count * 10))
      }

      t.m34 = -1 / 4000
      t = CATransform3DTranslate(t, debugView!.translation.x, debugView!.translation.y, 0)
      t = CATransform3DScale(t, debugView!.scale, debugView!.scale, 1)
      t = CATransform3DRotate(t, debugView!.rotation, 0, 1, 0)
    } else {
      for v in hero.container.subviews {
        animateZPosition(view: v, to: self.zPositionMap[v] ?? 0)
      }
      self.zPositionMap.removeAll()
    }

    let a = CABasicAnimation(keyPath: "sublayerTransform")
    a.fromValue = hero.container.layer.value(forKeyPath: "sublayerTransform")
    a.toValue = NSValue(caTransform3D: t)
    a.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
    a.duration = 0.4

    UIView.animate(withDuration: 0.4) {
      self.context.container.backgroundColor = UIColor(white: 0.85, alpha: 1.0)
    }

    hero.container.layer.add(a, forKey: "debug")
    hero.container.layer.sublayerTransform = t
  }
}
#endif
