require 'singleton'

module ThinkingSphinx
  module Raspell
    # Configuration settings for spelling suggestions in Thinking Sphinx. You
    # can access these either by the singleton instance
    # (ThinkingSphinx::Raspell::Configuration.instance), or through Thinking 
    # Sphinx's existing configuration instance
    # (ThinkingSphinx::Configuration.instance.raspell).
    # 
    # @author Pat Allan
    # 
    class Configuration
      include Singleton
      
      attr_reader :dictionary, :suggestion_mode, :options
      
      # Creates the instance of the singleton with the following defaults:
      # Dictionary is English (en), suggestion mode is normal, and the
      # ignore-case option is enabled.
      # 
      def initialize
        reset
      end
      
      # Resets the instance to the default settings. Probably not necessary in
      # general usage, but makes specs easier to run.
      # 
      def reset
        @dictionary      = 'en'
        @suggestion_mode = :normal
        @options         = {'ignore-case' => true}
        @speller         = nil
      end
      
      # Sets the dictionary for the spelling suggestions. Make sure you have the
      # dictionary installed on your system first.
      # 
      # @example
      #   config.dictionary = 'en_GB'
      # 
      # @param [String] dict dictionary code
      # @raise [ArgumentError] if the dictionary code is not known by Aspell
      # 
      def dictionary=(dict)
        unless dictionaries.include?(dict)
          raise ArgumentError, 'unknown dictionary'
        end
        
        @dictionary = dict
      end
      
      # The list of dictionaries Aspell has on offer.
      # 
      # @return [Array] collection of dictionary codes
      # 
      def dictionaries
        @dictionaries ||= Aspell.list_dicts.collect { |dict| dict.code }
      end
      
      # Set the suggestion mode. Accepts symbols, strings and Aspell constants.
      # The known values are ultra, fast, normal and badspellers/bad-spellers.
      # 
      # @param [Symbol, String] mode suggestion mode
      # @raise [ArgumentError] if the suggestion mode is not known by Aspell
      # 
      def suggestion_mode=(mode)
        mode = mode.gsub(/-/, '').to_sym if mode.is_a?(String)
        
        unless suggestion_modes.include?(mode)
          raise ArgumentError, 'unknown suggestion mode'
        end
        
        @suggestion_mode = mode
      end
      
      # The allowed suggestion modes.
      # 
      # @return [Array] available suggestion modes, as symbols.
      # 
      def suggestion_modes
        [:ultra, :fast, :normal, :badspellers]
      end

      # Set the speller. Accepts Aspell instances.
      #
      # @param [Aspell] speller Aspell instance.
      # @raise [ArgumentError] if the speller is not an Aspell instance
      def speller=(speller)
        raise ArgumentError, 'invalid Aspell speller' unless speller.is_a? Aspell

        @speller = speller
      end

      # Aspell instance with all appropriate settings defined.
      # 
      # @return [Aspell] the prepared Aspell instance
      # 
      def speller
        @speller ||= build_speller
      end
      
      private
      
      # Convert the suggestion mode symbol to a string, and ensures the dash
      # is added to the bad-spellers mode.
      # 
      # @return [String] Aspell-friendly suggestion mode
      # 
      def actual_suggestion_mode
        suggestion_mode.to_s.gsub(/badspellers/, 'bad-spellers')
      end
      
      # Converts user-defined options to have string keys, as that's what Aspell
      # requires.
      # 
      # @return [Hash] options with string keys
      # 
      def actual_options
        options.keys.inject({}) do |hash, key|
          hash[key] = options[key].to_s
          hash
        end
      end
      
      # Generate the Aspell instance, setting the dictionary, suggestion mode
      # and options as defined in the configuration instance.
      # 
      # @return [Aspell] a new prepared Aspell instance
      # 
      def build_speller
        speller = Aspell.new(dictionary, nil, nil, "utf-8")
        speller.suggestion_mode = actual_suggestion_mode
        actual_options.each do |key, value|
          speller.set_option key, value
        end
        
        speller
      end
    end
    
    module Hooks
      # The singleton ThinkingSphinx::Raspell::Configuration instance.
      # 
      # @return [ThinkingSphinx::Raspell::Configuration] config instance
      # 
      def raspell
        ThinkingSphinx::Raspell::Configuration.instance
      end
    end
  end
end

ThinkingSphinx::Configuration.send(:include, ThinkingSphinx::Raspell::Hooks)
