module Concurrent
  module Synchronization

    # @!visibility private
    class AbstractLockableObject < Synchronization::Object

      protected

      # @!macro synchronization_object_method_synchronize
      #
      #   @yield runs the block synchronized against this object,
      #     equivalent of java's `synchronize(this) {}`
      #   @note can by made public in descendants if required by `public :synchronize`
      def synchronize
        raise NotImplementedError
      end

      # @!macro synchronization_object_method_ns_wait_until
      #
      #   Wait until condition is met or timeout passes,
      #   protects against spurious wake-ups.
      #   @param [Numeric, nil] timeout in seconds, `nil` means no timeout
      #   @yield condition to be met
      #   @yieldreturn [true, false]
      #   @return [true, false] if condition met
      #   @note only to be used inside synchronized block
      #   @note to provide direct access to this method in a descendant add method
      #     ```
      #     def wait_until(timeout = nil, &condition)
      #       synchronize { ns_wait_until(timeout, &condition) }
      #     end
      #     ```
      def ns_wait_until(timeout = nil, &condition)
        if timeout
          wait_until = Concurrent.monotonic_time + timeout
          loop do
            now              = Concurrent.monotonic_time
            condition_result = condition.call
            return condition_result if now >= wait_until || condition_result
            ns_wait wait_until - now
          end
        else
          ns_wait timeout until condition.call
          true
        end
      end

      # @!macro synchronization_object_method_ns_wait
      #
      #   Wait until another thread calls #signal or #broadcast,
      #   spurious wake-ups can happen.
      #
      #   @param [Numeric, nil] timeout in seconds, `nil` means no timeout
      #   @return [self]
      #   @note only to be used inside synchronized block
      #   @note to provide direct access to this method in a descendant add method
      #     ```
      #     def wait(timeout = nil)
      #       synchronize { ns_wait(timeout) }
      #     end
      #     ```
      def ns_wait(timeout = nil)
        raise NotImplementedError
      end

      # @!macro synchronization_object_method_ns_signal
      #
      #   Signal one waiting thread.
      #   @return [self]
      #   @note only to be used inside synchronized block
      #   @note to provide direct access to this method in a descendant add method
      #     ```
      #     def signal
      #       synchronize { ns_signal }
      #     end
      #     ```
      def ns_signal
        raise NotImplementedError
      end

      # @!macro synchronization_object_method_ns_broadcast
      #
      #   Broadcast to all waiting threads.
      #   @return [self]
      #   @note only to be used inside synchronized block
      #   @note to provide direct access to this method in a descendant add method
      #     ```
      #     def broadcast
      #       synchronize { ns_broadcast }
      #     end
      #     ```
      def ns_broadcast
        raise NotImplementedError
      end

    end
  end
end
