# encoding: utf-8

require 'ruby_speech'

module Adhearsion
  class CallController
    module Record
      RecordError = Class.new StandardError # Represents failure to record such as when a file cannot be written.

      #
      # Handle a recording
      #
      class Recorder
        attr_accessor :record_component, :stopper_component

        #
        # @param [Adhearsion::CallController] controller on which to execute the recording
        # @param [Hash] options
        # @option options [Boolean, Optional] :async Execute asynchronously. Defaults to false
        # @option options [Boolean, Optional] :start_beep Indicates whether subsequent record will be preceded with a beep. Default is false.
        # @option options [Boolean, Optional] :start_paused Whether subsequent record will start in PAUSE mode. Default is false.
        # @option options [String, Optional] :max_duration Indicates the maximum duration (seconds) for a recording.
        # @option options [String, Optional] :format File format used during recording.
        # @option options [String, Optional] :initial_timeout Controls how long (seconds) the recognizer should wait after the end of the prompt for the caller to speak before sending a Recorder event.
        # @option options [String, Optional] :final_timeout Controls the length (seconds) of a period of silence after callers have spoken to conclude they finished.
        # @option options [Boolean, Optional] :interruptible Allows the recording to be terminated by any single DTMF key, default is false
        # @option options [Symbol, Optional] :direction Controls which sides of the conversation are recorded: :recv (what the caller hears), :send (what the caller said), or :duplex (both)
        #
        def initialize(controller, options = {})
          @controller = controller

          options = prep_options options

          @async = options.delete :async

          interruptible = options.delete :interruptible
          @stopper_component  = interruptible ? setup_stopper(interruptible) : nil
          @record_component   = Adhearsion::Rayo::Component::Record.new options
        end

        #
        # Execute the recorder
        #
        # @return nil
        #
        def run
          execute_stopper
          execute_recording
          terminate_stopper
          nil
        end

        #
        # Set a callback to be executed when recording completes
        #
        # @yield [Adhearsion::Event::Complete] the complete Event for the recording
        #
        def handle_record_completion(&block)
          @record_component.register_event_handler Adhearsion::Event::Complete, &block
        end

        private

        def setup_stopper(interrupt_digits)
          interrupt_digits = interrupt_digits == true ? '0123456789#*' : interrupt_digits.to_s
          @stopper_component = Adhearsion::Rayo::Component::Input.new :mode => :dtmf,
            :grammar => {
              :value => grammar_accept(interrupt_digits)
            }
          @stopper_component.register_event_handler Adhearsion::Event::Complete do |event|
            @record_component.stop! unless @record_component.complete?
          end
          @stopper_component
        end

        def grammar_accept(digits = '0123456789#*')
          allowed_digits = '0123456789#*'
          gram_digits = digits.chars.select { |x| allowed_digits.include? x }

          RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'inputdigits' do
            rule id: 'inputdigits', scope: 'public' do
              one_of do
                gram_digits.each { |d| item { d.to_s } }
              end
            end
          end
        end

        def execute_stopper
          @controller.write_and_await_response @stopper_component if @stopper_component
        end

        def execute_recording
          if @async
            @controller.write_and_await_response @record_component
          else
            @controller.execute_component_and_await_completion @record_component
          end
        end

        def terminate_stopper
          @stopper_component.stop! if @stopper_component && !@stopper_component.complete?
        end

        def prep_options(opts)
          opts.dup.tap do |options|
            [:max_duration, :initial_timeout, :final_timeout].each do |k|
              options[k] = options[k] * 1000 if options[k]
            end
          end
        end
      end

      #
      # Start a recording
      #
      # @example Record in a blocking way and use result
      #   record_result = record :start_beep => true, :max_duration => 60
      #   logger.info "Recording saved to #{record_result.complete_event.recording.uri}"
      # @example Asynchronous recording, execution of the controller will continue
      #   record :async => true do |event|
      #     logger.info "Async recording saved to #{event.recording.uri}"
      #   end
      #
      # @param [Hash] options
      # @option options [Boolean, Optional] :async Execute asynchronously. Defaults to false
      # @option options [Boolean, Optional] :start_beep Indicates whether subsequent record will be preceded with a beep. Default is false.
      # @option options [Boolean, Optional] :start_paused Whether subsequent record will start in PAUSE mode. Default is false.
      # @option options [String, Optional] :max_duration Indicates the maximum duration (seconds) for a recording.
      # @option options [String, Optional] :format File format used during recording.
      # @option options [String, Optional] :initial_timeout Controls how long (seconds) the recognizer should wait after the end of the prompt for the caller to speak before sending a Recorder event.
      # @option options [String, Optional] :final_timeout Controls the length (seconds) of a period of silence after callers have spoken to conclude they finished.
      # @option options [Boolean, String, Optional] :interruptible Allows the recording to be terminated by any single DTMF key, default is false
      #
      # @yield [event] Handle the recording completion event asyncronously.
      # @yieldparam [Adhearsion::Event::Complete] event the complete event for the recording
      #
      # @return Adhearsion::Rayo::Component::Record
      #
      def record(options = {})
        recorder = Recorder.new self, options

        recorder.handle_record_completion do |event|
          catching_standard_errors { yield event if block_given? }
        end

        recorder.run
        recorder.record_component
      end
    end
  end
end
