#
# Copyright (C) 2006 SIPfoundry Inc.
# Licensed by SIPfoundry under the LGPL license.
# 
# Copyright (C) 2006 Pingtel Corp.
# Licensed to SIPfoundry under a Contributor Agreement.
#
##############################################################################

# Application requires.  Assume that the load path has been set up for us.
require 'call_resolver'
require 'configure'
require 'exceptions'


# CallDirectionPlugin computes call direction for a CDR, a customer-specific
# requirement.  We do this in a plugin to keep the core CallResolver generic.
class CallDirectionPlugin
  
public

  SIPXCONFIG_DATABASE = 'SIPXCONFIG'
  GATEWAY_TABLE_NAME = 'gateway'
  
  # Configuration parameters
  CALL_DIRECTION = 'SIP_CALLRESOLVER_CALL_DIRECTION'
  CALL_DIRECTION_DEFAULT = Configure::DISABLE
  
  # Call direction: single-char codes stored in the call_direction column.
  INCOMING = 'I'        # calls that come in from a PSTN gateway
  OUTGOING = 'O'        # calls that go out to a PSTN gateway
  INTRANETWORK = 'A'    # calls that are pure SIP and don't go through a gateway

  
  def initialize(resolver)
    @resolver = resolver

    # Gateways are stored in the SIPXCONFIG database. Connect to that database.
    # Reuse an existing open connection if one is open. The hitch is that we
    # can't simply use the SIPXCDR connection inherited from ActiveRecord::Base,
    # we need a SIPXCONFIG connection. Reuse the open connection only if it has
    # a gateway table, which indicates a connection to a SIPXCONFIG database.
    if !Gateway.connected? or
       !Gateway.connection.tables.find {|table| table == GATEWAY_TABLE_NAME}
      Gateway.establish_connection(
        :adapter  => "postgresql",
        :host     => "localhost",
        :username => "postgres",
        :database => SIPXCONFIG_DATABASE)
    end
    load_and_resolve_gateways    
  end
  
  # When CallResolver tells us that a new CDR has been created, then compute
  # call direction for that CDR.
  def update(event_type,    # Call Resolver event type
             cdr)           # New CDR, not yet saved to DB
    # The "new CDR" event is the only event type handled by this plugin
    if event_type == CallResolver::EVENT_NEW_CDR
      set_call_direction(cdr)
    else
      log.error("CallDirectionPlugin#update: unrecognized event type #{event_type}")
    end
  end
  
  # Return true if call direction is enabled, false otherwise, based on the config.
  # :TODO: Factor out the code for ENABLE/DISABLE config params into a shared method.
  def CallDirectionPlugin.call_direction?(config)
    # Look up the config param
    call_direction = config[CALL_DIRECTION]
    
    # Apply the default if the param was not specified
    call_direction ||= CALL_DIRECTION_DEFAULT

    # Convert to a boolean
    if call_direction.casecmp(Configure::ENABLE) == 0
      call_direction = true
    elsif call_direction.casecmp(Configure::DISABLE) == 0
      call_direction = false
    else
      raise(ConfigException, "Unrecognized value \"#{call_direction}\" for " +
            "#{CALL_DIRECTION}.  Must be ENABLE or DISABLE.")
    end
    
    call_direction
  end

private

  # For use by test code
  attr_writer :gateway_addresses

  # Compute and set call direction for the CDR.
  def set_call_direction(cdr)
    # Compute the call direction, based on whether the from or to contact
    # is a gateway address. At most one of them can be a gateway address.
    call_direction = INTRANETWORK
    if cdr.caller_contact and is_gateway_address(cdr.caller_contact)
      call_direction = INCOMING
    elsif cdr.callee_contact and is_gateway_address(cdr.callee_contact)
      call_direction = OUTGOING
    end
    log.debug("CallDirectionPlugin#update: CDR has call ID = #{cdr.call_id}, " +
              "caller_contact = #{cdr.caller_contact}, " +
              "callee_contact = #{cdr.callee_contact}, " +
              "call_direction = #{call_direction}")
      
    # Update the CDR's call_direction
    cdr.call_direction = call_direction
  end

  def is_gateway_address(contact)
    contact_addr = Utils.contact_host(contact)
    @gateway_addresses.any? {|g| g == contact_addr}
  end
  
  def load_and_resolve_gateways
    # Find the gateways.  For gateways configured with domain names, resolve the
    # names to IP addresses so we have a canonical format for address matching.
    # Return array of resolved IP addresses.
    @gateways = Gateway.find(:all)
    @gateway_addresses = []
    gateway = nil

    # Build a gateway IP list
    @gateways.each do |g|
      gateway = g
      ip_addresses = g.ip_addresses
      if ip_addresses.length == 0
        log.error("Unable to resolve the domain name \"#{gateway.address}\" for the " +
        "gateway named \"#{gateway.name}\".  This gateway will not be used " +
        "when computing call direction.")
      else
        @gateway_addresses.concat(ip_addresses)
        log.debug do   
          # For debugging, list the gateways with their addresses.  If the gateway
          # has a domain name as its address, then list both the domain name and
          # the associated IP address, otherwise just list the IP address.            
          str = "  name = #{g.name}, "
          address = g.address
          ip_address = ip_addresses
          if address and (address != ip_addresses.to_s)
            str += "domain name = \"#{address}\", "
          else
            str += "domain name is unknown, "
          end
          addr_str = ''
          ip_addresses.each {|a| addr_str += "#{a} "}
          str += "IP address(es) = #{addr_str}"
          str     
        end
      end
    end
    log.debug("Found #{@gateway_addresses.length} gateways for computing call direction.")    
  end

  # Use the Call Resolver's Logger
  def log
    @resolver.log
  end
  
end


syntax highlighted by Code2HTML, v. 0.9.1