#!/usr/bin/env ruby
require 'bundler'
require 'logging'
Bundler.require

Dir['models/*.rb'].each { |file| require File.join Dir.pwd, file }

DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite://#{Dir.pwd}/development.db")
DataMapper.finalize

logger = Logging.logger['CallListener']
logger.level = :info
logger.add_appenders(
  Logging.appenders.stdout(layout: Logging.layouts.pattern(pattern: '[%d] %-5l: %m\n')),
  Logging.appenders.rolling_file('log/calls.log', age: 'daily', layout: Logging.layouts.pattern(pattern: '[%d] %-5l: %m\n'))
)

logger.info "Starting Calls"

module CallListener

  def post_init
    logger = Logging.logger['CallListener']
    Fiber.new do
      connection_headers = command('connect')
      operator_uuid = connection_headers['Caller-Unique-ID']

      command("events plain all")
      # command("filter Unique-ID #{operator_uuid}")
      command("filter Core-UUID #{operator_uuid}")
      command("filter Event-Name CHANNEL_ANSWER")
      command("filter Event-Name CHANNEL_HANGUP")

      rack_sid = connection_headers['Channel-Destination-Number'][8..-1]

      rack_session = Rack::Session::Redis.new(nil)
      rack = rack_session.get_session({}, rack_sid).last

      user = Identity.get rack['user_id']
      unless user.nil?
        company = Company.get rack['calling_to_company_id']

        targets = company.targets(status: :in_progress)
        target = targets[rand(targets.size)]
        target_contact = target.target_contacts.first(status: :not_called)
        logger.warn "#{company.name} #{target.name} has not contacts left" if target_contact.nil?

        unless target_contact.nil?

          rack['target_contact_id'] = target_contact.id
          rack_session.set_session({}, rack_sid, rack, {})

          logger.info "Bridging #{user.name} with #{target_contact.phone}"
          bridge("sofia/external/sip:#{target_contact.phone}@sip.zadarma.com")

          loop do
            @current_fiber = Fiber.current
            event_headers = Fiber.yield
            if event_headers['Content-Type'] == "text/disconnect-notice"
              break
            elsif event_headers['Event-Name'] == "CHANNEL_ANSWER"
              # in case uuid is same as operator_uuid, skip to next CHANNEL_ANSWER
              target_call_uuid = event_headers['Caller-Unique-ID']
              bridged_call operator_uuid, target_call_uuid, rack_sid, company.manager_phone
              # command "api uuid_kill #{target_call_uuid}"
              break
            elsif event_headers['Event-Name'] == "CHANNEL_HANGUP"
              break
            end
          end

        end
      end

      # command "api uuid_kill #{operator_uuid}"
      close_connection_after_writing
    end.resume
  end

  def bridged_call operator_uuid, target_call_uuid, rack_sid, manager_phone
    puts "operator_uuid #{operator_uuid}, target_call_uuid #{target_call_uuid}"
    subscriber = EM::Hiredis.connect
    subscriber.subscribe "transfer.#{rack_sid}"
    subscriber.on(:message) do |channel, message|
      puts "TRANSFER MESSAGE RECV"
      puts "Bridging with #{manager_phone}"
      Fiber.new do
        manager_uuid = command("api create_uuid\nevent-lock:true")['Body']
        puts "manager_uuid #{manager_uuid}"
        command("api originate {origination_uuid=#{manager_uuid}}sofia/external/sip:#{manager_phone}@sip.zadarma.com &park() inline")

        loop do
          puts "listen"
          @current_fiber = Fiber.current
          event_headers = Fiber.yield
          break if event_headers.nil?
          if event_headers['Content-Type'] == "text/disconnect-notice"
            break
          elsif event_headers['Event-Name'] == "CHANNEL_ANSWER"
            intercept manager_uuid, "-bleg #{operator_uuid}"
          elsif event_headers['Event-Name'] == "CHANNEL_HANGUP"
            # command "api uuid_kill #{operator_uuid}"
            break
          end
        end
      end.resume

      @current_fiber.resume nil if @current_fiber && @current_fiber.alive?
    end

    loop do
      @current_fiber = Fiber.current
      event_headers = Fiber.yield
      break if event_headers.nil?
      if event_headers['Content-Type'] == "text/disconnect-notice"
        break
      elsif event_headers['Event-Name'] == "CHANNEL_HANGUP"
        break
      end
    end
  end

  def bridge phone
  command <<-BRIDGE
sendmsg
call-command: execute
execute-app-name: bridge
execute-app-arg: #{phone}\n\n
BRIDGE
  end

  def intercept inbound_uuid, outbound_uuid
  command <<-INTERCEPT
sendmsg #{inbound_uuid}
call-command: execute
execute-app-name: intercept
execute-app-arg: #{outbound_uuid}\n\n
INTERCEPT
  end

  def command data
    send_data "#{data}\n\n"

    @current_fiber = Fiber.current
    Fiber.yield
  end

  def receive_data data
    headers = data.split(/\n/).inject({}) do |hash, row|
      ri = row.index(': ')
      hash[row[0..(ri-1)]] = row[(ri+2)..-1] unless ri.nil?
      hash
    end
    content_length = Integer(headers['Content-Length']) rescue nil
    headers['Body'] = content_length ? data[(data.size-content_length)..-1] : ''
    # puts "RECV: [#{headers['Event-Name']}] [#{headers['Content-Type']}]"
    # puts "BODY: [#{headers['Body'][0..100]}]" rescue nil
    @current_fiber.resume headers if @current_fiber && @current_fiber.alive?
  end

  # def send_data data
  #   puts "SNDING: #{data}"
  #   super
  # end
end

begin
  EM.run do
    EventMachine.start_server '127.0.0.1', 8084, CallListener
  end
rescue StandardError => e
  logger.error ["Unhandled exception in EM loop, restarting", e.message, e.backtrace].join('\n')
  retry
rescue Exception => e
  logger.fatal ["Unhandled FATAL exception in EM loop", e.message, e.backtrace].join('\n')
end
