/*
 * Asterisk -- A telephony toolkit for Linux.
 *
 * Distribute incoming calls to an agent pool.
 * 
 * Copyright (C) 2002, James Sharp
 *
 * James Sharp <jsharp@psychoses.org>
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License
 */
 
#include <asterisk/lock.h>
#include <asterisk/file.h>
#include <asterisk/frame.h>
#include <asterisk/logger.h>
#include <asterisk/channel.h>
#include <asterisk/pbx.h>
#include <asterisk/module.h>
#include <asterisk/translate.h>
#include <asterisk/config.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include <netinet/in.h>


static char *tdesc = "Automatic call distribution system";

static char *app = "ACD";

static char *synopsis = "Distribute an incoming call to a group of Asterisk devices";
static char *descrip = "Takes an incoming call and connects it to an Asterisk channel in a group of channels based on either \"Next available\" or \"Most idle\"";



#define ACD_MAX_GROUPS 16
#define ACD_MAX_AGENTS_PERGROUP 48
#define ACD_CONFIG "acd.conf"

STANDARD_LOCAL_USER;

LOCAL_USER_DECL;

static int acd_connect_to_acd_host(struct ast_config *);
static int route_by_nextavail(struct ast_channel *, int);
static int route_by_mostidle(struct ast_channel *, int);

struct acd_agent {
  char *agent_device;
  time_t last_call_time;
};

struct acd_agent_grouping {
  struct acd_agent agent[ACD_MAX_AGENTS_PERGROUP];
  char *group_name;
  int defined_agents;
  char *routing_type;
};


struct acd_agent_grouping acd_grouping[ACD_MAX_GROUPS];
static int acd_groups_defined;
static char *acdgroups[ACD_MAX_GROUPS];


static int acd_exec(struct ast_channel *chan, void *data)
{
  int dest_group, ret;

  dest_group = atoi((char *)data);
  ast_log(LOG_DEBUG,"Executing ACD acd_exec.  Source channel %s.  Destination group %i\n",chan->name,dest_group);

  /* Okay.  Now we figure out what channel we need to route this call down to. */
  /* One of two ways..."Next available" sends it down to the next non-busy channel */
  /* "Most Idle" sends it down the channel for the agent that has been idle (non-busy) longest */
  
  /* This is the "Next available" code */


  if (!strcasecmp(acd_grouping[dest_group].routing_type,"nextavail"))
    ret = route_by_nextavail(chan, dest_group);
  
  /*  if (!strcasecmp(acd_grouping[dest_group].routing_type,"mostidle"))
      ret = route_by_mostidle(chan, dest_group);
  */
  return 0;
}

static int route_by_nextavail(struct ast_channel *chan, int dest_group) 
{

  struct ast_channel *destchan;
  struct ast_frame *f;
  int i, res;
  char *tech, *number;
  time_t t;

  /* Go through the list of agents in the destination group.  Do an ast_request on each of them, if it returns NULL then the channel is definitely busy. */
  /* If ast_request returns a channel, then it is probably not busy...but we'll have to check for that since it could possibly be busy */


  while (chan->state == AST_STATE_UP) {
    
    for (i=0; i<acd_grouping[dest_group].defined_agents; i++) {
      tech = strdup(acd_grouping[dest_group].agent[i].agent_device);
      ast_log(LOG_DEBUG,"ast_request for %s\n",tech);    
      number = strchr(tech,'/');
      *number = '\0';
      number++;
      
      
      
      destchan = ast_request(tech, chan->nativeformats, number);
      
      if (destchan == NULL) {
	ast_log(LOG_DEBUG,"ast_request for %s/%s returned NULL.  Probably busy.\n",tech,number);
	free(tech);
	continue;
      }
      
      /* ast_request has given us a destination channel.  Attempt a call down it. */
      
      res = ast_call(destchan, number, 0);
      
      if (res) {
	/* If it returns an error, log it and go on to the next one */
	ast_log(LOG_DEBUG, "ast call on acd peer returned %d\n", res);
	ast_hangup(destchan);
	continue;
      }
      f = ast_read(destchan);
      
      if (f->frametype == AST_FRAME_CONTROL) {
	switch(f->subclass) {
	case AST_CONTROL_ANSWER:
	case AST_CONTROL_RINGING:
	  /* A valid channel.  Connect the two */
	  time(&t);
	  acd_grouping[dest_group].agent[i].last_call_time = t;
	  res = ast_channel_make_compatible(chan, destchan);
	  if (res < 0) {
	    ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, destchan->name);
	    ast_hangup(destchan);
	    return -1;
	  }
	  ast_bridge_call(chan,destchan,0);
	  ast_hangup(destchan);
	  return 0;
	  break;

	default:
	  /* Its busy or something's up.  Go onto the next one */
	  continue;
	  break;
	}
      }
      
    }   
    
    ast_streamfile(chan,"acd-agent-busy",chan->language);
    ast_safe_sleep(chan,10000);
  }

    return -1;
}
  

int load_module(void)
{
  struct ast_config *cfg;
  struct ast_variable *v;

  char *acdgroup;
  int i=0;
  int j=0;


  cfg = ast_load(ACD_CONFIG);
  if (!cfg) {
    ast_log(LOG_WARNING, "No such configuration file %s\n", ACD_CONFIG);
    return -1;
  }

  if (ast_variable_retrieve(cfg, "network", "hostname")) {
    acd_connect_to_acd_host(cfg);
  }
  
  acdgroup = ast_category_browse(cfg, NULL);
  /* Get all the ACD group names */
  while(acdgroup) {
    if (strstr(acdgroup,"acdgroup")) {
      acdgroups[i] = strdup(acdgroup);
      i++;
      ast_log(LOG_DEBUG,"Registered ACD Group name %s\n",acdgroup);
    }
    acdgroup = ast_category_browse(cfg,acdgroup);
  }
  
  acd_groups_defined = i;
  
  /* Now that we've got the group names, browse through the "agent" IDs under each group context */
  
  for (i=0;i<acd_groups_defined;i++)
    {
      j=0;
      acd_grouping[i].group_name = ast_variable_retrieve(cfg,acdgroups[i],"name");
      acd_grouping[i].routing_type = ast_variable_retrieve(cfg,acdgroups[i],"routing");
      if (acd_grouping[i].group_name == NULL)
	acd_grouping[i].group_name = strdup(acdgroups[i]);
      if (acd_grouping[i].routing_type == NULL)
	{
	  ast_log(LOG_WARNING,"ACD Group \"%s\":  No routing type defined.  Should be \"nextavail\" or \"mostidle\"\n",acd_grouping[i].group_name);
	  return -1;
	}
      if (strcasecmp(acd_grouping[i].routing_type,"nextavail") && strcasecmp(acd_grouping[i].routing_type,"mostidle"))
	{
	  ast_log(LOG_WARNING,"ACD Group \"%s\":  Routing type \"%s\".  Should be \"nextavail\" or \"mostidle\"\n",acd_grouping[i].group_name,acd_grouping[i].routing_type);
	  return -1;
	}
      

      v = ast_variable_browse(cfg,acdgroups[i]);
      while (v) {
	if (!strcasecmp(v->name,"agent"))
	  {
	    acd_grouping[i].agent[j].agent_device = strdup(v->value);
	    ast_log(LOG_DEBUG,"Registered agent %s as part of ACD Group %s using Routing Type %s\n",acd_grouping[i].agent[j].agent_device, acd_grouping[i].group_name,acd_grouping[i].routing_type);
	    j++;
	  }
	v=v->next;
      }
      acd_grouping[i].defined_agents = j;
    }  
  
  return ast_register_application(app, acd_exec, synopsis, descrip);
}

static int acd_connect_to_acd_host(struct ast_config *cfg)
{
  return 0;
}


int unload_module(void)
{
  int res;
  
  res = ast_unregister_application(app);
  return res;
}

int usecount(void)
{ 
  return 0;
}


char *key()
{
  return ASTERISK_GPL_KEY;
}


char *description(void)
{
  return tdesc;
}