/** @file daap_conn.c
 *  Manages the connection to a DAAP server.
 *
 *  Copyright (C) 2006-2011 XMMS2 Team
 *
 *  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.
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <glib.h>
#include <glib/gprintf.h>

#include "cc_handlers.h"
#include "daap_md5.h"
#include "daap_conn.h"
#include "daap_util.h"

#include "xmms/xmms_log.h"
#include "xmmsc/xmmsc_ipc_transport.h"
#include "xmmsc/xmmsc_ipc_msg.h"
#include "xmmsc/xmmsc_sockets.h"

GIOChannel *
daap_open_connection (gchar *host, gint port)
{
	gint ai_status;
	gint sockfd;
	struct sockaddr_in server;
	struct addrinfo *ai_hint, *ai_result;
	GIOChannel *sock_chan;
	GError *err = NULL;

	sockfd = socket (AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		return NULL;
	}

	sock_chan = g_io_channel_unix_new (sockfd);
	if (!g_io_channel_get_close_on_unref (sock_chan)) {
		g_io_channel_set_close_on_unref (sock_chan, TRUE);
	}

	g_io_channel_set_flags (sock_chan, G_IO_FLAG_NONBLOCK, &err);
	if (NULL != err) {
		XMMS_DBG ("Error setting nonblock flag: %s\n", err->message);
		g_io_channel_unref (sock_chan);
		return NULL;
	}

	/* call xmms_getaddrinfo() to convert a hostname to ip */

	ai_hint = g_new0 (struct addrinfo, 1);
	/* FIXME sometime in the future, we probably want to append
	 *       " | AF_INET6" for IPv6 support */
	ai_hint->ai_family = AF_INET;

	while ((ai_status = xmms_getaddrinfo (host, NULL, ai_hint, &ai_result))) {
		if (ai_status != EAI_AGAIN) {
			XMMS_DBG ("Error with getaddrinfo(): %s", gai_strerror (ai_status));
			g_io_channel_unref (sock_chan);
			return NULL;
		}
	}

	memset (&server, 0, sizeof (struct sockaddr_in));

	server.sin_addr = ((struct sockaddr_in *) ai_result->ai_addr)->sin_addr;
	server.sin_family = AF_INET;
	server.sin_port = htons (port);

	g_free (ai_hint);
	xmms_freeaddrinfo (ai_result);

	while (42) {
		fd_set fds;
		struct timeval tmout;
		gint sret;
		gint err = 0;
		guint errsize = sizeof (err);

		tmout.tv_sec = 3;
		tmout.tv_usec = 0;

		sret = connect (sockfd,
		                (struct sockaddr *) &server,
		                sizeof (struct sockaddr_in));

		if (sret == 0) {
			break;
		} else if (sret == -1 && errno != XMMS_EINPROGRESS) {
			xmms_log_error ("connect says: %s", strerror (errno));
			g_io_channel_unref (sock_chan);
			return NULL;
		}

		FD_ZERO (&fds);
		FD_SET (sockfd, &fds);

		sret = select (sockfd + 1, NULL, &fds, NULL, &tmout);
		if (sret == 0 || sret == SOCKET_ERROR) {
			g_io_channel_unref (sock_chan);
			return NULL;
		}

		/** Haha, lol lol ololo sockets in POSIX */
		if (getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &err, &errsize) < 0) {
			g_io_channel_unref (sock_chan);
			return NULL;
		}

		if (err != 0) {
			xmms_log_error ("Connect call failed!");
			g_io_channel_unref (sock_chan);
			return NULL;
		}

		if (FD_ISSET (sockfd, &fds)) {
			break;
		}
	}

	g_io_channel_set_encoding (sock_chan, NULL, &err);
	if (NULL != err) {
		XMMS_DBG ("Error setting encoding: %s\n", err->message);
		g_io_channel_unref (sock_chan);
		return NULL;
	}

	return sock_chan;
}

gchar *
daap_generate_request (const gchar *path, gchar *host, gint request_id)
{
	gchar *req;
	gint8 hash[33];

	memset (hash, 0, 33);

	daap_hash_generate (DAAP_VERSION, (guchar *) path, 2, (guchar *) hash,
	                    request_id);

	req = g_strdup_printf ("GET %s %s\r\n"
	                       "Host: %s\r\n"
	                       "Accept: */*\r\n"
	                       "User-Agent: %s\r\n"
	                       "Accept-Language: en-us, en;q=5.0\r\n"
	                       "Client-DAAP-Access-Index: 2\r\n"
	                       "Client-DAAP-Version: 3.0\r\n"
	                       "Client-DAAP-Validation: %s\r\n"
	                       "Client-DAAP-Request-ID: %d\r\n"
	                       "Connection: close\r\n"
	                       "\r\n",
	                       path, HTTP_VER_STRING, host,
	                       USER_AGENT, hash, request_id);
	return req;
}

void
daap_send_request (GIOChannel *sock_chan, gchar *request)
{
	gint n_bytes_to_send;

	n_bytes_to_send = strlen (request);

	write_buffer_to_channel (sock_chan, request, n_bytes_to_send);
}

void
daap_receive_header (GIOChannel *sock_chan, gchar **header)
{
	guint n_total_bytes_recvd = 0;
	gsize linelen;
	gchar *response, *recv_line;
	GIOStatus io_stat;
	GError *err = NULL;

	if (NULL != header) {
		*header = NULL;
	}

	response = g_malloc0 (MAX_HEADER_LENGTH);
	if (NULL == response) {
		XMMS_DBG ("Error: couldn't allocate memory for response.\n");
		return;
	}

	/* read data from the io channel one line at a time, looking for
	 * the end of the header */
	do {
		io_stat = g_io_channel_read_line (sock_chan, &recv_line, &linelen,
		                                  NULL, &err);
		if (io_stat == G_IO_STATUS_ERROR) {
			XMMS_DBG ("Error reading from channel: %s\n", err->message);
			break;
		}

		if (NULL != recv_line) {
			memcpy (response+n_total_bytes_recvd, recv_line, linelen);
			n_total_bytes_recvd += linelen;

			if (strcmp (recv_line, "\r\n") == 0) {
				g_free (recv_line);
				if (NULL != header) {
					*header = g_malloc0 (n_total_bytes_recvd);
					if (NULL == *header) {
						XMMS_DBG ("error: couldn't allocate header\n");
						break;
					}
					memcpy (*header, response, n_total_bytes_recvd);
				}
				break;
			}

			g_free (recv_line);
		}

		if (io_stat == G_IO_STATUS_EOF) {
			break;
		}

		if (n_total_bytes_recvd >= MAX_HEADER_LENGTH) {
			XMMS_DBG ("Warning: Maximum header size reached without finding "
			          "end of header; bailing.\n");
			break;
		}
	} while (TRUE);

	g_free (response);

	if (sock_chan) {
		g_io_channel_flush (sock_chan, &err);
		if (NULL != err) {
			XMMS_DBG ("Error flushing buffer: %s\n", err->message);
			return;
		}
	}
}

cc_data_t *
daap_handle_data (GIOChannel *sock_chan, gchar *header)
{
	cc_data_t * retval;
	gint response_length;
	gchar *response_data;

	response_length = get_data_length (header);

	if (BAD_CONTENT_LENGTH == response_length) {
		XMMS_DBG ("warning: Header does not contain a \""CONTENT_LENGTH
		          "\" parameter.\n");
		return NULL;
	} else if (0 == response_length) {
		XMMS_DBG ("warning: "CONTENT_LENGTH" is zero, most likely the result of "
		          "a bad request.\n");
		return NULL;
	}

	response_data = g_malloc0 (response_length);
	if (NULL == response_data) {
		XMMS_DBG ("error: could not allocate response memory\n");
		return NULL;
	}

	read_buffer_from_channel (sock_chan, response_data, response_length);

	retval = cc_handler (response_data, response_length);
	g_free (response_data);

	return retval;
}

gint
get_data_length (gchar *header)
{
	gint len;
	gchar *content_length;

	content_length = strstr (header, CONTENT_LENGTH);
	if (NULL == content_length) {
		len = BAD_CONTENT_LENGTH;
	} else {
		content_length += strlen (CONTENT_LENGTH);
		len = atoi (content_length);
	}

	return len;
}

gint
get_server_status (gchar *header)
{
	gint status;
	gchar *server_status;

	server_status = strstr (header, HTTP_VER_STRING);
	if (NULL == server_status) {
		status = UNKNOWN_SERVER_STATUS;
	} else {
		server_status += strlen (HTTP_VER_STRING" ");
		status = atoi (server_status);
	}

	return status;
}

