/*
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005-2012, Anthony Minessale II <anthm@freeswitch.org>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthm@freeswitch.org>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Karl Anderson <karl@2600hz.com>
 * Darren Schreiber <darren@2600hz.com>
 *
 *
 * mod_kazoo.c -- Socket Controlled Event Handler
 *
 */
#include "mod_kazoo.h"

#define KAZOO_DESC "kazoo information"
#define KAZOO_SYNTAX "<command> [<args>]"

#define API_COMMAND_DISCONNECT 0
#define API_COMMAND_REMOTE_IP 1
#define API_COMMAND_STREAMS 2
#define API_COMMAND_BINDINGS 3
#define API_COMMAND_OPTION 4

#define API_NODE_OPTION_FRAMING 0
#define API_NODE_OPTION_KEEPALIVE 1
#define API_NODE_OPTION_LEGACY 2
#define API_NODE_OPTION_MAX 99

static const char *node_runtime_options[] = {
		"event-stream-framing",
		"event-stream-keepalive",
		"enable-legacy",
		NULL
};

static int api_find_node_option(char *option) {
	int i;
	for(i = 0; node_runtime_options[i] != NULL; i++) {
		if(!strcasecmp(option, node_runtime_options[i])) {
			return i;
		}
	}
	return API_NODE_OPTION_MAX;
}

static switch_status_t api_get_node_option(ei_node_t *ei_node, switch_stream_handle_t *stream, char *arg) {
	int option = api_find_node_option(arg);
	switch_status_t ret = SWITCH_STATUS_SUCCESS;
	switch (option) {
	case API_NODE_OPTION_FRAMING:
		stream->write_function(stream, "+OK %i", ei_node->event_stream_framing);
		break;

	case API_NODE_OPTION_KEEPALIVE:
		stream->write_function(stream, "+OK %i", ei_node->event_stream_keepalive);
		break;

	case API_NODE_OPTION_LEGACY:
		stream->write_function(stream, "+OK %s", ei_node->legacy ? "true" : "false");
		break;

	default:
		stream->write_function(stream, "-ERR invalid option %s", arg);
		ret = SWITCH_STATUS_NOTFOUND;
		break;
	}

	return ret;
}

static switch_status_t api_set_node_option(ei_node_t *ei_node, switch_stream_handle_t *stream, char *name, char *value) {
	int option = api_find_node_option(name);
	short val;
	switch_status_t ret = SWITCH_STATUS_SUCCESS;
	switch (option) {
	case API_NODE_OPTION_FRAMING:
		val = atoi(value);
		if (val != 1 && val != 2 && val != 4) {
			stream->write_function(stream, "-ERR Invalid event stream framing value (%i)", val);
			ret = SWITCH_STATUS_GENERR;
		} else {
			stream->write_function(stream, "+OK %i", val);
			ei_node->event_stream_framing = val;
		}
		break;

	case API_NODE_OPTION_KEEPALIVE:
		val = switch_true(value);
		stream->write_function(stream, "+OK %i", val);
		ei_node->event_stream_keepalive = val;
		break;

	case API_NODE_OPTION_LEGACY:
		ei_node->legacy = switch_true(value);
		stream->write_function(stream, "+OK %s", ei_node->legacy ? "true" : "false");
		break;

	default:
		stream->write_function(stream, "-ERR invalid option %s", name);
		ret = SWITCH_STATUS_NOTFOUND;
		break;
	}

	return ret;
}

static switch_status_t api_erlang_status(switch_stream_handle_t *stream) {
	switch_sockaddr_t *sa;
	uint16_t port;
	char ipbuf[48];
	const char *ip_addr;
	ei_node_t *ei_node;

	switch_socket_addr_get(&sa, SWITCH_FALSE, kazoo_globals.acceptor);

	port = switch_sockaddr_get_port(sa);
	ip_addr = switch_get_addr(ipbuf, sizeof (ipbuf), sa);

	stream->write_function(stream, "Running %s\n", VERSION);
	stream->write_function(stream, "Listening for new Erlang connections on %s:%u with cookie %s\n", ip_addr, port, kazoo_globals.ei_cookie);
	stream->write_function(stream, "Registered as Erlang node %s, visible as %s\n", kazoo_globals.ei_cnode.thisnodename, kazoo_globals.ei_cnode.thisalivename);

	if (kazoo_globals.ei_compat_rel) {
		stream->write_function(stream, "Using Erlang compatibility mode: %d\n", kazoo_globals.ei_compat_rel);
	}

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	if (!ei_node) {
		stream->write_function(stream, "No erlang nodes connected\n");
	} else {
		stream->write_function(stream, "Connected to:\n");
		while(ei_node != NULL) {
			unsigned int year, day, hour, min, sec, delta;

			delta = (switch_micro_time_now() - ei_node->created_time) / 1000000;
			sec = delta % 60;
			min = delta / 60 % 60;
			hour = delta / 3600 % 24;
			day = delta / 86400 % 7;
			year = delta / 31556926 % 12;
			stream->write_function(stream, "  %s (%s:%d) up %d years, %d days, %d hours, %d minutes, %d seconds\n"
								   ,ei_node->peer_nodename, ei_node->remote_ip, ei_node->remote_port, year, day, hour, min, sec);
			ei_node = ei_node->next;
		}
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t api_erlang_event_filter(switch_stream_handle_t *stream) {
	switch_hash_index_t *hi = NULL;
	int column = 0;
	int idx = 0;

	for (hi = (switch_hash_index_t *)switch_core_hash_first_iter(kazoo_globals.event_filter, hi); hi; hi = switch_core_hash_next(&hi)) {
		const void *key;
		void *val;
		switch_core_hash_this(hi, &key, NULL, &val);
		stream->write_function(stream, "%-50s", (char *)key);
		if (++column > 2) {
			stream->write_function(stream, "\n");
			column = 0;
		}
	}

	if (++column > 2) {
		stream->write_function(stream, "\n");
	}

	while(kazoo_globals.kazoo_var_prefixes[idx] != NULL) {
		char var[100];
		char *prefix = kazoo_globals.kazoo_var_prefixes[idx];
		sprintf(var, "%s*", prefix);
		stream->write_function(stream, "%-50s", var);
		idx++;
	}


	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t api_erlang_nodes_list(switch_stream_handle_t *stream) {
	ei_node_t *ei_node;

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	while(ei_node != NULL) {
		stream->write_function(stream, "%s (%s)\n", ei_node->peer_nodename, ei_node->remote_ip);
		ei_node = ei_node->next;
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t api_erlang_nodes_count(switch_stream_handle_t *stream) {
	ei_node_t *ei_node;
	int count = 0;

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	while(ei_node != NULL) {
		count++;
		ei_node = ei_node->next;
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	stream->write_function(stream, "%d\n", count);

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t api_complete_erlang_node(const char *line, const char *cursor, switch_console_callback_match_t **matches) {
	switch_console_callback_match_t *my_matches = NULL;
	switch_status_t status = SWITCH_STATUS_FALSE;
	ei_node_t *ei_node;

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	while(ei_node != NULL) {
		switch_console_push_match(&my_matches, ei_node->peer_nodename);
		ei_node = ei_node->next;
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	if (my_matches) {
		*matches = my_matches;
		status = SWITCH_STATUS_SUCCESS;
	}

	return status;
}

static switch_status_t handle_node_api_event_stream(ei_event_stream_t *event_stream, switch_stream_handle_t *stream) {
	ei_event_binding_t *binding;
	int column = 0;

	switch_mutex_lock(event_stream->socket_mutex);
	if (event_stream->connected == SWITCH_FALSE) {
		switch_sockaddr_t *sa;
		uint16_t port;
		char ipbuf[48] = {0};
		const char *ip_addr;

		switch_socket_addr_get(&sa, SWITCH_TRUE, event_stream->acceptor);
		port = switch_sockaddr_get_port(sa);
		ip_addr = switch_get_addr(ipbuf, sizeof (ipbuf), sa);

		if (zstr(ip_addr)) {
			ip_addr = kazoo_globals.ip;
		}

		stream->write_function(stream, "%s:%d -> disconnected\n"
							   ,ip_addr, port);
	} else {
		unsigned int year, day, hour, min, sec, delta;

		delta = (switch_micro_time_now() - event_stream->connected_time) / 1000000;
		sec = delta % 60;
		min = delta / 60 % 60;
		hour = delta / 3600 % 24;
		day = delta / 86400 % 7;
		year = delta / 31556926 % 12;

		stream->write_function(stream, "%s:%d -> %s:%d for %d years, %d days, %d hours, %d minutes, %d seconds\n"
							   ,event_stream->local_ip, event_stream->local_port
							   ,event_stream->remote_ip, event_stream->remote_port
							   ,year, day, hour, min, sec);
	}

	binding = event_stream->bindings;
	while(binding != NULL) {
		if (binding->type == SWITCH_EVENT_CUSTOM) {
			stream->write_function(stream, "CUSTOM %-43s", binding->subclass_name);
		} else {
			stream->write_function(stream, "%-50s", switch_event_name(binding->type));
		}

		if (++column > 2) {
			stream->write_function(stream, "\n");
			column = 0;
		}

		binding = binding->next;
	}
	switch_mutex_unlock(event_stream->socket_mutex);

	if (!column) {
		stream->write_function(stream, "\n");
	} else {
		stream->write_function(stream, "\n\n");
	}

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t handle_node_api_event_streams(ei_node_t *ei_node, switch_stream_handle_t *stream) {
	ei_event_stream_t *event_stream;

	switch_mutex_lock(ei_node->event_streams_mutex);
	event_stream = ei_node->event_streams;
	while(event_stream != NULL) {
		handle_node_api_event_stream(event_stream, stream);
		event_stream = event_stream->next;
	}
	switch_mutex_unlock(ei_node->event_streams_mutex);

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t handle_node_api_command(ei_node_t *ei_node, switch_stream_handle_t *stream, uint32_t command) {
	unsigned int year, day, hour, min, sec, delta;

	switch (command) {
	case API_COMMAND_DISCONNECT:
		stream->write_function(stream, "Disconnecting erlang node %s at managers request\n", ei_node->peer_nodename);
		switch_clear_flag(ei_node, LFLAG_RUNNING);
		break;
	case API_COMMAND_REMOTE_IP:
		delta = (switch_micro_time_now() - ei_node->created_time) / 1000000;
		sec = delta % 60;
		min = delta / 60 % 60;
		hour = delta / 3600 % 24;
		day = delta / 86400 % 7;
		year = delta / 31556926 % 12;

		stream->write_function(stream, "Uptime           %d years, %d days, %d hours, %d minutes, %d seconds\n", year, day, hour, min, sec);
		stream->write_function(stream, "Local Address    %s:%d\n", ei_node->local_ip, ei_node->local_port);
		stream->write_function(stream, "Remote Address   %s:%d\n", ei_node->remote_ip, ei_node->remote_port);
		break;
	case API_COMMAND_STREAMS:
		handle_node_api_event_streams(ei_node, stream);
		break;
	case API_COMMAND_BINDINGS:
		handle_api_command_streams(ei_node, stream);
		break;
	default:
		break;
	}

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t api_erlang_node_command(switch_stream_handle_t *stream, const char *nodename, uint32_t command) {
	ei_node_t *ei_node;

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	while(ei_node != NULL) {
		int length = strlen(ei_node->peer_nodename);

		if (!strncmp(ei_node->peer_nodename, nodename, length)) {
			handle_node_api_command(ei_node, stream, command);
			switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);
			return SWITCH_STATUS_SUCCESS;
		}

		ei_node = ei_node->next;
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	return SWITCH_STATUS_NOTFOUND;
}

static switch_status_t handle_node_api_command_arg(ei_node_t *ei_node, switch_stream_handle_t *stream, uint32_t command, char *arg) {

	switch (command) {
	case API_COMMAND_OPTION:
		return api_get_node_option(ei_node, stream, arg);
		break;
	default:
		break;
	}

	return SWITCH_STATUS_NOTFOUND;
}

static switch_status_t handle_node_api_command_args(ei_node_t *ei_node, switch_stream_handle_t *stream, uint32_t command, int argc, char *argv[]) {

	switch (command) {
	case API_COMMAND_OPTION:
		return api_set_node_option(ei_node, stream, argv[0], argv[1]);
		break;
	default:
		break;
	}

	return SWITCH_STATUS_NOTFOUND;
}

static switch_status_t api_erlang_node_command_arg(switch_stream_handle_t *stream, const char *nodename, uint32_t command, char *arg) {
	ei_node_t *ei_node;
	switch_status_t ret = SWITCH_STATUS_NOTFOUND;

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	while(ei_node != NULL) {
		int length = strlen(ei_node->peer_nodename);

		if (!strncmp(ei_node->peer_nodename, nodename, length)) {
			ret = handle_node_api_command_arg(ei_node, stream, command, arg);
			switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);
			return ret ;
		}

		ei_node = ei_node->next;
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	return ret;
}

static switch_status_t api_erlang_node_command_args(switch_stream_handle_t *stream, const char *nodename, uint32_t command, int argc, char *argv[]) {
	ei_node_t *ei_node;
	switch_status_t ret = SWITCH_STATUS_NOTFOUND;

	switch_thread_rwlock_rdlock(kazoo_globals.ei_nodes_lock);
	ei_node = kazoo_globals.ei_nodes;
	while(ei_node != NULL) {
		int length = strlen(ei_node->peer_nodename);

		if (!strncmp(ei_node->peer_nodename, nodename, length)) {
			ret = handle_node_api_command_args(ei_node, stream, command, argc, argv);
			switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);
			return ret;
		}

		ei_node = ei_node->next;
	}
	switch_thread_rwlock_unlock(kazoo_globals.ei_nodes_lock);

	return ret;
}

SWITCH_STANDARD_API(exec_api_cmd)
{
	char *argv[1024] = { 0 };
	int unknown_command = 1, argc = 0;
	char *mycmd = NULL;

	const char *usage_string = "USAGE:\n"
		"--------------------------------------------------------------------------------------------------------------------\n"
		"erlang status                            - provides an overview of the current status\n"
		"erlang event_filter                      - lists the event headers that will be sent to Erlang nodes\n"
		"erlang nodes list                        - lists connected Erlang nodes (usefull for monitoring tools)\n"
		"erlang nodes count                       - provides a count of connected Erlang nodes (usefull for monitoring tools)\n"
		"erlang node <node_name> disconnect       - disconnects an Erlang node\n"
		"erlang node <node_name> connection       - Shows the connection info\n"
		"erlang node <node_name> event_streams    - lists the event streams for an Erlang node\n"
		"erlang node <node_name> fetch_bindings   - lists the XML fetch bindings for an Erlang node\n"
		"---------------------------------------------------------------------------------------------------------------------\n";

	if (zstr(cmd)) {
		stream->write_function(stream, "%s", usage_string);
		return SWITCH_STATUS_SUCCESS;
	}

	if (!(mycmd = strdup(cmd))) {
		return SWITCH_STATUS_MEMERR;
	}

	if (!(argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
		stream->write_function(stream, "%s", usage_string);
		switch_safe_free(mycmd);
		return SWITCH_STATUS_SUCCESS;
	}

	if (zstr(argv[0])) {
		stream->write_function(stream, "%s", usage_string);
		switch_safe_free(mycmd);
		return SWITCH_STATUS_SUCCESS;
	}

	if (!strncmp(argv[0], "status", 7)) {
		unknown_command = 0;
		api_erlang_status(stream);
	} else if (!strncmp(argv[0], "event_filter", 13)) {
		unknown_command = 0;
		api_erlang_event_filter(stream);
	} else if (!strncmp(argv[0], "nodes", 6) && !zstr(argv[1])) {
		if (!strncmp(argv[1], "list", 6)) {
			unknown_command = 0;
			api_erlang_nodes_list(stream);
		} else if (!strncmp(argv[1], "count", 6)) {
			unknown_command = 0;
			api_erlang_nodes_count(stream);
		}
	} else if (!strncmp(argv[0], "node", 6) && !zstr(argv[1]) && !zstr(argv[2])) {
		if (!strncmp(argv[2], "disconnect", 11)) {
			unknown_command = 0;
			api_erlang_node_command(stream, argv[1], API_COMMAND_DISCONNECT);
		} else if (!strncmp(argv[2], "connection", 11)) {
			unknown_command = 0;
			api_erlang_node_command(stream, argv[1], API_COMMAND_REMOTE_IP);
		} else if (!strncmp(argv[2], "event_streams", 14)) {
			unknown_command = 0;
			api_erlang_node_command(stream, argv[1], API_COMMAND_STREAMS);
		} else if (!strncmp(argv[2], "fetch_bindings", 15)) {
			unknown_command = 0;
			api_erlang_node_command(stream, argv[1], API_COMMAND_BINDINGS);
		} else if (!strncmp(argv[2], "option", 7) && !zstr(argv[3])) {
			unknown_command = 0;
			if(argc > 4 && !zstr(argv[4]))
				api_erlang_node_command_args(stream, argv[1], API_COMMAND_OPTION, argc - 3, &argv[3]);
			else
				api_erlang_node_command_arg(stream, argv[1], API_COMMAND_OPTION, argv[3]);
		}
	}

	if (unknown_command) {
		stream->write_function(stream, "%s", usage_string);
	}

	switch_safe_free(mycmd);
	return SWITCH_STATUS_SUCCESS;
}

void add_cli_api(switch_loadable_module_interface_t **module_interface)
{
	switch_api_interface_t *api_interface = NULL;
	SWITCH_ADD_API(api_interface, "erlang", KAZOO_DESC, exec_api_cmd, KAZOO_SYNTAX);
	switch_console_set_complete("add erlang status");
	switch_console_set_complete("add erlang event_filter");
	switch_console_set_complete("add erlang nodes list");
	switch_console_set_complete("add erlang nodes count");
	switch_console_set_complete("add erlang node ::erlang::node disconnect");
	switch_console_set_complete("add erlang node ::erlang::node connection");
	switch_console_set_complete("add erlang node ::erlang::node event_streams");
	switch_console_set_complete("add erlang node ::erlang::node fetch_bindings");
	switch_console_add_complete_func("::erlang::node", api_complete_erlang_node);

}

void remove_cli_api()
{
	switch_console_set_complete("del erlang");
	switch_console_del_complete_func("::erlang::node");

}


/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4:
 */
