//
//  CGFloatExtensions.swift
//  lottie-swift
//
//  Created by Brandon Withrow on 1/14/19.
//

import Foundation
import QuartzCore

extension CGFloat {

  // MARK: Internal

  var squared: CGFloat {
    self * self
  }

  var cubed: CGFloat {
    self * self * self
  }

  var cubicRoot: CGFloat {
    CGFloat(pow(Double(self), 1.0 / 3.0))
  }

  func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool {
    from <= self && self <= to
  }

  func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool {
    from < self && self < to
  }

  func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat {
    var t: CGFloat
    if self == P0.x {
      // Handle corner cases explicitly to prevent rounding errors
      t = 0
    } else if self == P3.x {
      t = 1
    } else {
      // Calculate t
      let a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x;
      let b = 3 * P0.x - 6 * P1.x + 3 * P2.x;
      let c = -3 * P0.x + 3 * P1.x;
      let d = P0.x - self;
      let tTemp = CGFloat.SolveCubic(a, b, c, d);
      if tTemp == -1 {
        return -1;
      }
      t = tTemp
    }

    // Calculate y from t
    return (1 - t).cubed * P0.y + 3 * t * (1 - t).squared * P1.y + 3 * t.squared * (1 - t) * P2.y + t.cubed * P3.y;
  }

  func cubicBezier(_ t: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat {
    let t_ = (1.0 - t)
    let tt_ = t_ * t_
    let ttt_ = t_ * t_ * t_
    let tt = t * t
    let ttt = t * t * t

    return self * ttt_
      + 3.0 * c1 * tt_ * t
      + 3.0 * c2 * t_ * tt
      + end * ttt;
  }

  // MARK: Fileprivate

  fileprivate static func SolveQuadratic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat {
    var result = (-b + sqrt(b.squared - 4 * a * c)) / (2 * a);
    guard !result.isInRangeOrEqual(0, 1) else {
      return result
    }

    result = (-b - sqrt(b.squared - 4 * a * c)) / (2 * a);
    guard !result.isInRangeOrEqual(0, 1) else {
      return result
    }

    return -1;
  }

  fileprivate static func SolveCubic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat, _ d: CGFloat) -> CGFloat {
    if a == 0 {
      return SolveQuadratic(b, c, d)
    }
    if d == 0 {
      return 0
    }
    let a = a
    var b = b
    var c = c
    var d = d
    b /= a
    c /= a
    d /= a
    var q = (3.0 * c - b.squared) / 9.0
    let r = (-27.0 * d + b * (9.0 * c - 2.0 * b.squared)) / 54.0
    let disc = q.cubed + r.squared
    let term1 = b / 3.0

    if disc > 0 {
      var s = r + sqrt(disc)
      s = (s < 0) ? -((-s).cubicRoot) : s.cubicRoot
      var t = r - sqrt(disc)
      t = (t < 0) ? -((-t).cubicRoot) : t.cubicRoot

      let result = -term1 + s + t;
      if result.isInRangeOrEqual(0, 1) {
        return result
      }
    } else if disc == 0 {
      let r13 = (r < 0) ? -((-r).cubicRoot) : r.cubicRoot;

      var result = -term1 + 2.0 * r13;
      if result.isInRangeOrEqual(0, 1) {
        return result
      }

      result = -(r13 + term1);
      if result.isInRangeOrEqual(0, 1) {
        return result
      }

    } else {
      q = -q;
      var dum1 = q * q * q;
      dum1 = acos(r / sqrt(dum1));
      let r13 = 2.0 * sqrt(q);

      var result = -term1 + r13 * cos(dum1 / 3.0);
      if result.isInRangeOrEqual(0, 1) {
        return result
      }
      result = -term1 + r13 * cos((dum1 + 2.0 * .pi) / 3.0);
      if result.isInRangeOrEqual(0, 1) {
        return result
      }
      result = -term1 + r13 * cos((dum1 + 4.0 * .pi) / 3.0);
      if result.isInRangeOrEqual(0, 1) {
        return result
      }
    }

    return -1;
  }
}
