require 'md5'
require 'thread'

module AGI
  class Error < Exception; end
  class Hangup < Exception; end
  class Digit < Exception; end
  class Timeout < Exception; end

  ENV = {}
  @sequence = 0
  @digit_queue = []

  @stdin_mutex = Mutex.new
  @stdin_queue = Queue.new
  @stdout_mutex = Mutex.new

  def self.flush_digits
    @digit_queue = []
  end

  def self.queue_digit(digit)
    digit=digit[0] if String === digit 
    @digit_queue << digit unless digit == 0
  end

  def self.cmd_digit(command)
    if @digit_queue.empty?
      cmd(command)
    else
      $stderr.puts "Unqueued digit"
      ["",@digit_queue.shift]
    end
  end

  def self.wait_for_digits_thread
    Thread.new do
      loop do
	@stdout_mutex.synchronize do
	  $stdout.puts("WAIT FOR DIGIT -1")
	end
	@stdin_mutex.synchronize do
	  begin
	    response = $stdin.gets
	  rescue Errno::EPIPE
	    response = nil
	  end
	  @stdin_queue << response
	end
      end
    end
  end

  def self.read_input
    if @stdin_queue.length > 0 then
      result = @stdin_queue.pop(true)
    else
      result = $stdin.gets
    end
    #$stderr.puts "Read #{result.inspect}"
    result
  end

  def self.cmd(command)
    $stderr.puts("Command #{command.inspect}")
    begin
      @sequence += 1
      @stdout_mutex.synchronize do
	# This will wake up the thread, and the response
	# will get queued
	$stdout.puts("SET VARIABLE ruby_agi_seq #{@sequence}")
	$stdout.puts("GET VARIABLE ruby_agi_seq")
	@stdin_mutex.synchronize do
	  # Read until we get the response for our sequence number
	  while response = read_input
	    case response
	    when /^HANGUP/, nil
	      raise Hangup 
	    when /^200 result=1 \(#{@sequence}\)/
	      break
	    when /^200 result=(-?\d+)/
	      # May be a potential digit
	      digit = $1.to_i
	      case digit
	      when (?0..?9), ?#, ?*, (?A..?D)
		$stderr.puts("Queued digit #{digit}")
		@digit_queue << digit
	      end
	    end
	  end

	  raise Hangup unless response
	  $stdout.puts(command)
	  response = $stdin.gets
	  case response
	  when /^HANGUP/, nil 
	    raise Hangup
	  when /^200 result=(-?\d+)/
	    return [response, $1.to_i]
	  else
	    raise Error, response.inspect + " " + command.inspect
	  end
	end
      end
    rescue Errno::EPIPE
      raise Hangup
    end
  end

  def self.say(text,digits=nil,options={})
    if @digit_queue.length > 0 then
      return ["",@digit_queue.shift]
    end
    name = "ruby-agi-say-#{MD5.new(text)}"
    tf = "/sw/asterisk/var/lib/asterisk/sounds/#{name}.wav"
    unless File.exists?(tf)
      IO.popen("text2wave -F 8000 -scale 2 -o #{tf} /dev/stdin >/dev/null 2>&1","w") do |fp|
	fp.puts(text)
      end
    end
    begin
      digits = "0123456789" if digits.nil?
      digits = '""' if digits == ""
      response, digit = cmd_digit("STREAM FILE #{name} #{digits}")
      if options[:raise_digit] then
	raise Digit,digit
      end
      return [response, digit]
    ensure
      #File.unlink(tf) unless options[:static]
    end
  end

  def self.queue_say(text,digits=nil,options={})
    @say_queue << [text,digits,options]
  end

  def self.map_digit(digit)
    case digit
    when 0
      raise Timeout
    when -1
      "failure"
    else
      digit.chr
    end
  end

  def self.goto(obj,selector)
    @obj = obj
    @selector = selector
  end

  def self.hangup
    self.cmd("HANGUP")
  end

  def self.wait_for_digit(where="unspec")
    while args = @say_queue.shift
      response,digit = self.say(*args)
      $stderr.puts "Response is #{digit.inspect} to #{where}"
      if digit > 0 then
	return digit
      end
    end

    response, digit = self.cmd_digit("WAIT FOR DIGIT #{(@obj.timeout*1000).to_i}")
    raise Timeout if digit == 0
    $stderr.puts "Response is #{digit.inspect} to #{where}"
    return digit
  end


  def self.run(obj,selector=:start)
    @obj = obj
    @selector = "exten_#{selector}"
    while @selector
      @say_queue = []
      $stderr.puts "Entering selector #{@selector}"
      selector = @selector
      @selector = nil
      @obj.__send__(selector)
      next if @selector
      begin
	digit = wait_for_digit("run")
	$stderr.puts "Got digit #{digit.inspect}"
	target = "exten_#{map_digit(digit)}"
      rescue Timeout
	target = "exten_timeout"
      end
      if @obj.respond_to?(target) then
	@selector = target
      else
	@selector = "exten_invalid"
      end
      $stderr.puts "Selector is now #{@selector}"
    end
  end

  class Context
    def initialize
      @timeout = 4
    end

    attr_accessor :timeout

    def exten_invalid
      exten_hangup
    end

    def exten_timeout
      exten_hangup
    end

    def exten_hangup
      AGI.hangup
      exit
    end

    def goto(exten)
      AGI.goto(self,"exten_#{exten}")
    end
  end

  def self.initialize
    #$stderr.puts "Reading env lines"
    while line = $stdin.gets
      break if line.length<=1
      if line =~ /^agi_(\w+):\s+(.*)$/ then
	AGI::ENV[$1] = $2
      end
    end
    $stdout.sync = true
  end

end



