# encoding: utf-8

require 'adhearsion/call_controller/input/ask_grammar_builder'
require 'adhearsion/call_controller/input/prompt_builder'
require 'adhearsion/call_controller/input/menu_builder'

module Adhearsion
  class CallController
    module Input

      InputError = Class.new Adhearsion::Error

      #
      # Prompts for input, handling playback of prompts, DTMF grammar construction, and execution
      #
      # @example A basic DTMF digit collection:
      #   ask "Welcome, ", "/opt/sounds/menu-prompt.mp3",
      #       timeout: 10, terminator: '#', limit: 3
      #
      # The first arguments will be a list of sounds to play, as accepted by #play, including strings for TTS, Date and Time objects, and file paths.
      # :timeout, :terminator and :limit options may be specified to automatically construct a grammar, or grammars may be manually specified.
      #
      # @param [Object, Array<Object>] args A list of outputs to play, as accepted by #play
      # @param [Hash] options Options to modify the grammar
      # @option options [Boolean] :interruptible If the prompt should be interruptible or not. Defaults to true
      # @option options [Integer] :limit Digit limit (causes collection to cease after a specified number of digits have been collected)
      # @option options [Integer] :timeout Timeout in seconds before the first and between each input digit
      # @option options [String] :terminator Digit to terminate input
      # @option options [RubySpeech::GRXML::Grammar, Array<RubySpeech::GRXML::Grammar>] :grammar One of a collection of grammars to execute
      # @option options [String, Array<String>] :grammar_url One of a collection of URLs for grammars to execute
      # @option options [Hash] :input_options A hash of options passed directly to the Input constructor
      # @option options [Hash] :output_options A hash of options passed directly to the Output constructor
      #
      # @return [Result] a result object from which the details of the utterance may be established
      #
      # @see Output#play
      # @see Adhearsion::Rayo::Component::Input.new
      # @see Adhearsion::Rayo::Component::Output.new
      #
      def ask(*args)
        options = args.last.kind_of?(Hash) ? args.pop : {}
        prompts = args.flatten.compact

        options[:grammar] || options[:grammar_url] || options[:limit] || options[:terminator] || raise(ArgumentError, "You must specify at least one of limit, terminator or grammar")

        grammars = AskGrammarBuilder.new(options).grammars

        output_document = prompts.empty? ? nil : output_formatter.ssml_for_collection(prompts)

        PromptBuilder.new(output_document, grammars, options).execute self
      end

      # Creates and manages a multiple choice menu driven by DTMF, handling playback of prompts,
      # invalid input, retries and timeouts, and final failures.
      #
      # @example A complete example of the method is as follows:
      #   menu "Welcome, ", "/opt/sounds/menu-prompt.mp3", tries: 2, timeout: 10 do
      #     match 1, OperatorController
      #
      #     match 10..19 do
      #       pass DirectController
      #     end
      #
      #     match 5, 6, 9 do |exten|
      #       play "The #{exten} extension is currently not active"
      #     end
      #
      #     match '7', OfficeController
      #
      #     invalid { play "Please choose a valid extension" }
      #     timeout { play "Input timed out, try again." }
      #     failure { pass OperatorController }
      #   end
      #
      # The first arguments will be a list of sounds to play, as accepted by #play, including strings for TTS, Date and Time objects, and file paths.
      # :tries and :timeout options respectively specify the number of tries before going into failure, and the timeout in seconds allowed on each digit input.
      # The most important part is the following block, which specifies how the menu will be constructed and handled.
      #
      # #match handles connecting an input pattern to a payload.
      # The pattern can be one or more of: an integer, a Range, a string, an Array of the possible single types.
      # Input is matched against patterns, and the first exact match has it's payload executed.
      # Matched input is passed in to the associated block, or to the controller through #options.
      #
      # Allowed payloads are the name of a controller class, in which case it is executed through its #run method, or a block, which is executed in the context of the current controller.
      #
      # #invalid has its associated block executed when the input does not possibly match any pattern.
      # #timeout's block is run when timeout expires before receiving any input
      # #failure runs its block when the maximum number of tries is reached without an input match.
      #
      # Execution of the current context resumes after #menu finishes. If you wish to jump to an entirely different controller, use #pass.
      # Menu will return :failed if failure was reached, or :done if a match was executed.
      #
      # @param [Object] args A list of outputs to play, as accepted by #play
      # @param [Hash] options Options to use for the menu
      # @option options [Integer] :tries Number of tries allowed before failure
      # @option options [Integer] :timeout Timeout in seconds before the first and between each input digit
      # @option options [Boolean] :interruptible If the prompt should be interruptible or not. Defaults to true
      # @option options [String, Symbol] :mode Input mode to accept. May be :voice or :dtmf.
      # @option options [Hash] :input_options A hash of options passed directly to the Input constructor
      # @option options [Hash] :output_options A hash of options passed directly to the Output constructor
      #
      # @return [Result] a result object from which the details of the utterance may be established
      #
      # @see Output#play
      # @see CallController#pass
      #
      def menu(*args, &block)
        raise ArgumentError, "You must specify a block to build the menu" unless block
        options = args.last.kind_of?(Hash) ? args.pop : {}
        prompts = args.flatten.compact

        menu_builder = MenuBuilder.new(options, &block)

        output_document = prompts.empty? ? nil : output_formatter.ssml_for_collection(prompts)

        menu_builder.execute output_document, self
      end
    end
  end
end
