/*
 * Asterisk -- A telephony toolkit for Linux.
 *
 * Pickup, channel independent call pickup
 *
 * Copyright (C) 2005, Thorsten Knabe <ast@thorsten-knabe.de>
 * 
 * Copyright (C) 2004, Junghanns.NET GmbH
 *
 * Klaus-Peter Junghanns <kpj@junghanns.net>
 *
 * Copyright (C) 2004, Florian Overkamp <florian@obsimref.com>
 *
 * 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/logger.h>
#include <asterisk/channel.h>
#include <asterisk/channel_pvt.h>
#include <asterisk/pbx.h>
#include <asterisk/module.h>
#include <asterisk/features.h>
#include <asterisk/options.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#include <pthread.h>

static char *tdesc = "PickUp2/PickDown2/Steal2 version 0.1";

static char *app = "PickUp2";
static char *synopsis = "PickUp ringing channel.";
static char *descrip = 
"  PickUp2(Technology/resource[&Technology2/resource2&...]):\n"
"Matches the list of prefixes in the parameter list against channels in\n"
"state RINGING. If a match is found the channel is picked up and 0 is\n"
"returned. If no matching channel is found -1 is returned.\n";

static char *app2 = "PickDown2";
static char *synopsis2 = "Hangup ringing channel.";
static char *descrip2 = 
"  PickDown2(Technology/resource[&Technology2/resource2&...]):\n"
"Matches the list of prefixes in the parameter list against channels in\n"
"state RINGING. If a match is found the channel is hung up and 0 is\n"
"returned. If no matching channel is found -1 is returned.\n";

static char *app3 = "Steal2";
static char *synopsis3 = "Steal a connected channel.";

static char *descrip3 = 
"  Steal2(Technology/resource[&Technology2/resource2&...]):\n"
"Matches the list of prefixes in the parameter list against channels in\n"
"state UP. If a match is found the channel is stolen and 0 is\n"
"returned. If no matching channel is found -1 is returned.\n";

STANDARD_LOCAL_USER;

LOCAL_USER_DECL;

/* Find channel matching given pattern and state, skipping our own channel.
 * Returns locked channel, which has to be unlocked using ast_mutex_unlock().
 * Returns NULL when no matching channel is found.
 */
static struct ast_channel *find_matching_channel(struct ast_channel *chan,
	void *pattern, int chanstate)
{
	struct ast_channel *cur;
	char *pat = NULL;
	char *next_pat = NULL;

	/* copy original pattern or use empty pattern if no pattern has been given*/
	if (pattern) {
		pat = alloca(strlen(pattern) + 1);
		strcpy(pat, pattern);
	} else {
		pat = "";
	}
	ast_verbose(VERBOSE_PREFIX_4 
		"find_matching_channel: pattern='%s' state=%d\n",
		(char *)pattern, chanstate);

	/* Iterate over each part of the pattern */
	while (pat) {
		/* find pattern for next iteration, terminate current pattern */
		for (next_pat = pat; *next_pat && *next_pat != '&'; next_pat++);
		if (*next_pat == '&') {
			*next_pat = 0;
			next_pat++;
		} else
			next_pat = NULL;
		/* Iterate over all channels */
		cur = ast_channel_walk_locked(NULL);
		while (cur) {
			ast_verbose(VERBOSE_PREFIX_4 
				"find_matching_channel: trying channel='%s' "
				"state=%d pattern='%s'\n",
				cur->name, cur->_state, pat);
			if ((cur != chan) && (cur->_state == chanstate) &&
					!strncmp(pat, cur->name, strlen(pat))) {
				ast_verbose(VERBOSE_PREFIX_4
						"find_matching_channel: "
						"found channel='%s'\n",
						cur->name);
				return(cur);
			}
			ast_mutex_unlock(&cur->lock);
			cur = ast_channel_walk_locked(cur);
		}
		pat = next_pat;
	}
	return(NULL);
}

static int pickup_channel(struct ast_channel *chan, void *pattern)
{
	int ret = -1;
	struct localuser *u;
	struct ast_channel *cur;
	LOCAL_USER_ADD(u);
	cur = find_matching_channel(chan, pattern, AST_STATE_RINGING);
	if (cur) {
		ast_verbose(VERBOSE_PREFIX_3 
			"Channel %s picked up ringing channel %s\n",
			chan->name, cur->name);
		if (chan->_state != AST_STATE_UP) {
			ast_answer(chan);
		}
		if (ast_channel_masquerade(cur, chan)) {
			ast_log(LOG_ERROR, "unable to masquerade\n");
		}
		ast_mutex_unlock(&cur->lock);
		ast_mutex_unlock(&chan->lock);
		ast_answer(cur);
		ret = 0;
	}
	LOCAL_USER_REMOVE(u);
	return(ret);
}

static int pickdown_channel(struct ast_channel *chan, void *pattern)
{
	int ret = -1;
	struct localuser *u;
	struct ast_channel *cur;
	LOCAL_USER_ADD(u);
	cur = find_matching_channel(chan, pattern, AST_STATE_RINGING);
	if (cur) {
                ast_verbose(VERBOSE_PREFIX_3 
			"Channel %s hung up ringing channel %s\n",
			chan->name, cur->name);
		cur->_softhangup = AST_SOFTHANGUP_DEV;
		ast_mutex_unlock(&cur->lock);
		ret = 0;
	}
	LOCAL_USER_REMOVE(u);
	return(ret);
}

static int steal_channel(struct ast_channel *chan, void *pattern)
{
	int ret = -1;
	struct localuser *u;
	struct ast_channel *cur;
	LOCAL_USER_ADD(u);
	cur = find_matching_channel(chan, pattern, AST_STATE_UP);
	if (cur) {
		ast_verbose(VERBOSE_PREFIX_3 
			"Channel %s stole channel %s\n",
			chan->name, cur->name);
		if (chan->_state != AST_STATE_UP) {
			ast_answer(chan);
		}
		if (ast_channel_masquerade(cur, chan)) {
			ast_log(LOG_ERROR, "unable to masquerade\n");
		}
		ast_mutex_unlock(&cur->lock);
		ast_mutex_unlock(&chan->lock);
		ret = 0;
	}
	LOCAL_USER_REMOVE(u);
	return(ret);
}

int unload_module(void)
{
	STANDARD_HANGUP_LOCALUSERS;
	ast_unregister_application(app3);
	ast_unregister_application(app2);
	return ast_unregister_application(app);
}

int load_module(void)
{
	ast_register_application(app3, steal_channel, synopsis3, descrip3);
	ast_register_application(app2, pickdown_channel, synopsis2, descrip2);
	return ast_register_application(app, pickup_channel, synopsis, descrip);
}

char *description(void)
{
	return tdesc;
}

int usecount(void)
{
	int res;
	STANDARD_USECOUNT(res);
	return res;
}

char *key()
{
	return ASTERISK_GPL_KEY;
}
