# frozen_string_literal: true

module Vedeu

  module Runtime

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

      class << self

        # @return [void]
        def start
          new.start
        end

        # @return [void]
        def restart
          new.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 exit stop

      end # Eigenclass

      # :nocov:

      # Returns a new instance of Vedeu::Runtime::Application.
      #
      # @return [Vedeu::Runtime::Application]
      def initialize; 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

      private

      # Runs the application loop either once, or forever (exceptions
      # and signals permitting).
      #
      # @param block [Proc]
      # @return [void]
      def runner(&block)
        return yield if Vedeu.config.once?

        run_many { yield }
      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 Vedeu.config.interactive?
          Vedeu::Input::Capture.read

        else
          Vedeu.trigger(:_standalone_)
          Vedeu.refresh

        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.
      #
      # @param block [Proc]
      # @return [void]
      def run_many(&block)
        Vedeu::Runtime::MainLoop.start! { yield }

      rescue Vedeu::Error::ModeSwitch
        Vedeu::Runtime::Application.restart
      end

      # :nocov:

    end # Application

  end # Runtime

  # @!method exit
  #   @see Vedeu::Runtime::Application.stop
  def_delegators Vedeu::Runtime::Application,
                 :exit

  # :nocov:

  # See {file:docs/events/system.md#\_exit_}
  Vedeu.bind(:_exit_) { Vedeu.exit }

  # See {file:docs/events/system.md#\_mode_switch_}
  Vedeu.bind(:_mode_switch_) do |mode|
    Vedeu::Runtime::MainLoop.mode_switch!(mode)
  end

  # See {file:docs/events/system.md#\_cleanup_}
  Vedeu.bind(:_cleanup_) do
    Vedeu.trigger(:_drb_stop_)
    Vedeu.trigger(:cleanup)
  end

  # :nocov:

end # Vedeu