class CATransform3D
  # CATransform3D.make  # default transform: identity matrix
  # # make a transform from scratch. please don't ever do this ;-)
  # CATransform3D.make(
  #   m11: 1, m12: 0, m13: 0, m14: 0,
  #   m21: 0, m22: 1, m23: 0, m24: 0,
  #   m31: 0, m32: 0, m33: 1, m34: 0,
  #   m41: 0, m42: 0, m43: 0, m44: 1,
  # )
  #
  # # make a transform using primitives
  # CATransform3D.make(scale: 2, translate: [10, 10, 0], rotate: Math::PI)
  def self.make(options = {})
    if options[:m11]
      args = [
        :m11, :m12, :m13, :m14,
        :m21, :m22, :m23, :m24,
        :m31, :m32, :m33, :m34,
        :m41, :m42, :m43, :m44,
      ].map do |key|
        raise "#{key.inspect} is required" unless options.key? key
        options[key]
      end
      return self.new(*args)
    else
      retval = self.identity
      if options[:translate]
        retval = retval.translate(options[:translate])
      end
      if options[:scale]
        retval = retval.scale(options[:scale])
      end
      if options[:rotate]
        retval = retval.rotate(options[:rotate])
      end
      return retval
    end
  end

  # Returns a transform that is translated. Accepts one or three arguments. One
  # argument can be an Array with three numbers, three arguments should be the
  # x, y, z values.
  # @return [CATransform3D]
  def self.translate(point, ty=nil, tz=nil)
    if ty
      tx = point
    elsif point.is_a?(CGPoint)
      tx = point[0]
      ty = point[1]
    else
      tx = point[0]
      ty = point[1]
      tz = point[2]
    end

    tz ||= 0
    CATransform3DMakeTranslation(tx, ty, tz)
  end

  # Returns a transform that is scaled. Accepts one or three arguments. One
  # argument can be an Array with three items or a number that will be used to
  # scale both x and y directions (z will be scale 1). Three arguments should be
  # the x, y, z values.
  def self.scale(scale, sy=nil, sz=nil)
    if sy
      sx = scale
    elsif scale.is_a?(Numeric)
      sx = sy = scale
      sz = 1
    else
      sx = scale[0]
      sy = scale[1]
      sz = scale[2]
    end
    CATransform3DMakeScale(sx, sy, sz)
  end

  # Returns a transform that is rotated by `angle` (+ => counterclockwise, - => clockwise)
  # @return [CATransform3D]
  def self.rotate(angle, point=nil, y=nil, z=nil)
    if point && y && z
      x = point
    elsif point
      x = point[0]
      y = point[1]
      z = point[2]
    else
      # default: spins around z-axis
      x = 0
      y = 0
      z = 1
    end
    CATransform3DMakeRotation(angle, x, y, z)
  end

  # A "shear" translation turns a rectangle into a parallelogram.  Delegates to
  # the CGAffineTransform with the same name
  # @param px [Numeric] how much to shear in x direction
  # @param py [Numeric] how much to shear in x direction
  # @return CATransform3D
  def self.shear(px, py)
    CGAffineTransform.shear(px, py).to_transform3d
  end

  # A perspective transform is a lot like a rotation... but different.  These
  # numbers should be very small, like 0.002 is a decent perspective shift.
  # @return CATransform3D
  def self.perspective(x, y)
    CATransform3D.new(1,0,0,x, 0,1,0,y, 0,0,1,0, 0,0,0,1)
  end

  # Returns the CATransform3D identity matrix
  # @return [CATransform3D]
  def self.identity
    CATransform3DMakeTranslation(0, 0, 0)
  end

  # Return true if the receiver is the identity matrix, false otherwise
  # @return [Boolean]
  def identity?
    CATransform3DIsIdentity(self)
  end

  # Return true if the receiver can be represented as an affine transform
  # @return [Boolean]
  def affine?
    CATransform3DIsAffine(self)
  end

  # @return [CGAffineTransform]
  def to_affine_transform
    CATransform3DGetAffineTransform(self)
  end

  # @return [Boolean] true if the two matrices are equal
  def ==(transform)
    CATransform3DEqualToTransform(self, transform)
  end

  # Returns self
  # @return [CATransform3D]
  def +@
    self
  end

  # Concatenates the two transforms
  # @return [CATransform3D]
  def concat(transform)
    CATransform3DConcat(self, transform)
  end
  alias :+ :concat
  alias :<< :concat

  # Inverts the transform
  # @return [CATransform3D]
  def invert
    CATransform3DInvert(self)
  end
  alias :-@ :invert

  # Inverts the second transform and adds the result to `self`
  # @return [CATransform3D]
  def -(transform)
    self.concat transform.invert
  end

  # Applies a translation transform to the receiver
  # @return [CATransform3D]
  def translate(point, ty=nil, tz=nil)
    if ty
      tx = point
    elsif point.is_a?(CGPoint)
      tx = point[0]
      ty = point[1]
    else
      tx = point[0]
      ty = point[1]
      tz = point[2]
    end

    tz ||= 0
    CATransform3DTranslate(self, tx, ty, tz)
  end

  # Applies a scale transform to the receiver
  # @return [CATransform3D]
  def scale(scale, sy=nil, sz=nil)
    if sy
      sx = scale
    elsif scale.is_a?(Numeric)
      sx = sy = scale
      sz = 1
    else
      sx = scale[0]
      sy = scale[1]
      sz = scale[2]
    end
    CATransform3DScale(self, sx, sy, sz)
  end

  # Applies a rotation transform to the receiver
  # @return [CATransform3D]
  def rotate(angle, point=nil, y=nil, z=nil)
    if point && y && z
      x = point
    elsif point
      x = point[0]
      y = point[1]
      z = point[2]
    else
      # default: spins around z-axis
      x = 0
      y = 0
      z = 1
    end
    CATransform3DRotate(self, angle, x, y, z)
  end

  def shear(px, py)
    self.concat CGAffineTransform.shear(px, py).to_transform3d
  end

  # Applies a perspective transform to the receiver
  # @return CATransform3D
  def perspective(x, y)
    self.concat CATransform3D.perspective(x, y)
  end

  def to_ns_value
    NSValue.valueWithCATransform3D(self)
  end

  def self.from_ns_value(value)
    value.CATransform3DValue
  end

  def to_a
    [self.m11, self.m12, self.m13, self.m14, self.m21, self.m22, self.m23, self.m24, self.m31, self.m32, self.m33, self.m34, self.m41, self.m42, self.m43, self.m44]
  end

private
  def to_ary
    to_a
  end

end
