module Vedeu

  module Runtime

    # Orchestrates the running of the main application loop.
    #
    class Application

      class << self

        # @param (see #initialize)
        def start(configuration)
          new(configuration).start
        end

        # @param (see #initialize)
        def restart(configuration)
          new(configuration).start
        end

        # Stops the application! - The `:_cleanup_` event is
        # triggered, which in turn triggers the client event
        # `:cleanup`; the client application may treat this event as
        # Vedeu signalling that it is about to terminate. Client
        # applications are encouraged to use this event to close any
        # open buffers, save files, empty trash, etc.
        #
        # @example
        #   Vedeu.exit
        #
        # @return [void]
        def stop
          Vedeu.trigger(:_cleanup_)

          Vedeu::Runtime::MainLoop.stop!
        end
        alias_method :exit, :stop

      end # Eigenclass

      # :nocov:

      # Returns a new instance of Vedeu::Runtime::Application.
      #
      # @param configuration [Vedeu::Configuration]
      # @return [Vedeu::Runtime::Application]
      def initialize(configuration)
        @configuration = configuration
      end

      # Starts the application!
      #
      # - A new terminal screen is opened (or rather the current
      #   terminal is requested into either :raw or :cooked mode).
      # - The cursor visibility is then set dependent on this mode.
      #   In :raw mode, the cursor is hidden.
      # - The `:_initialize_` event is triggered. The client
      #   application is may treat this event as Vedeu signalling that
      #   it is now ready.
      # - We enter into the main sequence where the application will
      #   either run once or continuous, interactively or standalone.
      #
      # @return [Array] The last output sent to the terminal.
      def start
        Vedeu.trigger(:_drb_start_)

        Vedeu::Terminal.open do
          Vedeu::Terminal.set_cursor_mode

          Vedeu.trigger(:_initialize_)

          runner { main_sequence }
        end
      end

      protected

      # @!attribute [r] configuration
      # @return [Vedeu::Configuration]
      attr_reader :configuration

      private

      # Runs the application loop either once, or forever (exceptions
      # and signals permitting).
      #
      # @return [void]
      def runner
        if configuration.once?
          yield

        else
          run_many { yield }

        end
      end

      # For an interactive application we capture input, (usually from
      # the user), and continue the main loop. If the client
      # application does not require user input then Vedeu triggers
      # the `:_standalone_` event for each run of the main loop. The
      # client application is expected to respond to this event and
      # 'do something useful'. When the client application has
      # finished, it should trigger the `:_exit_` event.
      #
      # @return [void]
      def main_sequence
        if configuration.interactive?
          Vedeu::Input::Input.capture(Terminal)

        else
          Vedeu.trigger(:_standalone_)

        end
      end

      # Runs the application in a continuous loop. This loop is
      # stopped when an uncaught exception occurs or when either the
      # `:_mode_switch_` or `:_exit_` event is triggered.
      #
      # @return [void]
      def run_many
        Vedeu::Runtime::MainLoop.start! { yield }

      rescue Vedeu::Error::ModeSwitch
        Vedeu::Terminal.switch_mode!

        Vedeu.trigger(:_drb_restart_)

        Vedeu::Runtime::Application.restart(configuration)
      end

      # :nocov:

    end # Application

  end # Runtime

end # Vedeu