module Vedeu

  # Namespace for configuration classes.
  #
  module Config

    module_function

    # Custom log for configuration.
    #
    # @param from [String] Which configuration set the options.
    # @param options [Hash] The configuration options set.
    # @return [Hash] The options param.
    def log(from, options)
      options.each do |option, value|
        Vedeu.log(type:    :config,
                  message: "#{from} #{option}: #{value}")
      end
    end

  end

  # Allows the customisation of Vedeu's behaviour through the
  # configuration API.
  #
  # Provides access to Vedeu's configuration, which was set with
  # sensible defaults (influenced by environment variables),
  # overridden by client application settings (via the configuration
  # API).
  #
  class Configuration

    include Singleton

    class << self

      # Returns the base_path value.
      #
      # @return [String]
      def base_path
        instance.options[:base_path]
      end

      # Returns the compression value.
      #
      # @return [Boolean]
      def compression
        instance.options[:compression]
      end
      alias_method :compression?, :compression

      # Provides the mechanism to configure Vedeu. If the client
      # application sets options, override the defaults with those.
      #
      # @example
      #   Vedeu.configure do
      #     # ...
      #   end
      #
      # @param opts [Hash]
      # @option opts stdin [File|IO]
      # @option opts stdout [File|IO]
      # @option opts stderr [File|IO]
      # @param block [Proc]
      # @raise [Vedeu::Error::InvalidSyntax]
      #   When the required block is not given.
      # @return [Hash]
      def configure(opts = {}, &block)
        instance.configure(opts, &block)
      end

      # Returns the configuration singleton.
      # Append configuration methods to access the configuration
      # variable.
      #
      # @example
      #   Vedeu.configuration
      #
      # @return [Vedeu::Configuration]
      def configuration
        instance
      end

      # Returns the chosen colour mode.
      #
      # @return [Fixnum]
      def colour_mode
        instance.options[:colour_mode]
      end

      # Returns whether debugging is enabled or disabled. Default is
      # false; meaning nothing apart from warnings are written to the
      # log file.
      #
      # @return [Boolean]
      def debug?
        instance.options[:debug]
      end
      alias_method :debug, :debug?

      # Returns whether the DRb server is enabled or disabled. Default
      # is false.
      #
      # @return [Boolean]
      def drb?
        instance.options[:drb]
      end
      alias_method :drb, :drb?

      # Returns the hostname for the DRb server.
      #
      # @return [String]
      def drb_host
        instance.options[:drb_host]
      end

      # Returns the port for the DRb server.
      #
      # @return [String]
      def drb_port
        instance.options[:drb_port]
      end

      # Returns the height for the fake terminal in the DRb server.
      #
      # @return [Fixnum]
      def drb_height
        instance.options[:drb_height]
      end

      # Returns the width for the fake terminal in the DRb server.
      #
      # @return [Fixnum]
      def drb_width
        instance.options[:drb_width]
      end

      # Returns the client defined height for the terminal.
      #
      # @return [Fixnum]
      def height
        instance.options[:height]
      end

      # Returns whether the application is interactive (required user
      # input) or standalone (will run until terminates of natural
      # causes.) Default is true; meaning the application will require
      # user input.
      #
      # @return [Boolean]
      def interactive?
        instance.options[:interactive]
      end
      alias_method :interactive, :interactive?

      # Returns the path to the log file.
      #
      # @return [String]
      def log
        instance.options[:log]
      end

      # Returns a boolean indicating whether the log has been
      # configured.
      #
      # @return [Boolean]
      def log?
        log != nil
      end

      # @return [Array<Symbol>]
      def log_only
        instance.options[:log_only] || []
      end

      # Returns whether the application will run through its main loop
      # once or not. Default is false; meaning the application will
      # loop forever or until terminated by the user.
      #
      # @return [Boolean]
      def once?
        instance.options[:once]
      end
      alias_method :once, :once?

      # Returns the renderers which should receive output.
      #
      # @return [Array<Class>]
      def renderers
        instance.options[:renderers]
      end

      # Returns the root of the client application. Vedeu will execute
      # this controller first.
      #
      # @return [Class]
      def root
        instance.options[:root]
      end

      # Returns the redefined setting for STDIN.
      #
      # @return [File|IO]
      def stdin
        instance.options[:stdin]
      end

      # Returns the redefined setting for STDOUT.
      #
      # @return [File|IO]
      def stdout
        instance.options[:stdout]
      end

      # Returns the redefined setting for STDERR.
      #
      # @return [File|IO]
      def stderr
        instance.options[:stderr]
      end

      # Returns the terminal mode for the application. Default is
      # `:raw`.
      #
      # @return [Symbol]
      def terminal_mode
        instance.options[:terminal_mode]
      end

      # Returns the client defined width for the terminal.
      #
      # @return [Fixnum]
      def width
        instance.options[:width]
      end

      # @param value [void]
      # @return [void]
      def options=(value)
        instance.options = value
      end

      # Reset the configuration to the default values.
      #
      # @return [Hash]
      def reset!
        instance.reset!
      end

    end # Eigenclass

    # @!attribute [r] options
    # @return [Hash]
    attr_reader :options

    # Create a new singleton instance of Vedeu::Configuration.
    #
    # @return [Vedeu::Configuration]
    def initialize
      @options = defaults
    end

    # Set up default configuration and then allow the client
    # application to modify it via the configuration API.
    #
    # @param block [Proc]
    # @return [Hash]
    def configure(opts = {}, &block)
      @options.merge!(opts)

      @options.merge!(Config::API.configure(&block)) if block_given?

      Vedeu::Renderers.renderer(*@options[:renderers])

      Vedeu::Configuration
    end

    # Reset the configuration to the default values.
    #
    # @return [Hash]
    def reset!
      @options = defaults
    end

    private

    # The Vedeu default options, which of course are influenced by
    # environment variables also.
    #
    # @return [Hash<Symbol => void>]
    def defaults
      {
        base_path:     base_path,
        colour_mode:   detect_colour_mode,
        compression:   true,
        debug:         false,
        drb:           false,
        drb_host:      nil,
        drb_port:      nil,
        drb_height:    25,
        drb_width:     80,
        height:        nil,
        interactive:   true,
        log:           nil,
        log_only:      [],
        once:          false,
        renderers:     [Vedeu::Renderers::Terminal.new],
        root:          nil,
        stdin:         nil,
        stdout:        nil,
        stderr:        nil,
        terminal_mode: :raw,
        width:         nil,
      }
    end

    # Attempt to determine the terminal colour mode via $TERM
    # environment variable, or be optimistic and settle for 256
    # colours.
    #
    # @return [Fixnum]
    def detect_colour_mode
      case ENV['TERM']
      when /-truecolor$/         then 16_777_216
      when /-256color$/, 'xterm' then 256
      when /-color$/, 'rxvt'     then 16
      else 256
      end
    end

    # @return [String]
    def base_path
      File.expand_path('.')
    end

  end # Configuration

end # Vedeu