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

#include "b_peer.h"
#include "b_conn.h"
#include "btp_debug.h"


struct _BPeerHandler
{
  BPeerHandlerFunc func;
  gpointer data;

};


static void server_func (GServer* gserver, GServerStatus status, 
			 GConn* conn, gpointer user_data);
	   
	   
static void receive_hello (BConn* conn, BPacket* packet, gpointer user_data);
static void receive_hello_timeout (BConn* conn, gint type, gint subtype, 
				   gpointer user_data);

static void get_handler_data_hfunc (gpointer key, gpointer value, 
				    gpointer user_data);


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


BPeer* 
b_peer_new (const gchar* hostname, const GInetAddr* iface, gboolean force_port)
{
  GServer* server;
  BPeer* peer;

  BTPP (1, "b_peer_new\n");

  g_return_val_if_fail (hostname, NULL);

  server = gnet_server_new (iface, force_port, server_func, NULL);
  if (!server)
    return NULL;

  peer = g_new0 (BPeer, 1);

  server->user_data = peer;
  peer->server = server;
  peer->hostname = g_strdup (hostname);
  peer->port = server->port;
  peer->handlers = g_hash_table_new (g_str_hash, g_str_equal);

  return peer;
}


static void  
peer_delete_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  g_free (key);		/* string  */
  g_free (value);	/* handler */
}


void
b_peer_delete (BPeer* peer)
{
  GSList* i;
      
  if (!peer)
    return;

  g_free (peer->hostname);

  if (peer->server)
    gnet_server_delete (peer->server);

  g_hash_table_foreach (peer->handlers, peer_delete_hfunc, NULL);
  g_hash_table_destroy (peer->handlers);

  for (i = peer->conns; i != NULL; i = i->next)
    b_conn_delete ((BConn*) i->data);
  g_slist_free (peer->conns);

  g_free (peer);
}


static void  
peer_print_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  fprintf ((FILE*) user_data, "\t%s\n", (gchar*) key);
}


void
b_peer_print (FILE* file, BPeer* peer)
{
  if (!peer)
    {
      fprintf (file, "(null)");
      return;
    }

  fprintf (file, "%s:%d [%d pending conns]\n", 
	   peer->hostname, peer->port,
	   g_slist_length (peer->conns));

  fprintf (file, "  handlers:\n");
  g_hash_table_foreach (peer->handlers, peer_print_hfunc, file);
}




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


/**

   Called when someone connects to us.

   We get the conn, create a new BConn, add watches for read and
   error, then set a timer for receiving the HELLO.

   Last checked: 	2001-4-30 DAH

*/
static void
server_func (GServer* gserver, GServerStatus status, GConn* conn, gpointer user_data)
{
  BPeer* peer = (BPeer*) user_data;
  BConn* bconn = NULL;

  BTPP (1, "server_func %d\n", status); 

  g_assert (status != GNET_SERVER_STATUS_ERROR);

  /* Create a new conn.  */
  bconn = b_conn_new_accepted (conn);

  /* Watch read and err */
  b_conn_watch_read (bconn);
  b_conn_watch_error (bconn);

  /* Handle a HELLO packet */
  b_conn_handler_add_full (bconn, B_PACKET_TYPE_HELLO, -1, 
			   receive_hello, receive_hello_timeout, 
			   B_RECEIVE_HELLO_TIME, peer);

  /* Save connection */
  peer->conns = g_slist_prepend (peer->conns, bconn);

  /* We should now receive a HELLO and will send back an OK or ERROR.
     If we don't receive a HELLO, the timer will expire and the
     connection will be closed. */
}




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



/**

   Handle a HELLO packet.

   Last checked:	2001-4-30 DAH

 */
static void
receive_hello (BConn* conn, BPacket* packet, gpointer user_data)
{
  BPeer* 	peer;
  GURL* 	url = NULL;
  gchar*	hostname = NULL;
  gint		port = 0;
  GSList* 	i;
  BPeerHandler*	handler;
  BPacket*	new_packet;

  BTPP (1, "receive_hello\n");

  peer = (BPeer*) user_data;
  g_return_if_fail (peer);
  g_return_if_fail (conn);
  g_return_if_fail (packet);

  /* Remove from conn list */
  peer->conns = g_slist_remove (peer->conns, conn);

  /* Remove HELLO handler */
  b_conn_handler_remove (conn, B_PACKET_TYPE_HELLO, -1, NULL);

  /* Parse the packet */
  if (b_packet_parse_hello (packet, &url, &hostname, &port) || 
      !url->resource)
    {
      g_warning ("Recieved bad HELLO\n");
      goto drop;
    }

  /* Make sure the sender isn't us */
  if (!strcmp(hostname, peer->hostname) && port == peer->port)
    {
      g_warning ("Received HELLO packet from myself\n");
      g_free (hostname);
      goto drop;
    }

  /* Fix the hostname, port, and address of the connection */
  g_free (conn->conn->hostname);
  conn->conn->hostname = hostname;
  conn->conn->port = port;
  gnet_inetaddr_delete (conn->conn->inetaddr);
  conn->conn->inetaddr = NULL;

  /* Save the out packet ID */
  conn->out_conn_id = packet->id;
  BTPP (0, "conn->out_conn_id = %d\n", conn->out_conn_id);

  /* Choose an in packet ID for us */
  conn->in_conn_id = rand();
  BTPP (0,"conn->in_conn_id = %d\n", conn->in_conn_id);

  /* 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;

  /* Find a peer handler for it */
  BTPP (0, "find handler for %s\n", url->resource);
  handler = (BPeerHandler*) g_hash_table_lookup (peer->handlers, url->resource);
  BTPP (0, "handler is %p\n", handler);
  if (!handler) goto drop;
  if (handler->func(conn, handler->data)) 
    {
      BTPP (1, "receive_hello_timeout: handler not found\n");
      goto drop;
    }
  /* TODO: Send an ERROR packet.  This requires additional
     bookkeepping, so we don't bother doing it. */

  /* Send an OK packet */
  new_packet = b_packet_new_ok_hello (conn);
  b_conn_send_packet (conn, new_packet);

  /* Handle BYE and PING */
  b_conn_ping_start (conn);
  b_conn_ping_handle (conn);
  b_conn_bye_handle (conn);

  /* Call connect */
  b_conn_func_connect (conn);

 done:
  BTPP (1, "receive_hello_timeout: connection complete\n");

  /* Delete everything */
  gnet_url_delete (url);
  return;

 drop:
  BTPP (1, "receive_hello_timeout: connection dropped\n");

  /* Delete the connection immediately */
  b_conn_delete (conn);
  goto done;
}



static void
receive_hello_timeout (BConn* conn, gint type, gint subtype, gpointer user_data)
{
  BPeer* peer = (BPeer*) user_data;

  g_return_if_fail (type == B_PACKET_TYPE_HELLO);

  BTPP (1, "receive_hello_timeout\n");

  /* Remove from conn list */
  peer->conns = g_slist_remove (peer->conns, conn);

  /* Delete */
  b_conn_delete (conn);
}



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

void
b_peer_add_handler (BPeer* peer, const gchar* name, BPeerHandlerFunc func,
		    gpointer user_data)
{
  BPeerHandler* handler;

  g_return_if_fail (peer);
  g_return_if_fail (func);

  BTPP (1, "b_peer_add_handler %s\n", name);

  handler = g_new0 (BPeerHandler, 1);
  handler->func = func;
  handler->data = user_data;

  g_hash_table_insert (peer->handlers, g_strdup (name), handler);
}


void
b_peer_remove_handler (BPeer* peer, const gchar* name)
{
  gchar* orig_key;
  BPeerHandler* handler;

  g_return_if_fail (peer);
  g_return_if_fail (name);

  if (!g_hash_table_lookup_extended(peer->handlers, name, 
				    (gpointer) &orig_key, (gpointer) &handler))
    return;

  g_hash_table_remove (peer->handlers, name);
  g_free (orig_key);
  g_free (handler);
}


gboolean
b_peer_has_handler (BPeer* peer, const gchar* name)
{
  return (g_hash_table_lookup (peer->handlers, name) != NULL);
}


GSList*  
b_peer_get_handler_data (BPeer* peer)
{
  GSList* list;

  list = NULL;
  g_hash_table_foreach (peer->handlers, get_handler_data_hfunc, &list);
  return list;
}


static void
get_handler_data_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  BPeerHandler* handler;
  GSList**      listp;

  handler = (BPeerHandler*) value;
  listp   = (GSList**) user_data;

  *listp = g_slist_prepend (*listp, handler->data);
}


syntax highlighted by Code2HTML, v. 0.9.1