module Processing


  # Vector class.
  #
  class Vector

    include Comparable

    # Initialize vector object.
    #
    # @overload new()
    # @overload new(x)
    # @overload new(x, y)
    # @overload new(x, y, z)
    # @overload new(v)
    # @overload new(a)
    #
    # @param x [Numeric] x of vector
    # @param y [Numeric] y of vector
    # @param z [Numeric] z of vector
    # @param v [Vector]  vector object to copy
    # @param a [Array]   array like [x, y, z]
    #
    def initialize(x = 0, y = 0, z = 0, context: nil)
      @point = case x
        when Rays::Point then x.dup
        when Vector      then x.getInternal__.dup
        when Array       then Rays::Point.new x[0] || 0, x[1] || 0, x[2] || 0
        else                  Rays::Point.new x    || 0, y    || 0, z    || 0
        end
      @context = context || Context.context__
    end

    # Initializer for dup or clone
    #
    def initialize_copy(o)
      @point = o.getInternal__.dup
    end

    # Copy vector object
    #
    # @return [Vector] duplicated vector object
    #
    alias copy dup

    # Sets x, y and z.
    #
    # @overload set(x)
    # @overload set(x, y)
    # @overload set(x, y, z)
    # @overload set(v)
    # @overload set(a)
    #
    # @param x [Numeric] x of vector
    # @param y [Numeric] y of vector
    # @param z [Numeric] z of vector
    # @param v [Vector]  vector object to copy
    # @param a [Array]   array with x, y, z
    #
    # @return [nil] nil
    #
    def set(*args)
      initialize(*args)
      self
    end

    # Gets x value.
    #
    # @return [Numeric] x value of vector
    #
    def x()
      @point.x
    end

    # Gets y value.
    #
    # @return [Numeric] y value of vector
    #
    def y()
      @point.y
    end

    # Gets z value.
    #
    # @return [Numeric] z value of vector
    #
    def z()
      @point.z
    end

    # Sets x value.
    #
    # @return [Numeric] x value of vector
    #
    def x=(x)
      @point.x = x
    end

    # Sets y value.
    #
    # @return [Numeric] y value of vector
    #
    def y=(y)
      @point.y = y
    end

    # Sets z value.
    #
    # @return [Numeric] z value of vector
    #
    def z=(z)
      @point.z = z
    end

    # Returns the interpolated vector between 2 vectors.
    #
    # @overload lerp(v, amount)
    # @overload lerp(x, y, amount)
    # @overload lerp(x, y, z, amount)
    #
    # @param v      [Vector]  vector to interpolate
    # @param x      [Numeric] x of vector to interpolate
    # @param y      [Numeric] y of vector to interpolate
    # @param z      [Numeric] z of vector to interpolate
    # @param amount [Numeric] amount to interpolate
    #
    # @return [Vector] interporated vector
    #
    def lerp(*args, amount)
      v      = toVector__(*args)
      self.x = x + (v.x - x) * amount
      self.y = y + (v.y - y) * amount
      self.z = z + (v.z - z) * amount
      self
    end

    # Returns the interpolated vector between 2 vectors.
    #
    # @param v1     [Vector] vector to interpolate
    # @param v2     [Vector] vector to interpolate
    # @param amount [Numeric] amount to interpolate
    #
    # @return [Vector] interporated vector
    #
    def self.lerp(v1, v2, amount)
      v1.dup.lerp v2, amount
    end

    # Returns x, y, z as an array
    #
    # @param n [Numeric] number of dimensions
    #
    # @return [Array] array of x, y, z
    #
    def array(n = 3)
      @point.to_a n
    end

    alias to_a array

    # Adds a vector.
    #
    # @overload add(v)
    # @overload add(x, y)
    # @overload add(x, y, z)
    #
    # @param v [Vector] vector to add
    # @param x [Vector] x of vector to add
    # @param y [Vector] y of vector to add
    # @param z [Vector] z of vector to add
    #
    # @return [Vector] added vector
    #
    def add(*args)
      @point += toVector__(*args).getInternal__
      self
    end

    # Subtracts a vector.
    #
    # @overload sub(v)
    # @overload sub(x, y)
    # @overload sub(x, y, z)
    #
    # @param v [Vector] vector to subtract
    # @param x [Vector] x of vector to subtract
    # @param y [Vector] y of vector to subtract
    # @param z [Vector] z of vector to subtract
    #
    # @return [Vector] subtracted vector
    #
    def sub(*args)
      @point -= toVector__(*args).getInternal__
      self
    end

    # Multiplies a vector by scalar.
    #
    # @param num [Numeric] number to multiply the vector
    #
    # @return [Vector] multiplied vector
    #
    def mult(num)
      @point *= num
      self
    end

    # Divides a vector by scalar.
    #
    # @param num [Numeric] number to divide the vector
    #
    # @return [Vector] divided vector
    #
    def div(num)
      @point /= num
      self
    end

    # Adds a vector.
    #
    # @param v [Vector] vector to add
    #
    # @return [Vector] added vector
    #
    def +(v)
      dup.add v
    end

    # Subtracts a vector.
    #
    # @param v [Vector] vector to subtract
    #
    # @return [Vector] subtracted vector
    #
    def -(v)
      dup.sub v
    end

    # Multiplies a vector by scalar.
    #
    # @param num [Numeric] number to multiply the vector
    #
    # @return [Vector] multiplied vector
    #
    def *(num)
      dup.mult num
    end

    # Divides a vector by scalar.
    #
    # @param num [Numeric] number to divide the vector
    #
    # @return [Vector] divided vector
    #
    def /(num)
      dup.div num
    end

    # Adds 2 vectors.
    #
    # @overload add(v1, v2)
    # @overload add(v1, v2, target)
    #
    # @param v1     [Vector] a vector
    # @param v2     [Vector] another vector
    # @param target [Vector] vector to store added vector
    #
    # @return [Vector] added vector
    #
    def self.add(v1, v2, target = nil)
      v = v1 + v2
      target.set v if self === target
      v
    end

    # Subtracts 2 vectors.
    #
    # @overload sub(v1, v2)
    # @overload sub(v1, v2, target)
    #
    # @param v1     [Vector] a vector
    # @param v2     [Vector] another vector
    # @param target [Vector] vector to store subtracted vector
    #
    # @return [Vector] subtracted vector
    #
    def self.sub(v1, v2, target = nil)
      v = v1 - v2
      target.set v if self === target
      v
    end

    # Multiplies a vector by scalar.
    #
    # @overload mult(v, num)
    # @overload mult(v, num, target)
    #
    # @param v      [Vector]  a vector
    # @param num    [Numeric] number to multiply the vector
    # @param target [Vector]  vector to store multiplied vector
    #
    # @return [Vector] multiplied vector
    #
    def self.mult(v1, num, target = nil)
      v = v1 * num
      target.set v if self === target
      v
    end

    # Divides a vector by scalar.
    #
    # @overload div(v, num)
    # @overload div(v, num, target)
    #
    # @param v      [Vector]  a vector
    # @param num    [Numeric] number to divide the vector
    # @param target [Vector]  vector to store divided vector
    #
    # @return [Vector] divided vector
    #
    def self.div(v1, num, target = nil)
      v = v1 / num
      target.set v if self === target
      v
    end

    # Returns the length of the vector.
    #
    # @return [Numeric] length
    #
    def mag()
      @point.length
    end

    # Returns squared length of the vector.
    #
    # @return [Numeric] squared length
    #
    def magSq()
      Rays::Point::dot(@point, @point)
    end

    # Changes the length of the vector.
    #
    # @overload setMag(len)
    # @overload setMag(target, len)
    #
    # @param len    [Numeric] length of new vector
    # @param target [Vector]  vector to store new vector
    #
    # @return [Vector] vector with new length
    #
    def setMag(target = nil, len)
      (target || self).set @point.normal * len
    end

    # Changes the length of the vector to 1.0.
    #
    # @param target [Vector] vector to store the normalized vector
    #
    # @return [Vector] normalized vector
    #
    def normalize(target = nil)
      (target || self).set @point.normal
    end

    # Changes the length of the vector if it's length is greater than the max value.
    #
    # @param max [Numeric] max length
    #
    # @return [Vector] new vector
    #
    def limit(max)
      setMag max if magSq > max ** 2
      self
    end

    # Returns the distance of 2 vectors.
    #
    # @param v [Vector] a vector
    #
    # @return [Numeric] the distance
    #
    def dist(v)
      (self - v).mag
    end

    # Returns the distance of 2 vectors.
    #
    # @param v1 [Vector] a vector
    # @param v2 [Vector] another vector
    #
    # @return [Numeric] the distance
    #
    def self.dist(v1, v2)
      v1.dist v2
    end

    # Calculates the dot product of 2 vectors.
    #
    # @overload dot(v)
    # @overload dot(x, y)
    # @overload dot(x, y, z)
    #
    # @param v [Vector] a vector
    # @param x [Numeric] x of vector
    # @param y [Numeric] y of vector
    # @param z [Numeric] z of vector
    #
    # @return [Numeric] result of dot product
    #
    def dot(*args)
      Rays::Point::dot getInternal__, toVector__(*args).getInternal__
    end

    # Calculates the dot product of 2 vectors.
    #
    # @param v1 [Vector] a vector
    # @param v2 [Vector] another vector
    #
    # @return [Numeric] result of dot product
    #
    def self.dot(v1, v2)
      v1.dot v2
    end

    # Calculates the cross product of 2 vectors.
    #
    # @overload cross(v)
    # @overload cross(x, y)
    # @overload cross(x, y, z)
    #
    # @param v [Vector] a vector
    # @param x [Numeric] x of vector
    # @param y [Numeric] y of vector
    # @param z [Numeric] z of vector
    #
    # @return [Numeric] result of cross product
    #
    def cross(a, *rest)
      target = self.class === rest.last ? rest.pop : nil
      v = self.class.new Rays::Point::cross getInternal__, toVector__(a, *rest).getInternal__
      target.set v if self.class === target
      v
    end

    # Calculates the cross product of 2 vectors.
    #
    # @param v1 [Vector] a vector
    # @param v2 [Vector] another vector
    #
    # @return [Numeric] result of cross product
    #
    def self.cross(v1, v2, target = nil)
      v1.cross v2, target
    end

    # Rotate the vector.
    #
    # @param angle [Numeric] the angle of rotation
    #
    # @return [Vector] rotated this object
    #
    def rotate(angle)
      angle = @context ?
        @context.toAngle__(angle) : angle * GraphicsContext::RAD2DEG__
      @point.rotate! angle
      self
    end

    # Returns the angle of rotation for this vector.
    #
    # @return [Numeric] the angle in radians
    #
    def heading()
      Math.atan2 y, x
    end

    # Returns rotated new vector.
    #
    # @param angle  [Numeric] the angle of rotation
    # @param target [Vector]  vector to store new vector
    #
    # @return [Vector] rotated vector
    #
    def self.fromAngle(angle, target = nil)
      v = self.new(1, 0, 0).rotate(angle)
      target.set v if target
      v
    end

    # Returns angle between 2 vectors.
    #
    # @param v1 [Vector] a vector
    # @param v2 [Vector] another vector
    #
    # @return [Numeric] angle in radians
    #
    def self.angleBetween(v1, v2)
      x1, y1, z1 = v1.array
      x2, y2, z2 = v2.array
      return 0 if (x1 == 0 && y1 == 0 && z1 == 0) || (x2 == 0 && y2 == 0 && z2 == 0)

      x = dot(v1, v2) / (v1.mag * v2.mag)
      return Math::PI if x <= -1
      return 0        if x >= 1
      return Math.acos x
    end

    # Returns a new 2D unit vector with a random direction.
    #
    # @param target [Vector] a vector to store the new vector
    #
    # @return [Vector] a random vector
    #
    def self.random2D(target = nil)
      v = self.new(1, 0, 0)
      v.getInternal__.rotate! rand 0.0...360.0
      target.set v if target
      v
    end

    # Returns a new 3D unit vector with a random direction.
    #
    # @param target [Vector] a vector to store the new vector
    #
    # @return [Vector] a random vector
    #
    def self.random3D(target = nil)
      angle = rand 0.0...(Math::PI * 2)
      z     = rand(-1.0..1.0)
      z2    = z ** 2
      x     = Math.sqrt(1.0 - z2) * Math.cos(angle)
      y     = Math.sqrt(1.0 - z2) * Math.sin(angle)
      v     = self.new x, y, z
      target.set v if target
      v
    end

    # Returns a string containing a human-readable representation of object.
    #
    # @return [String] inspected text
    #
    def inspect()
      "#<#{self.class.name}: #{x}, #{y}, #{z}>"
    end

    # @private
    def <=>(o)
      @point <=> o&.getInternal__
    end

    # @private
    def getInternal__()
      @point
    end

    # @private
    private def toVector__(*args)
      self.class === args.first ? args.first : self.class.new(*args)
    end

  end# Vector


end# Processing