/*
 *
 *  oFono - Open Source Telephony
 *
 *  Copyright (C) 2009-2010  Nokia Corporation and/or its subsidiary(-ies).
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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 <stdint.h>
#include <errno.h>
#include <glib.h>

#include "client.h"
#include "pipe.h"

#define PN_PIPE			0xD9
#define PN_PIPE_INVALID_HANDLE	0xFF

struct isi_pipe_create_req {
	uint8_t cmd;
	uint8_t state_after;
	uint8_t priority;

	uint8_t device1;
	uint8_t object1;
	uint8_t type1;
	uint8_t pad;

	uint8_t device2;
	uint8_t object2;
	uint8_t type2;
	uint8_t n_sb;
};

struct isi_pipe_enable_req {
	uint8_t cmd;
	uint8_t pipe_handle;
	uint8_t pad;
};

struct isi_pipe_reset_req {
	uint8_t cmd;
	uint8_t pipe_handle;
	uint8_t state_after;
};

struct isi_pipe_remove_req {
	uint8_t cmd;
	uint8_t pipe_handle;
};

struct isi_pipe_resp {
	uint8_t pipe_handle;
	uint8_t error_code;
	uint8_t error1;
	uint8_t error2;
};

enum isi_pipe_message_id {
	PNS_PIPE_CREATE_REQ,
	PNS_PIPE_CREATE_RESP,
	PNS_PIPE_REMOVE_REQ,
	PNS_PIPE_REMOVE_RESP,
	PNS_PIPE_RESET_REQ,
	PNS_PIPE_RESET_RESP,
	PNS_PIPE_ENABLE_REQ,
	PNS_PIPE_ENABLE_RESP,
	PNS_PIPE_REDIRECT_REQ,
	PNS_PIPE_REDIRECT_RESP,
	PNS_PIPE_DISABLE_REQ,
	PNS_PIPE_DISABLE_RESP,
};

enum pn_pipe_error {	/* error codes */
	PN_PIPE_ERR_NO_ERROR,
	PN_PIPE_ERR_INVALID_PARAM,
	PN_PIPE_ERR_INVALID_HANDLE,
	PN_PIPE_ERR_INVALID_CTRL_ID,
	PN_PIPE_ERR_NOT_ALLOWED,
	PN_PIPE_ERR_PEP_IN_USE,
	PN_PIPE_ERR_OVERLOAD,
	PN_PIPE_ERR_DEV_DISCONNECTED,
	PN_PIPE_ERR_TIMEOUT,
	PN_PIPE_ERR_ALL_PIPES_IN_USE,
	PN_PIPE_ERR_GENERAL,
	PN_PIPE_ERR_NOT_SUPPORTED,
};

enum pn_pipe_state {	/* initial pipe state */
	PN_PIPE_DISABLE,
	PN_PIPE_ENABLE,
};

enum pn_msg_priority {
	PN_MSG_PRIORITY_LOW = 1,
	PN_MSG_PRIORITY_HIGH,
};

struct _GIsiPipe {
	GIsiClient *client;
	GIsiPipeHandler handler;
	GIsiPipeErrorHandler error_handler;
	void *opaque;
	int error;
	uint8_t handle;
	gboolean enabled;
	gboolean enabling;
};

static int g_isi_pipe_error(enum pn_pipe_error code)
{
	switch (code) {
	case PN_PIPE_ERR_NO_ERROR:
		return 0;
	case PN_PIPE_ERR_INVALID_PARAM:
		return -EINVAL;
	case PN_PIPE_ERR_INVALID_HANDLE:
		return -EBADF;
	case PN_PIPE_ERR_INVALID_CTRL_ID:
		return -ENOTSUP;
	case PN_PIPE_ERR_NOT_ALLOWED:
		return -EPERM;
	case PN_PIPE_ERR_PEP_IN_USE:
		return -EBUSY;
	case PN_PIPE_ERR_OVERLOAD:
		return -ENOBUFS;
	case PN_PIPE_ERR_DEV_DISCONNECTED:
		return -ENETDOWN;
	case PN_PIPE_ERR_TIMEOUT:
		return -ETIMEDOUT;
	case PN_PIPE_ERR_ALL_PIPES_IN_USE:
		return -ENFILE;
	case PN_PIPE_ERR_GENERAL:
		return -EAGAIN;
	case PN_PIPE_ERR_NOT_SUPPORTED:
		return -ENOSYS;
	}
	return -EBADMSG;
}

static void g_isi_pipe_handle_error(GIsiPipe *pipe, uint8_t code)
{
	int err = g_isi_pipe_error(code);

	if (err == 0)
		return;

	pipe->error = err;

	if (pipe->error_handler)
		pipe->error_handler(pipe);
}

static void g_isi_pipe_created(const GIsiMessage *msg, void *data)
{
	struct isi_pipe_resp *resp;
	size_t len = sizeof(struct isi_pipe_resp);
	GIsiPipe *pipe = data;

	if (g_isi_msg_error(msg) < 0) {
		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
		return;
	}

	if (g_isi_msg_id(msg) != PNS_PIPE_CREATE_RESP)
		return;

	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
		return;

	if (resp->pipe_handle == PN_PIPE_INVALID_HANDLE) {
		g_isi_pipe_handle_error(pipe, resp->error_code);
		return;
	}

	pipe->handle = resp->pipe_handle;

	if (pipe->enabling)
		g_isi_pipe_start(pipe);

	if (pipe->handler)
		pipe->handler(pipe);
}

/**
 * Create a Phonet pipe in disabled state and with low priority.
 * @param modem ISI modem to create a pipe with
 * @param created optional callback for created event
 * @param obj1 Object handle of the first end point
 * @param obj2 Object handle of the second end point
 * @param type1 Type of the first end point
 * @param type2 Type of the second end point
 * @return a pipe object on success, NULL on error.
 */
GIsiPipe *g_isi_pipe_create(GIsiModem *modem, GIsiPipeHandler cb, uint16_t obj1,
				uint16_t obj2, uint8_t type1, uint8_t type2)
{
	struct isi_pipe_create_req msg = {
		.cmd = PNS_PIPE_CREATE_REQ,
		.state_after = PN_PIPE_DISABLE,
		.priority = PN_MSG_PRIORITY_LOW,
		.device1 = obj1 >> 8,
		.object1 = obj1 & 0xff,
		.type1 = type1,
		.device2 = obj2 >> 8,
		.object2 = obj2 & 0xff,
		.type2 = type2,
		.n_sb = 0,
	};
	size_t len = sizeof(msg);
	GIsiPipe *pipe;

	pipe = g_try_new0(GIsiPipe, 1);
	if (pipe == NULL) {
		errno = ENOMEM;
		return NULL;
	}

	pipe->client = g_isi_client_create(modem, PN_PIPE);
	if (pipe->client == NULL) {
		errno = ENOMEM;
		g_free(pipe);
		return NULL;
	}

	pipe->handler = cb;
	pipe->error_handler = NULL;
	pipe->error = 0;
	pipe->enabling = FALSE;
	pipe->enabled = FALSE;
	pipe->handle = PN_PIPE_INVALID_HANDLE;

	if (g_isi_client_send(pipe->client, &msg, len,
					g_isi_pipe_created, pipe, NULL))
		return pipe;

	g_isi_client_destroy(pipe->client);
	g_free(pipe);

	return NULL;
}

static void g_isi_pipe_enabled(const GIsiMessage *msg, void *data)
{
	GIsiPipe *pipe = data;
	const struct isi_pipe_resp *resp;
	size_t len = sizeof(struct isi_pipe_resp);

	if (g_isi_msg_error(msg) < 0) {
		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
		return;
	}

	if (g_isi_msg_id(msg) != PNS_PIPE_ENABLE_RESP)
		return;

	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
		return;

	if (pipe->handle != resp->pipe_handle)
		return;

	g_isi_pipe_handle_error(pipe, resp->error_code);

	pipe->enabling = FALSE;

	if (!pipe->error)
		pipe->enabled = TRUE;
}

static void g_isi_pipe_enable(GIsiPipe *pipe)
{
	struct isi_pipe_enable_req msg = {
		.cmd = PNS_PIPE_ENABLE_REQ,
		.pipe_handle = pipe->handle,
	};
	size_t len = sizeof(msg);

	g_isi_client_send(pipe->client, &msg, len,
				g_isi_pipe_enabled, pipe, NULL);
}

/**
 * Enable a pipe, i.e. turn on data transfer between the two end points.
 * @param pipe pipe as returned from g_isi_pipe_create()
 * @return 0 on success or an error code
 */
int g_isi_pipe_start(GIsiPipe *pipe)
{
	if (pipe->error)
		return pipe->error;

	if (pipe->enabling || pipe->enabled)
		return 0;

	if (pipe->handle != PN_PIPE_INVALID_HANDLE)
		g_isi_pipe_enable(pipe);
	else
		pipe->enabling = TRUE;

	return 0;
}

/* Not very useful, it will never have time to trigger */
static void g_isi_pipe_removed(const GIsiMessage *msg, void *data)
{
	GIsiPipe *pipe = data;
	struct isi_pipe_resp *resp;
	size_t len = sizeof(struct isi_pipe_resp);

	if (g_isi_msg_error(msg) < 0) {
		g_isi_pipe_handle_error(pipe, PN_PIPE_ERR_TIMEOUT);
		return;
	}

	if (g_isi_msg_id(msg) != PNS_PIPE_REMOVE_RESP)
		return;

	if (!g_isi_msg_data_get_struct(msg, 0, (const void **) &resp, len))
		return;

	if (pipe->handle != resp->pipe_handle)
		return;

	pipe->handle = PN_PIPE_INVALID_HANDLE;
	pipe->error = -EPIPE;
}


static void g_isi_pipe_remove(GIsiPipe *pipe)
{
	struct isi_pipe_remove_req msg = {
		.cmd = PNS_PIPE_REMOVE_REQ,
		.pipe_handle = pipe->handle,
	};
	size_t len = sizeof(msg);

	g_isi_client_send(pipe->client, &msg, len,
				g_isi_pipe_removed, pipe, NULL);
}

/**
 * Destroy a pipe. If it was connected, it is removed.
 * @param pipe pipe as returned from g_isi_pipe_create()
 */
void g_isi_pipe_destroy(GIsiPipe *pipe)
{
	if (!pipe->error)
		g_isi_pipe_remove(pipe);

	g_isi_client_destroy(pipe->client);
	g_free(pipe);
}

void g_isi_pipe_set_error_handler(GIsiPipe *pipe, GIsiPipeErrorHandler cb)
{
	pipe->error_handler = cb;
}

int g_isi_pipe_get_error(const GIsiPipe *pipe)
{
	return pipe->error;
}

void *g_isi_pipe_set_userdata(GIsiPipe *pipe, void *opaque)
{
	void *old = pipe->opaque;

	pipe->opaque = opaque;
	return old;
}

void *g_isi_pipe_get_userdata(GIsiPipe *pipe)
{
	return pipe->opaque;
}

/**
 * Return a pipe handle.
 * @param pipe a ready-made pipe with handler data present. Available
 * after the pipe creation callback is called.
 * @return uint8_t handle.
 */

uint8_t g_isi_pipe_get_handle(GIsiPipe *pipe)
{
	return pipe->handle;
}
