/* 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 <stdlib.h>

#include "b_conn_connect.h"
#include "b_packet.h"
#include "btp_debug.h"


static gboolean connect_gconn_func (GConn* conn, GConnStatus status, 
				    gchar* buffer, gint length, gpointer user_data);

static void	receive_ok_connect (BConn* conn, BPacket* packet, gpointer user_data);
static void 	receive_error_connect (BConn* conn, BPacket* packet, gpointer user_data);
static void 	receive_ok_timeout (BConn* conn, gint type, gint subtype, gpointer user_data);

static void 	receive_bye  (BConn* conn, BPacket* packet, gpointer user_data);

static gboolean	bye_timeout_cb (gpointer data);


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



/* 

   Connect to a host.  We assume the conn is unconnected when this is
   called.  Two hosts are connected if they have exchanged a HELLO-OK
   pair of packets.

   Last checked: 2001-5-1 DAH

*/
void
b_conn_connect (BConn* bconn)
{
  g_return_if_fail (bconn);
  g_return_if_fail (bconn->conn);

  BTPP (1, "b_conn_connect %s:%d\n", 
	bconn->conn->hostname, bconn->conn->port);

  /* Ignore if we aren't unconnected (ie, we're connected or
     connecting) */
  if (!b_conn_is_unconnected(bconn))
    return;

  /* Double check: we shouldn't have a low-level connection */
  g_return_if_fail (!gnet_conn_is_connected (bconn->conn));

  /* Dub is connecting */
  bconn->status = B_CONN_STATUS_CONNECTING;

  /* Set callbacks for low level connection */
  bconn->conn->func = connect_gconn_func;
  bconn->conn->user_data = bconn;

  BTPP (11, "CONNECT START %s:%d\n", bconn->conn->hostname, bconn->conn->port);

  /* Initiate low-level connection */
  gnet_conn_connect (bconn->conn, BCONNECT_CONNECT_TIME);
}



static gboolean
connect_gconn_func (GConn* conn, GConnStatus status, 
		    gchar* buffer, gint length, gpointer user_data)
{
  BConn* bconn = (BConn*) user_data;

  g_return_val_if_fail (bconn, FALSE);

  /* Don't need to call this again */
  conn->func = NULL;
  conn->user_data = NULL;

  switch (status)
    {
    case GNET_CONN_STATUS_CONNECT:
      {
	BPacket* packet;

	/* Set up watches */
	b_conn_watch_read (bconn);
	b_conn_watch_error (bconn);
	/* We will add the write watch when we call send_packet */

	/* Assign the conn an ID.  For now we just use a random
	   number, since the TCP connection is enough to identify it
	   after fully connecting. */
	bconn->in_conn_id = rand();

	BTPP (0, "connect_gconn_func: in_conn_id = %d\n", bconn->in_conn_id);

	BTPP (11, "CONNECT ACCEPTED %s:%d\n", bconn->conn->hostname, bconn->conn->port);

	/* Send a HELLO packet */
	packet = b_packet_new_hello (bconn);
	b_conn_send_packet (bconn, packet);

	/* Handle an OK packet */
	b_conn_handler_add_full (bconn, 
				 B_PACKET_TYPE_OK, B_PACKET_OK_SUBTYPE_HELLO,
				 receive_ok_connect, receive_ok_timeout,
				 BCONNECT_RECEIVE_OK_TIME, NULL);

	/* Handle an ERROR packet (don't need a timeout - OK timeout is enough) */
	b_conn_handler_add (bconn, 
			    B_PACKET_TYPE_ERROR, B_PACKET_ERROR_SUBTYPE_HELLO,
			    receive_error_connect, NULL);

	break;
      }

    case GNET_CONN_STATUS_ERROR:
    case GNET_CONN_STATUS_TIMEOUT:
      {
	BTPP (11, "CONNECT ERROR %s:%d\n", bconn->conn->hostname, bconn->conn->port);

	/* Fail the node */
	b_conn_func_fail (bconn);

	break;
      }

    default:
      {
	g_assert_not_reached();
	break;
      }
    }

  return FALSE;
}


static void
receive_ok_connect (BConn* conn, BPacket* packet, gpointer user_data)
{
  GSList* i;

  BTPP (1, "receive_ok_connect\n");

  /* Stop handling OK and ERROR  */
  b_conn_handler_remove (conn, B_PACKET_TYPE_OK, B_PACKET_OK_SUBTYPE_HELLO, NULL);
  b_conn_handler_remove (conn, B_PACKET_TYPE_ERROR, B_PACKET_ERROR_SUBTYPE_HELLO, NULL);

  /* The packet should have an ID in it */
  if (g_ntohs(packet->length) != sizeof(packet->id))
    {
      g_warning ("Received HELLO-OK without ID\n");
      b_conn_func_fail (conn);
      return;
    }
  
  /* Save the ID of the conn */
  conn->out_conn_id = *(guint32*) packet->data;

  /* Fix the ID of any packets in our packet queue */
  for (i = conn->pkt_queue; i != NULL; i = i->next)
    ((BPacket*) i->data)->id = conn->out_conn_id;

  /* Dub it connected */
  conn->status = B_CONN_STATUS_CONNECTED;

  /* Start sending pings */
  b_conn_ping_start (conn);
  b_conn_ping_handle (conn);
  b_conn_bye_handle (conn);

  /* Add a write watch if we have packets to send */
  if (conn->pkt_queue)
    b_conn_watch_write (conn);

  BTPP (11, "CONNECT COMPLETE %s:%d\n", conn->conn->hostname, conn->conn->port);

  /* Call up */
  b_conn_func_connect (conn);
}


static void
receive_error_connect (BConn* conn, BPacket* packet, gpointer user_data)
{
  BTPP (1, "receive_error_connect\n");

  BTPP (11, "CONNECT ERROR2 %s:%d\n", conn->conn->hostname, conn->conn->port);

  b_conn_func_fail (conn);
}


static void
receive_ok_timeout (BConn* conn, gint type, gint subtype, gpointer user_data)
{
  BTPP (1, "receive_ok_timeout\n");
  BTPP (11, "CONNECT TIMEOUT %s:%d\n", conn->conn->hostname, conn->conn->port);

  b_conn_func_fail (conn);
}



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

/**

   Close a connection.  If the connection is already closed, the
   connection is closed immediately.  Otherwise, we send a BYE
   message.

   Last checked: 2001-5-1 DAH

 */
void
b_conn_close (BConn* conn)
{
  BPacket* packet;

  g_return_if_fail (conn);

  BTPP (1, "b_conn_close\n");

  /* Close immediately if we aren't even connected.  This will reset
     the connection and do the upcall. */
  if (!b_conn_is_connected(conn))
    {
      b_conn_func_close (conn);
      return;
    }

  BTPP (11, "CONNECT CLOSE %s:%d\n", conn->conn->hostname, conn->conn->port);

  /* Stop pinging the host */
  b_conn_ping_stop (conn);

  /* Ignore any packets we receive */
  b_conn_ignore_read (conn);

  /* Send a BYE */
  packet = b_packet_new_bye (conn);
  b_conn_send_packet (conn, packet);

  /* Set a timer on sending the BYE.  The timer will be canceled with
     the BYE is sent.  If it is not cancelled, we will close the
     connection by force. */
  g_return_if_fail (!conn->bye_to_timer);
  conn->bye_to_timer = g_timeout_add (BCONNECT_BYE_TIME, bye_timeout_cb, conn);

  /* Set the status to closing */
  conn->status = B_CONN_STATUS_CLOSING;

}


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

void
b_conn_bye_handle (BConn* conn)
{
  g_return_if_fail (conn);
  g_return_if_fail (b_conn_is_connected (conn));
  g_return_if_fail (!b_conn_is_closing (conn));

  /* Start handling BYE */
  b_conn_handler_add (conn, B_PACKET_TYPE_BYE, 0, receive_bye, NULL);
}


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

  /* Stop handling BYE */
  b_conn_handler_remove (conn, B_PACKET_TYPE_BYE, 0, NULL);
}


static void
receive_bye (BConn* conn, BPacket* packet, gpointer user_data)
{
  g_return_if_fail (conn);
  g_return_if_fail (packet);
  g_return_if_fail (packet->type == B_PACKET_TYPE_BYE);

  BTPP (1, "receive_bye\n");

  b_conn_func_close (conn);
}


static gboolean
bye_timeout_cb (gpointer data)
{
  BConn* conn = (BConn*) conn;

  b_conn_func_fail (conn);

  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1