module Ray
  class Vector2
    class << self
      alias :[] :new
    end

    # @return [true, false] True if the receive is contained in the rect
    def inside?(rect)
      rect.to_rect.contain? self
    end

    # Opposite of #inside?
    def outside?(rect)
      !inside?(rect)
    end

    # @param [Ray::Vector2, #to_vector2] other
    # @return [Ray::Vector2] (x + other.x, y + other.y)
    def +(other)
      other = other.to_vector2
      Ray::Vector2[x + other.x, y + other.y]
    end

    # @param [Ray::Vector2, #to_vector2] other
    # @return [Ray::Vector2] (x - other.x, y - other.y)
    def -(other)
      other = other.to_vector2
      Ray::Vector2[x - other.x, y - other.y]
    end

    # @overload *(float)
    #   @param [Float] float
    #   @return [Ray::Vector2] (x * float, y * float)
    #
    # @overload *(vector)
    #   @param [Ray::Vector2, #to_vector2] vector
    #   @return [Ray::Vector2] (a.x * b.x, a.y * b.y)
    def *(other)
      if other.respond_to? :to_vector2
        other = other.to_vector2
        Ray::Vector2[x * other.x, y * other.y]
      else
        Ray::Vector2[x * other, y * other]
      end
    end

    # @overload /(float)
    #   @param [Float] float
    #   @return [Ray::Vector2] (x / float, y / float)
    #
    # @overload /(vector)
    #   @param [Ray::Vector2, #to_vector2] vector
    #   @return [Ray::Vector2] (a.x / b.x, a.y / b.y)
    def /(other)
      if other.respond_to? :to_vector2
        other = other.to_vector2
        Ray::Vector2[x / other.x, y / other.y]
      else
        Ray::Vector2[x / other, y / other]
      end
    end


     # @param [Ray::Vector2, #to_vector2]
     # @return [Float] a.x * b.x  + a.y * b.y
     def dot(vector)
       vector = vector.to_vector2
       x * vector.x + y * vector.y
     end

    # @return [Ray::Vector2] (-x, -y)
    def -@; Ray::Vector2[-x, -y] end

    # @return [Ray::Vector2] (x, y)
    def +@; self; end

    # @return [Float] The length of the vector
    def length
      Math.sqrt(x * x + y * y)
    end

    alias :norm :length

    # @return [Ray::Vector2] Normalized vector (i.e. length will be 1)
    def normalize
      self / length
    end

    # Normalizes the vector, by dividing it by its length.
    def normalize!
      length = self.length

      self.x /= length
      self.y /= length

      self
    end

    # @param [Ray::Vector2, #to_vector2] other
    # @return [Float] The distance between two vectors
    def dist(other)
      (self - other).length
    end
    alias distance dist

    def ==(other)
      if other.is_a? Vector2
        x == other.x && y == other.y
      elsif other.is_a? Array
        if other.size <= 2
          other = other.to_vector2
          x == other.x && y == other.y
        else
          false
        end
      elsif other.respond_to? :to_vector2
        other = other.to_vector2
        x == other.x && y == other.y
      else
        false
      end
    end

    def eql?(other)
      self.class == other.class && self == other
    end

    def hash
      to_a.hash
    end

    def to_a
      [x, y]
    end

    def to_vector2
      self
    end

    def to_s
      "(%g, %g)" % [x, y]
    end

    def pretty_print(q)
      q.text "("
      q.pp(("%g" % x).to_f) # hides simple-precision inacurracy
      q.text ", "
      q.pp(("%g" % y).to_f)
      q.text ")"
    end

    alias :w :x
    alias :h :y

    alias :width  :w
    alias :height :h

    alias :w= :x=
    alias :h= :y=

    alias :width=  :x=
    alias :height= :y=
  end

  class Vector3
    class << self
      alias :[] :new
    end

    # @param [Ray::Vector3, #to_vector3] other
    # @return [Ray::Vector3] (x + other.x, y + other.y, z + other.z)
    def +(other)
      other = other.to_vector3
      Ray::Vector3[x + other.x, y + other.y, z + other.z]
    end

    # @param [Ray::Vector3, #to_vector3] other
    # @return [Ray::Vector3] (x - other.x, y - other.y, z - other.z)
    def -(other)
      other = other.to_vector3
      Ray::Vector3[x - other.x, y - other.y, z - other.z]
    end

    # @overload *(float)
    #   @param [Float] float
    #   @return [Ray::Vector3] (x * float, y * float, z * float)
    #
    # @overload *(vector)
    #   @param [Ray::Vector3, #to_vector3] vector
    #   @return [Ray::Vector3] (a.x * b.x, a.y * b.y, a.z * b.z)
    def *(other)
      if other.respond_to? :to_vector3
        other = other.to_vector3
        Ray::Vector3[x * other.x, y * other.y, z * other.z]
      else
        Ray::Vector3[x * other, y * other, z * other]
      end
    end

    # @overload /(float)
    #   @param [Float] float
    #   @return [Ray::Vector3] (x / float, y / float, z / float)
    #
    # @overload /(vector)
    #   @param [Ray::Vector3, #to_vector3] vector
    #   @return [Ray::Vector3] (a.x / b.x, a.y / b.y, a.z / b.z)
    def /(other)
      if other.respond_to? :to_vector3
        other = other.to_vector3
        Ray::Vector3[x / other.x, y / other.y, z / other.z]
      else
        Ray::Vector3[x / other, y / other, z / other]
      end
    end

    # @param [Ray::Vector3, #to_vector3] vector
    # @return [Float] Dot product (i.e. x * vector.x + y * vector.y + z * vector.z)
    def dot(vector)
      vector = vector.to_vector3
      x * vector.x + y * vector.y + z * vector.z
    end

    # @return [Ray::Vector3] (-x, -y, -z)
    def -@; Ray::Vector3[-x, -y, -z] end

    # @return [Ray::Vector3] (x, y, y)
    def +@; self; end

    # @return [Float] The length of the vector
    def length
      Math.sqrt(x * x + y * y + z * z)
    end

    alias :norm :length

    # @return [Ray::Vector3] Normalized vector (i.e. length will be 1)
    def normalize
      self / length
    end

    # Normalizes the vector, by dividing it by its length.
    def normalize!
      length = self.length

      self.x /= length
      self.y /= length
      self.z /= length

      self
    end

    # @param [Ray::Vector3, #to_vector3] other
    # @return [Float] The distance between two vectors
    def dist(other)
      (self - other).length
    end
    alias distance dist

    def ==(other)
      if other.is_a? Vector3
        x == other.x && y == other.y && z == other.z
      elsif other.is_a? Array
        if other.size <= 3
          other = other.to_vector3
          x == other.x && y == other.y && z == other.z
        else
          false
        end
      elsif other.respond_to? :to_vector3
        other = other.to_vector3
        x == other.x && y == other.y && z == other.z
      else
        false
      end
    end

    def eql?(other)
      self.class == other.class && self == other
    end

    def hash
      to_a.hash
    end

    def to_s
      "(%g, %g, %g)" % [x, y, z]
    end

    def pretty_print(q)
      q.text "("
      q.pp(("%g" % x).to_f) # hides simple-precision inacurracy
      q.text ", "
      q.pp(("%g" % y).to_f)
      q.text ", "
      q.pp(("%g" % z).to_f)
      q.text ")"
    end

    def to_a
      [x, y, z]
    end

    def to_vector3
      self
    end
  end
end

class Array
  # @return [Ray::Vector2] Converts an array into a vector2.
  def to_vector2
    Ray::Vector2[*self]
  end

  # @return [Ray::Vector3] Converts an array into a vector3.
  def to_vector3
    Ray::Vector3[*self]
  end
end
