/*-*-linux-c-*-*/

/*
 *  gnewtellium - Newtella for Unix
 *  Copyright (C) 2001 Elias Athanasopoulos
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* These includes are needed in the specified
   order by the BSD family. It maybe better to
   add an #ifdef BSD or something here...
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#include "global.h"
#include "gui.h"
#include "newtella.h"
#include "connection.h"
#include "hosts.h"

#ifdef SOLARIS
#include <sys/filio.h>
#endif

extern struct newtella_options *gl_options;
extern int gl_server;
extern struct host_info *gl_my_host_info;

int bytes_avail(int s)
{
	int ret;

	ioctl(s, FIONREAD, &ret);

	return ret;
}

int con_recv_packet(struct newtella_connection *con, int len)
{
  	int rval = recv(con->s, con->b, len, 0);

	if (rval == 0 || (rval < 0 && errno != EAGAIN))
		return 0;
	else
		return rval;

}

void con_send_packet(struct newtella_connection *con, gchar *buf, gint len, gint maydrop)
{
	struct gpacket *packet;
	gint qlen;

	g_return_if_fail(con->s);

	if (con->state == GS_FAILED || con->state == GS_TIMEOUT)
		return;

	/* we don't drop our packets */
	if (!maydrop)
		goto real_send;

	qlen = g_list_length(con->queue);

	/* drop packet if our queue is full */
	if (qlen >= QUEUE_FULL && ((con->type == CT_OUTGOING) ||
				   (con->type == CT_INCOMING)))
	{
		con->total_dropped_to_send++;
		goto skip;
	}

 real_send:
	packet = newtella_malloc(sizeof(struct gpacket));

	packet->_time = time((time_t) NULL);
	packet->len   = len;
	packet->data  = newtella_malloc(len);

	memcpy(packet->data, buf, len);

	con->queue = g_list_prepend(con->queue, packet);

 skip:
 	if (!con->write_tag)
		con->write_tag = gdk_input_add(con->s,
					       GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
					       (GdkInputFunction) newtella_write,
					       (gpointer) con);
	return;
}

int con_send_to_all(gchar *buf, gint len, gint maydrop)
{
	GList *iter;
	struct newtella_connection *con;
	gint fwd = 0;
	int i=0;

        /* forward a packet to every alive con */
	iter = g_list_first(gl_con);
	while (iter) {
		i++;
		con = iter->data;
		if (con->state == GS_CONNECTED) {
			con_send_packet(con, buf, len, maydrop);
			fwd++;
		}

		iter = g_list_next(iter);
	}

	return fwd;
}

int con_send_ex_one(struct newtella_connection *excon, gchar *buf, gint len, gint maydrop)
{
	GList *iter;
	struct newtella_connection *con;

	if (!excon) {
		con_send_to_all(buf, len, maydrop);
		return 1;
	}

        /* forward a packet to every alive except con */
	iter = g_list_first(gl_con);
	while (iter) {
		con = iter->data;

		if (con->state == GS_CONNECTED && con->s != excon->s)
			con_send_packet(con, buf, len, maydrop);

		iter = iter->next;
	}

	return 1;
}

void con_zero_stats(struct newtella_connection *con)
{
	/* sent data */
	con->total_sent             = 0;
        con->total_dropped_to_send  = 0;

	/* received data */
        con->total_handled          = 0;
        con->total_received_dropped = 0;
        con->total_bad_received     = 0;
}

void con_init_queue(struct newtella_connection *con)
{
	/* init queue */
	con->queue = NULL;
}

int con_add(uint8_t *ip, guint16 port)
{
	/* add a new gnutella connection */
	struct newtella_connection *new_con = newtella_malloc(sizeof(struct newtella_connection));

	bzero(new_con->ip, sizeof(new_con->ip));
	memcpy(new_con->ip, ip, sizeof(new_con->ip));
	new_con->port = port;

	con_zero_stats(new_con);
	con_init_queue(new_con);

	new_con->state   = GS_WAITINGCONNECT;
	new_con->type    = CT_OUTGOING;

	gl_con = g_list_append(gl_con, new_con);

	gui_add_con_to_list(new_con);

	new_con->s = newtella_force_connect(ip, port);

	new_con->read_tag = gdk_input_add(new_con->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
		      (GdkInputFunction) newtella_read, (gpointer)new_con);
	new_con->write_tag = gdk_input_add(new_con->s, GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
		      (GdkInputFunction) newtella_connect, (gpointer)new_con);


	/* update time */
	new_con->last_time = time((time_t)NULL);

	return 1;
}

int con_remove(struct newtella_connection *con)
{
	GList *l;
	struct gpacket *packet;

	/* remove gdk i/o tags */
	if (con->read_tag)
		gdk_input_remove(con->read_tag);
	if (con->write_tag)
		gdk_input_remove(con->write_tag);

	/* close socket */
	close(con->s);

	/* empty queue */
	l = g_list_first(con->queue);

	while (l) {
		packet = l->data;

		newtella_free(packet->data);
		newtella_free(packet);

		l = l->next;
	}

	g_list_free(con->queue);

	/* remove it from GUI */
	gui_remove_con_from_list(con);

	/* remove it from the connection's list */
	gl_con = g_list_remove(gl_con, con);

	/* remove host */
	host_remove(con->ip, con->port);

	newtella_free(con);

	return 1;
}

int con_mark(struct newtella_connection *con, con_state state)
{

	switch (state) {
	case GS_HOSTFEED:
		con->state = GS_HOSTFEED;
		gl_options->connected++;
		break;
	case GS_FAILED:
		if (con->state == GS_FAILED || con->state == GS_TIMEOUT)
			break;

		if (con->state == GS_CONNECTED || con->state == GS_HOSTFEED)
			gl_options->connected--;

		con->state = GS_FAILED;

		//if (con->type == CT_INCOMING || con->type == CT_OUTGOING)
		//	gl_options->cons--;
	        if (con->type == CT_UPLOAD)
			gl_options->ucons--;
		else if (con->type == CT_DOWNLOAD)
			gl_options->dcons--;

		break;
	case GS_TIMEOUT:
		if (con->state == GS_TIMEOUT || con->state == GS_FAILED)
			break;

		if (con->state == GS_CONNECTED || con->state == GS_HOSTFEED)
			gl_options->connected--;

		con->state = GS_TIMEOUT;

		//if (con->type == CT_INCOMING || con->type == CT_OUTGOING)
		//	gl_options->cons--;
	        if (con->type == CT_UPLOAD)
			gl_options->ucons--;
		else if (con->type == CT_DOWNLOAD)
			gl_options->dcons--;

		break;
	case GS_CONNECTED:
		con->state = GS_CONNECTED;
		gl_options->connected++;
		break;
	default:
		break;
	}

	con->last_time = time((time_t)NULL);
	gui_update_con_status(con);

	return 1;
}

int con_reset_buffer(struct newtella_connection *con)
{
	/* init buffer */
	con->buffer_len = 0;
//	con->b          = NULL;

	return 1;
}

int con_activate(void)
{
	GList *iter;
	struct host_info *host;
	char test[128];

	iter = g_list_first(gl_hosts_in_cache);
	while (iter) {
		host = iter->data;
		bzero(test, 128);
		if (!host->used) {
			host->used = 1;
			con_add(host->ip, host->port);

			if (gl_options->cons >= gl_options->min_con)
				break;
		}

		iter = g_list_next(iter);
	}

	return 1;
}
