module RubEmUp
  module Utils
    ##
    # Class combining in itself information about both speed and direction.
    class Velocity
      class << self
        # @!group Creating velocity

        ##
        # Creates velocity from speed and a direction(or a valid direction
        # representation).
        #
        # @note Valid Symbols representing a direction are ```:forward```,
        #   ```:backward```, ```:left```, ```:right```, ```:forward_left```,
        #   ```:backward_left```, ```:backward_right```, ```:forward_right```
        # @note ```:forward``` is ```0``` degrees, ```:left``` is ```90```,
        #   ```:backward``` is ```180``` and ```:right``` is ```270```
        #
        # @param speed [Float]
        # @param angle_or_direction [Direction, Symbol, Float] direction or
        #   valid direction representation
        #
        # @return [Velocity]
        #
        # @see Velocity.from_vector
        # @see #initialize
        def [](speed, angle_or_direction)
          Velocity.new(speed, angle_or_direction)
        end

        ##
        # Creates velocity from a {Vector} instance, i.e, the magnitude of
        # the vector is the speed and the direction or the vector is the
        # direction of the velocity.
        #
        # @param vector [Vector]
        #
        # @return [Velocity]
        #
        # @see Velocity.[]
        # @see #initialize
        def from_vector(vector)
          Velocity.new(vector.magnitude, vector.angle)
        end
      end

      ##
      # Creates velocity from speed and a direction(or a valid direction
      # representation).
      #
      # @note (see Velocity.[])
      #
      # @param (see Velocity.[])
      #
      # @see Velocity.[]
      # @see Velocity.from_vector
      def initialize(speed, angle_or_direction)
        angle = if angle_or_direction.is_a? Direction
                  angle_or_direction.to_angle
                elsif angle_or_direction.is_a? Symbol
                  Direction.new(angle_or_direction).to_angle
                else
                  angle_or_direction
                end

        @vector = Vector[speed, 0].rotate(angle)
      end

      # @!endgroup

      ##
      # @!attribute [r] speed
      #
      # @return [Float] the speed of the velocity
      #
      # @see #direction
      def speed
        @vector.magnitude
      end

      ##
      # @!attribute [r] direction
      #
      # @return [Direction] the direction of the velocity
      #
      # @see #speed
      def direction
        Direction.new(@vector.angle)
      end

      # @!group Velocity arithmetics

      ##
      # Adds two velocities by adding together their vector representations.
      #
      # @param other [Velocity, #to_vector] the velocity to be added to *self*,
      #   could be another object that implements #to_vector
      #
      # @return [Velocity] the resulting velocity
      #
      # @see #-
      # @see #*
      # @see #/
      def +(other)
        Velocity.from_vector(@vector + other.to_vector)
      end

      ##
      # Subtracts two velocities by subtracting their vector representations.
      #
      # @param other [Velocity, #to_vector] the velocity to be subtracted from
      #   *self*, could be another object that implements #to_vector
      #
      # @return (see #+)
      #
      # @see #+
      # @see #*
      # @see #/
      def -(other)
        Velocity.from_vector(@vector - other.to_vector)
      end

      ##
      # Multiplies velocity by a given number. The velocity's speed gets
      # multiplied by that number and if the number is negative, the direction
      # will become the opposite.
      #
      # @param number [Float]
      #
      # @return (see #+)
      #
      # @see #+
      # @see #-
      # @see #/
      def *(number)
        Velocity.from_vector(@vector * number)
      end

      ##
      # Divides velocity by a given number. The velocity's speed gets divided
      # by that number and if the number is negative, the direction will become
      # the opposite.
      #
      # @param (see #*)
      #
      # @return (see #+)
      #
      # @see #+
      # @see #-
      # @see #*
      def /(number)
        Velocity.from_vector(@vector / number)
      end

      # @!endgroup

      # @!group Changing existing velocity

      ##
      # Returns a new velocity with speed passed as an argument and the
      # direction of *self*.
      #
      # @param speed [Float] the new velocity's speed
      #
      # @return [Velocity] the new velocity with changed speed
      #
      # @see #with_direction
      # @see #convert_to_basis
      def with_speed(speed)
        Velocity[speed, direction]
      end

      ##
      # Returns a new velocity with direction passed as an arguments and the
      # speed of *self*.
      #
      # @note (see #initialize)
      #
      # @param direction [Direction, Symbol, Float] the new velocity's
      #   direction
      #
      # @return [Velocity] the new velocity with changed direction
      #
      # @see #with_speed
      # @see #convert_to_basis
      def with_direction(direction)
        Velocity[speed, direction]
      end

      ##
      # Returns a new velocity that represents *self* in the given basis.
      #
      # @param x_vector [Vector] one of the basis vectors in a 2D vector space
      # @param y_vector [Vector] one of the basis vectors in a 2D vector space
      #
      # @return [Velocity] the new velocity representing *self* in the given
      #   basis
      #
      # @see #with_speed
      # @see #with_direction
      def convert_to_basis(x_vector, y_vector)
        Velocity.from_vector(@vector.convert_to_basis(x_vector, y_vector))
      end

      # @!endgroup

      # @!group Representation methods

      # @!group Comparison methods

      ##
      # Compares both velocities and returns: ```true``` if their speeds and
      # directions are equal and ```false``` otherwise.
      #
      # @param other [Velocity] the velocity *self* will be compared with
      #
      # @return [Boolean] whether the velocitues are equal or not
      def ==(other)
        @vector == other.to_vector
      end

      # @!endgroup

      ##
      # Returns a {Vector} representation of *self*. That is a {Vector} with
      # magnitude equal to the speed of *self* and direction equal to the
      # direction of *self*.
      #
      # @return [Vector] the {Vector} representation
      #
      # @see #to_s
      def to_vector
        @vector
      end

      ##
      # Returns a string representation of *self*.
      #
      # @return [String] the string representation
      #
      # @see #to_vector
      def to_s
        "Velocity(#{speed}, #{angle})"
      end

      # @!endgroup
    end
  end
end
