/* BTP library - Banana Tree Protocol
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>

#include "b_conn.h"
#include "b_packet.h"
#include "btp_debug.h"
#include "util.h"

guint b_conn_num_conns              = 0;
void (*b_conn_num_conns_zero)(void) = NULL;

static void b_conn_reset (BConn* conn);

/* ************************************************************ */


BConn*   
b_conn_new (gchar* hostname, gint port, BGroup* group,
	    BConnFuncs* funcs, gpointer user_data)
{
  BConn* conn;

  g_return_val_if_fail (hostname,  NULL);
  g_return_val_if_fail (port != 0, NULL);
  g_return_val_if_fail (group, 	   NULL);

  BTPP (1, "b_conn_new\n");

  conn = g_new0 (BConn, 1);
  conn->conn = 	gnet_conn_new (hostname, port, NULL, 0);
  conn->group = group;
  conn->funcs = funcs;
  conn->user_data = user_data;
  conn->status = B_CONN_STATUS_UNCONNECTED;

  ++b_conn_num_conns;

  return conn;
}


BConn*	 
b_conn_new_accepted (GConn* gconn)
{
  BConn* conn;

  conn = g_new0 (BConn, 1);
  conn->conn = gconn;
  conn->status = B_CONN_STATUS_ACCEPTED;

  ++b_conn_num_conns;

  return conn;
}




void
b_conn_delete (BConn* conn)
{
  g_return_if_fail (conn != NULL);

  BTPP (1, "b_conn_delete\n");

  /* If we're in an upcall, don't delete yet */
  if (conn->in_upcall)
    {
      conn->should_destroy = 1;
      return;
    }

  b_conn_reset (conn);
  gnet_conn_delete (conn->conn, FALSE);

  memset (conn, 0, sizeof(*conn));
  g_free (conn);

  /* Call callback */
  if (b_conn_num_conns)
    {
      --b_conn_num_conns;
      if (!b_conn_num_conns && b_conn_num_conns_zero)
	(b_conn_num_conns_zero)();
    }
  else
    g_warning ("b_conn_num_conns invalid\n");
}



void
b_conn_reset (BConn* conn)
{
  GSList* i;

  g_return_if_fail (conn);

  BTPP (1, "b_conn_reset\n");

  conn->status = B_CONN_STATUS_UNCONNECTED;

  /* Stop pinging it */
  b_conn_ping_stop (conn);
  /* keep the distance estimate */

  /* Reset BYE timer */
  rand_timer_cancel (&conn->bye_to_timer);

  /* Remove all IO handlers */
  b_conn_ignore_all (conn);

  BTPP (1, "b_conn_reset: disconnect %p\n", conn->conn);

  /* Reset connection */
  gnet_conn_disconnect (conn->conn, TRUE);

  BTPP (1, "b_conn_reset: disconnect done\n");

  /* Remove all handlers */
  b_conn_handler_remove_all (conn);

  /* Reset packet management stuff */
  conn->in_conn_id = 0;
  conn->out_conn_id = 0;

  BTPP (1, "b_conn_reset: delete packets\n");

  for (i = conn->pkt_queue; i != NULL; i = i->next)
    b_packet_delete ((BPacket*) i->data);

  g_slist_free (conn->pkt_queue);
  conn->pkt_queue = NULL;

  conn->pkt_queue_size = 0;

  conn->buffer_offset = 0;
  conn->cur_read = 0;
  b_packet_delete (conn->cur_pkt);  conn->cur_pkt = NULL;

  BTPP (1, "b_conn_reset: done\n");
}



void
b_conn_print (FILE* file, BConn* conn)
{
  if (conn)
    {
      gchar* status = "none";
  
      switch (conn->status)
	{
	case B_CONN_STATUS_UNCONNECTED:	status = "unconnected";	break;
	case B_CONN_STATUS_CLOSING:	status = "closing";	break;
	case B_CONN_STATUS_CONNECTED:	status = "connected";	break;
	case B_CONN_STATUS_CONNECTING:	status = "connecting";	break;
	case B_CONN_STATUS_ACCEPTED:	status = "accepted";	break;
	}

      fprintf (file,
	       "%s:%d [status = %s, distance = %d, "
	       "pktq length = %d, pktq size = %d, handlers = ",
	       conn->conn->hostname, conn->conn->port, status, conn->distance,
	       g_slist_length (conn->pkt_queue), conn->pkt_queue_size);
      b_conn_handler_print (file, conn);
      fprintf (file, "]\n");
    }
  else
    {
      fprintf (file, "<null>");
    }
}



/* **************************************** */


void
b_conn_func_connect (BConn* conn)
{
  g_return_if_fail (conn);

  BTPP (1, "b_conn_func_connect\n");

  if (conn->funcs && conn->funcs->connect)
    conn->funcs->connect (conn, conn->user_data);
}


void
b_conn_func_ping (BConn* conn)
{
  g_return_if_fail (conn);

  BTPP (1, "b_conn_func_ping\n");

  if (conn->funcs && conn->funcs->ping)
    conn->funcs->ping (conn, conn->user_data);
}


void
b_conn_func_writeable (BConn* conn)
{
  g_return_if_fail (conn);

  BTPP (1, "b_conn_func_writeable\n");

  if (conn->funcs && conn->funcs->writeable)
    conn->funcs->writeable (conn, conn->user_data);
}


void
b_conn_func_close (BConn* conn)
{
  g_return_if_fail (conn);

  BTPP (1, "b_conn_func_close\n");

  b_conn_reset (conn);
  
  if (conn->funcs && conn->funcs->close)
    conn->funcs->close (conn, conn->user_data);
}


void
b_conn_func_fail (BConn* conn)
{
  g_return_if_fail (conn);

  BTPP (1, "b_conn_func_fail\n");

  b_conn_reset (conn);

  if (conn->funcs && conn->funcs->fail)
    conn->funcs->fail (conn, conn->user_data);
}




/* **************************************** */


gboolean
b_conn_has_packet (BConn* conn, guint8 type)
{
  GSList* i;

  g_return_val_if_fail (conn != NULL, FALSE);

  for (i = conn->pkt_queue; i != NULL; i = i->next)
    {
      BPacket* packet = (BPacket*) i->data;

      if (packet->type == type)
	return TRUE;
    }

  return FALSE;
}


gboolean
b_conn_has_packet_by_subtype (BConn* conn, guint8 type, guint8 subtype)
{
  GSList* i;

  g_return_val_if_fail (conn != NULL, FALSE);

  for (i = conn->pkt_queue; i != NULL; i = i->next)
    {
      BPacket* packet = (BPacket*) i->data;

      if (packet->type == type && packet->subtype == subtype)
	return TRUE;
    }

  return FALSE;
}


/* **************************************** */



void 
b_conn_send_packet (BConn* bconn, BPacket* packet)
{
  g_return_if_fail (bconn);
  g_return_if_fail (packet);


  BTPP (1, "b_conn_send_packet: proto = %d, version = %d, type = %d, %d, length = %d, id = %d\n",
	packet->protocol, packet->version, packet->type, 
	packet->subtype, g_ntohs(packet->length), packet->id);
  BTPP (3, "SEND %s (%d)\n", b_packet_type_to_string(packet->type), 
	packet->subtype);

  /* Connect if unconnected */
  if (b_conn_is_unconnected(bconn))
    {
      b_conn_connect (bconn);
    }

  /* Make sure we're watching for a write */
  else if (gnet_conn_is_connected(bconn->conn))
    {
      b_conn_watch_write (bconn);
    }


  /* If the packet is a HELLO, put it at the front of the packet list */
  if (packet->type == B_PACKET_TYPE_HELLO)
    {
      /* Remove any HELLOs that are in the queue now */
      b_conn_remove_packets (bconn, B_PACKET_TYPE_HELLO);

      /* Put at the front of the queue */
      b_conn_prepend_packet (bconn, packet);
    }

  /* Put any reply immediately after the last reply or at the front,
     which ever is first */
  else if (packet->type == B_PACKET_TYPE_OK ||
	   packet->type == B_PACKET_TYPE_ERROR)
    {
      GSList* i;
      GSList* last_reply = NULL;

      for (i = bconn->pkt_queue; i != NULL; i = i->next)
	{
	  gint t = ((BPacket*) i->data)->type;

	  if (t == B_PACKET_TYPE_OK ||
	      t == B_PACKET_TYPE_ERROR)
	    last_reply = i;
	}

      if (last_reply)
	last_reply = g_slist_insert (last_reply, packet, 1);
      else
	bconn->pkt_queue = g_slist_prepend (bconn->pkt_queue, packet);
    }

  /* If the packet is MCAST data, make sure we have enough room. */
  else if (packet->type == B_PACKET_TYPE_MCAST)
    {
      if ((bconn->pkt_queue_size + g_ntohs(packet->length)) <
	  B_CONN_MAX_MCAST_SIZE)
	{
	  b_conn_append_packet (bconn, packet);
	  bconn->pkt_queue_size += g_ntohs(packet->length);
	}
      else
	{
	  BTPP (15, "Dropping MCAST packet due to space\n");
	}

    }

  /* Otherwise, its just a regular packet, so add it to the back of
     the list */
  else
    b_conn_append_packet (bconn, packet);
}


void
b_conn_prepend_packet (BConn* conn, BPacket* packet)
{
  g_return_if_fail (conn);
  g_return_if_fail (packet);

  conn->pkt_queue = g_slist_prepend (conn->pkt_queue, packet);
}


void	 
b_conn_append_packet (BConn* conn, BPacket* packet)
{
  g_return_if_fail (conn);
  g_return_if_fail (packet);

  conn->pkt_queue = g_slist_append (conn->pkt_queue, packet);
}




/* **************************************** */


BPacket* 
b_conn_pop_packet (BConn* conn)
{
  BPacket* packet = NULL;
  BPacket* data_packet = NULL;
  GSList* i;

  g_return_val_if_fail (conn != NULL, NULL);

  BTPP (1, "b_conn_pop_packet\n");

  if (conn->pkt_queue == NULL)
    return NULL;

  for (i = conn->pkt_queue; i != NULL; i = i->next)
    {
      BPacket* pkt = (BPacket*) i->data;
      BTPP (1, "b_conn_pop_packet: QUEUE type = %d, %d, length = %d\n",
	    pkt->type, pkt->subtype, g_ntohs(pkt->length));
    }

  /* Search for a control packet (other than BYE).  Also keep
     track of the first data packet we see.  */
  for (i = conn->pkt_queue; i != NULL; i = i->next)
    {
      BPacket* p = (BPacket*) i->data;

      if (p->type == B_PACKET_TYPE_MCAST ||
	  p->type == B_PACKET_TYPE_UCAST)
	{
	  if (data_packet == NULL)
	    data_packet = p;
	}
      else if (p->type != B_PACKET_TYPE_BYE)
	{
	  packet = p;
	  break;
	}
      /* otherwise it's a BYE, ignore it for now.  This effectively
	 pushes the BYE to the back of the queue. */
    }


  /* If we didn't find a control packet, use a data packet.  If we
     didn't find a data packet, use the top packet, which should be a
     BYE. */
  if (packet == NULL)
    {
      if (data_packet != NULL)
	packet = data_packet;
      else
	{
	  packet = (BPacket*) conn->pkt_queue->data;

	  g_return_val_if_fail (packet->type == B_PACKET_TYPE_BYE, NULL);
	}
    }

  g_return_val_if_fail (packet != NULL, NULL);

  /* If we aren't connected and this is a non-connect packet, return
     NULL.  We don't want to send anything special yet. */
  if (conn->status == B_CONN_STATUS_CONNECTING && 
      packet->type != B_PACKET_TYPE_HELLO)
    return NULL;

  /* Remove the packet from the list */
  conn->pkt_queue = g_slist_remove (conn->pkt_queue, packet);
  BTPP (1, "b_conn_pop_packet: remove type = %d, %d, lenght = %d\n",
	packet->type, packet->subtype, g_ntohs(packet->length));


  /* If it's a ping, set the time now.  (If we set it earlier, it
     might sit around in our buffers before we actually sent it,
     which could cause a bad distance estimate.) */
  if (packet->type == B_PACKET_TYPE_INFO_REQUEST &&
      packet->subtype == B_PACKET_INFO_SUBTYPE_PING)
    {
      BTPP (1, "b_conn_pop_packet: update ping\n");
      gettimeofday((struct timeval*) packet->data, NULL);
    }

  /* Otherwise, if it's a MCAST packet, deduct it's length from
     pkt_queue_size. */
  else if (packet->type == B_PACKET_TYPE_MCAST)
    {
      conn->pkt_queue_size -= g_ntohs(packet->length);
    }

  return packet;
}


void
b_conn_remove_packets (BConn* conn, guint8 type)
{
  GSList* i;

  BTPP (1, "b_conn_remove_packets\n");

  for (i = conn->pkt_queue; i != NULL; )
    {
      GSList* old_i = i;
      BPacket* packet = (BPacket*) i->data;
      i = i->next;

      if (packet->type == type)
	{
	  if (packet->type == B_PACKET_TYPE_MCAST)
	    conn->pkt_queue_size -= g_ntohs (packet->length);

	  b_packet_delete (packet);
	  conn->pkt_queue = g_slist_remove_link (conn->pkt_queue, old_i);
	}
    }
}


void
b_conn_remove_packets_by_subtype (BConn* conn, guint8 type, guint8 subtype)
{
  GSList* i;

  BTPP (1, "b_conn_remove_packets_by_subtype\n");

  for (i = conn->pkt_queue; i != NULL; )
    {
      GSList* old_i = i;
      BPacket* packet = (BPacket*) i->data;
      i = i->next;

      if (packet->type == type && packet->subtype == subtype)
	{
	  if (packet->type == B_PACKET_TYPE_MCAST)
	    conn->pkt_queue_size -= g_ntohs (packet->length);

	  b_packet_delete (packet);
	  conn->pkt_queue = g_slist_remove_link (conn->pkt_queue, old_i);
	}
    }
}


syntax highlighted by Code2HTML, v. 0.9.1