require 'thread'

require 'timeout'
require 'listen/event/processor'

module Listen
  module Event
    class Loop
      class Error < RuntimeError
        class NotStarted < Error
        end
      end

      def initialize(config)
        @config = config
        @wait_thread = nil
        @state = :paused
        @reasons = ::Queue.new
      end

      def wakeup_on_event
        return if stopped?
        return unless processing?
        return unless wait_thread.alive?
        _wakeup(:event)
      end

      def paused?
        wait_thread && state == :paused
      end

      def processing?
        return false if stopped?
        return false if paused?
        state == :processing
      end

      def setup
        # TODO: use a Fiber instead?
        q = ::Queue.new
        @wait_thread = Internals::ThreadPool.add do
          _wait_for_changes(q, config)
        end

        Listen::Logger.debug('Waiting for processing to start...')
        Timeout.timeout(5) { q.pop }
      end

      def resume
        fail Error::NotStarted if stopped?
        return unless wait_thread
        _wakeup(:resume)
      end

      def pause
        # TODO: works?
        # fail NotImplementedError
      end

      def teardown
        return unless wait_thread
        if wait_thread.alive?
          _wakeup(:teardown)
          wait_thread.join
        end
        @wait_thread = nil
      end

      def stopped?
        !wait_thread
      end

      private

      attr_reader :config
      attr_reader :wait_thread

      attr_accessor :state

      def _wait_for_changes(ready_queue, config)
        processor = Event::Processor.new(config, @reasons)

        _wait_until_resumed(ready_queue)
        processor.loop_for(config.min_delay_between_events)
      rescue StandardError => ex
        _nice_error(ex)
      end

      def _sleep(*args)
        Kernel.sleep(*args)
      end

      def _wait_until_resumed(ready_queue)
        self.state = :paused
        ready_queue << :ready
        sleep
        self.state = :processing
      end

      def _nice_error(ex)
        indent = "\n -- "
        msg = format(
          'exception while processing events: %s Backtrace:%s%s',
          ex,
          indent,
          ex.backtrace * indent
        )
        Listen::Logger.error(msg)
      end

      def _wakeup(reason)
        @reasons << reason
        wait_thread.wakeup
      end
    end
  end
end
