/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2006-2007  Nokia Corporation
 *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>

#include "logging.h"
#include "telephony.h"

static const char *chld_str = "0,1,1x,2,2x,3,4";
static char *subscriber_number = NULL;
static char *active_call_number = NULL;
static int active_call_status = 0;
static int active_call_dir = 0;

static gboolean events_enabled = FALSE;

/* Response and hold state
 * -1 = none
 *  0 = incoming call is put on hold in the AG
 *  1 = held incoming call is accepted in the AG
 *  2 = held incoming call is rejected in the AG
 */
static int response_and_hold = -1;

#define CALL_STATUS_INDEX 3
#define CALL_SETUP_INDEX 4
#define LAST_DIAL_INDEX  7
static struct indicator dummy_indicators[] =
{
	{ "battchg",	"0-5",	5 },
	{ "signal",	"0-5",	5 },
	{ "service",	"0,1",	1 },
	{ "call",	"0,1",	0 },
	{ "callsetup",	"0-3",	0 },
	{ "callheld",	"0-2",	0 },
	{ "roam",	"0,1",	0 },
	{ "last_dial",	"0,1",	0 },
	{ NULL }
};

static inline DBusMessage *invalid_args(DBusMessage *msg)
{
	return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments",
					"Invalid arguments in method call");
}

void telephony_device_connected(void *telephony_device)
{
	debug("telephony-dummy: device %p connected", telephony_device);
}

void telephony_device_disconnected(void *telephony_device)
{
	debug("telephony-dummy: device %p disconnected", telephony_device);
	events_enabled = FALSE;
}

void telephony_event_reporting_req(void *telephony_device, int ind)
{
	events_enabled = ind == 1 ? TRUE : FALSE;

	telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_response_and_hold_req(void *telephony_device, int rh)
{
	response_and_hold = rh;

	telephony_response_and_hold_ind(response_and_hold);

	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
}

int telephony_last_dialed_number_req(void *telephony_device)
{
	if( dummy_indicators[LAST_DIAL_INDEX].val == 0)
	{
		/* Since the Last dialed memory is Null Returning Error */
		debug("last_dial number is null");
		return 0;
	}
	telephony_last_dialed_number_rsp(telephony_device, CME_ERROR_NONE);

	/* Notify outgoing call set-up successfully initiated */
	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_OUTGOING);
	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_ALERTING);

	active_call_status = CALL_STATUS_ALERTING;
	active_call_dir = CALL_DIR_OUTGOING;
	
	return 1;
}

void telephony_terminate_call_req(void *telephony_device)
{
	g_free(active_call_number);
	active_call_number = NULL;

	telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);

	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0)
		telephony_update_indicator(dummy_indicators, "callsetup",
						EV_CALLSETUP_INACTIVE);
	else
		telephony_update_indicator(dummy_indicators, "call",
						EV_CALL_INACTIVE);
}

void telephony_answer_outgoingcall_req(void *telephony_device)
{

        telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE);
        telephony_update_indicator(dummy_indicators, "callsetup",
                                        EV_CALLSETUP_INACTIVE);

        active_call_status = CALL_STATUS_ACTIVE;
}


void telephony_answer_call_req(void *telephony_device)
{
	telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);

	telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE);
	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_INACTIVE);

	active_call_status = CALL_STATUS_ACTIVE;
}

int telephony_dial_number_req(void *telephony_device, const char *number)
{
	g_free(active_call_number);
	active_call_number = g_strdup(number);

	debug("telephony-dummy: dial request to %s ,last-dial %d", active_call_number,dummy_indicators[LAST_DIAL_INDEX].val);
	/*PTS Testcase:OCM Dial to mem or Index  in phonebook is invalid */
	if(number[0] == '>' && dummy_indicators[LAST_DIAL_INDEX].val == 0)
	{
		debug("rejecting the dial request on wrong index");
		return 0;
	}

	telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);

	/* Notify outgoing call set-up successfully initiated */
	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_OUTGOING);
	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_ALERTING);

	active_call_status = CALL_STATUS_ALERTING;
	active_call_dir = CALL_DIR_OUTGOING;

	return 1;
}

void telephony_transmit_dtmf_req(void *telephony_device, char tone)
{
	debug("telephony-dummy: transmit dtmf: %c", tone);
	telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_subscriber_number_req(void *telephony_device)
{
	debug("telephony-dummy: subscriber number request");
	if (subscriber_number)
		telephony_subscriber_number_ind(subscriber_number, 0,
						SUBSCRIBER_SERVICE_VOICE);
	telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_list_current_calls_req(void *telephony_device)
{
	debug("telephony-dummy: list current calls request");
	if (active_call_number)
		telephony_list_current_call_ind(1, active_call_dir,
						active_call_status,
						CALL_MODE_VOICE,
						CALL_MULTIPARTY_NO,
						active_call_number,
						0);
	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_operator_selection_req(void *telephony_device)
{
	telephony_operator_selection_ind(OPERATOR_MODE_AUTO, "DummyOperator");
	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
}

void telephony_call_hold_req(void *telephony_device, const char *cmd)
{
	debug("telephony-dymmy: got call hold request %s", cmd);
	telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
}

/* D-Bus method handlers */
static DBusMessage *outgoing_call(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	const char *number;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	debug("telephony-dummy: outgoing call to %s", number);

	g_free(active_call_number);
	active_call_number = g_strdup(number);

	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_OUTGOING);
	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_ALERTING);

	active_call_status = CALL_STATUS_ALERTING;
	active_call_dir = CALL_DIR_OUTGOING;

	return dbus_message_new_method_return(msg);
}

static DBusMessage *incoming_call(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	const char *number;
	dbus_uint32_t type_of_number;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
				DBUS_TYPE_UINT32, &type_of_number,DBUS_TYPE_INVALID))
		return invalid_args(msg);

	debug("telephony-dummy: incoming call to %s,type_of_number %d ", number,type_of_number);

	g_free(active_call_number);
	active_call_number = g_strdup(number);

	telephony_update_indicator(dummy_indicators, "callsetup",
					EV_CALLSETUP_INCOMING);

	active_call_status = CALL_STATUS_INCOMING;
	active_call_dir = CALL_DIR_INCOMING;

	telephony_incoming_call_ind(number, type_of_number);

	return dbus_message_new_method_return(msg);
}
static DBusMessage *outgoing_call_answered(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	debug("telephony-dummy: outgoing call was answered");

	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) {
		telephony_update_indicator(dummy_indicators, "callsetup",
						EV_CALLSETUP_INACTIVE);
		telephony_calling_stopped_ind();
	}

	if (telephony_get_indicator(dummy_indicators, "call") > 0)
		telephony_update_indicator(dummy_indicators, "call",
						EV_CALL_ACTIVE);

	return dbus_message_new_method_return(msg);
}


static DBusMessage *cancel_call(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	debug("telephony-dummy: cancel call");

	g_free(active_call_number);
	active_call_number = NULL;

	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) {
		telephony_update_indicator(dummy_indicators, "callsetup",
						EV_CALLSETUP_INACTIVE);
		telephony_calling_stopped_ind();
	}

	if (telephony_get_indicator(dummy_indicators, "call") > 0)
		telephony_update_indicator(dummy_indicators, "call",
						EV_CALL_INACTIVE);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *signal_strength(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	dbus_uint32_t strength;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	if (strength > 5)
		return invalid_args(msg);

	telephony_update_indicator(dummy_indicators, "signal", strength);

	debug("telephony-dummy: signal strength set to %u", strength);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *battery_level(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	dbus_uint32_t level;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	if (level > 5)
		return invalid_args(msg);

	telephony_update_indicator(dummy_indicators, "battchg", level);

	debug("telephony-dummy: battery level set to %u", level);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *roaming_status(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	dbus_bool_t roaming;
	int val;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE;

	telephony_update_indicator(dummy_indicators, "roam", val);

	debug("telephony-dummy: roaming status set to %d", val);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *last_dial_memory_status(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	dbus_bool_t memory_status;
	int val;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &memory_status,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	val = memory_status ? 1 : 0;

	telephony_update_indicator(dummy_indicators, "last_dial", val);

	debug("telephony-dummy: last_dial_memory_status status set to %d", val);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *registration_status(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	dbus_bool_t registration;
	int val;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &registration,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE;

	telephony_update_indicator(dummy_indicators, "service", val);

	debug("telephony-dummy: registration status set to %d", val);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *set_subscriber_number(DBusConnection *conn,
						DBusMessage *msg,
						void *data)
{
	const char *number;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
						DBUS_TYPE_INVALID))
		return invalid_args(msg);

	g_free(subscriber_number);
	subscriber_number = g_strdup(number);

	debug("telephony-dummy: subscriber number set to %s", number);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *call_setup_status(DBusConnection *conn, DBusMessage *msg,
					void *data)
{
	dbus_uint32_t call_status;
	dbus_uint32_t callsetup_status;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &call_status,
				DBUS_TYPE_UINT32, &callsetup_status,DBUS_TYPE_INVALID))
		return invalid_args(msg);

	dummy_indicators[CALL_STATUS_INDEX].val = call_status;
	dummy_indicators[CALL_SETUP_INDEX].val = callsetup_status;
	return dbus_message_new_method_return(msg);
}

static GDBusMethodTable dummy_methods[] = {
	{ "OutgoingCall",	"s",	"",	outgoing_call		},
	{ "IncomingCall",	"su",	"",	incoming_call		},
	{ "CancelCall",		"",	"",	cancel_call		},
	{ "OutgoingCallAnswered",		"",	"",     outgoing_call_answered             },
	{ "SignalStrength",	"u",	"",	signal_strength		},
	{ "BatteryLevel",	"u",	"",	battery_level		},
	{ "RoamingStatus",	"b",	"",	roaming_status		},
	{ "RegistrationStatus",	"b",	"",	registration_status	},
	{ "SetSubscriberNumber","s",	"",	set_subscriber_number	},
	{ "LastDialMemoryStatus",	"b",	"",	last_dial_memory_status	},
	{ "CallStatus",	"uu",	"",	call_setup_status	},
	{ }
};

static DBusConnection *connection = NULL;

int telephony_init(void)
{
	uint32_t features = AG_FEATURE_REJECT_A_CALL |
				AG_FEATURE_ENHANCED_CALL_STATUS |
				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;

	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);

	g_dbus_register_interface(connection, "/org/bluez/test",
					"org.bluez.TelephonyTest",
					dummy_methods, NULL,
					NULL, NULL, NULL);

	telephony_ready_ind(features, dummy_indicators, response_and_hold,
				chld_str);

	return 0;
}

void telephony_exit(void)
{
	dbus_connection_unref(connection);
	connection = NULL;
}
