/*******************************************************************
  uLan Utilities Library - C library of basic reusable constructions

  ul_evcbase.c	- event connectors

  (C) Copyright 2001-2003 by Pavel Pisa - Originator

  The uLan utilities library can be used, copied and modified under
  next licenses
    - GPL - GNU General Public License
    - LGPL - GNU Lesser General Public License
    - MPL - Mozilla Public License
    - and other licenses added by project originators
  Code can be modified and re-distributed under any combination
  of the above listed licenses. If contributor does not agree with
  some of the licenses, he/she can delete appropriate line.
  Warning, if you delete all lines, you are not allowed to
  distribute source code and/or binaries utilizing code.
  
  See files COPYING and README for details.

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

#include <stdarg.h>
#include <string.h>
#include "ul_utmalloc.h"
#include "ul_list.h"
#include "ul_evcbase.h"

UL_LIST_CUST_DEC(evc_tx_list, evc_tx_hub_t, evc_link_t, links, src.peers)
UL_LIST_CUST_DEC(evc_rx_list, evc_rx_hub_t, evc_link_t, links, dst.multi.peers)

/**
 * evc_link_init - Initialize Event Connector Link
 * @link:	pointer to the link
 *
 * Link reference count is set to 1 by this function
 * Return Value: negative value informs about failure.
 */
int evc_link_init(evc_link_t *link)
{
  memset(link,0,sizeof(evc_link_t));
  evc_rx_list_init_detached(link);
  evc_tx_list_init_detached(link);
  evc_link_inc_refcnt(link);
  return 0;
}

/**
 * evc_link_new - Allocates New Event Connector Link
 *
 * Link reference count is set to 1 by this function
 * Return Value: pointer to the new link or %NULL.
 */
evc_link_t *evc_link_new(void)
{
  evc_link_t *link;
  
  link=malloc(sizeof(evc_link_t));
  if(link==NULL) return NULL;

  evc_link_init(link);
  link->malloced=1;
  return link;
}

/**
 * evc_link_connect - Connects Link between Two Hubs
 * @link:	pointer to the non-connected initialized link
 * @src:	pointer to the source hub of type &evc_tx_hub_t
 * @dst:	pointer to the destination hub of type &evc_rx_hub_t
 * @prop:	propagation function corresponding to source and destination
 *		expected event arguments
 *
 * If ready flag is not set, link state is set to ready
 * and reference count is increased.
 * Return Value: negative return value indicates fail.
 */
int evc_link_connect(evc_link_t *link, evc_tx_hub_t *src, evc_rx_hub_t *dst,
                     evc_prop_fnc_t *prop)
{
  int link_ready;

  if(!link || !src || !dst || !prop) return -1;
  if(link->standalone) return -1;
  evc_link_inc_refcnt(link);
  link->delete_pend=0;
  link->propagate=prop;
  link->src.hub=src;
  evc_tx_list_insert(src,link);
  link->dst.multi.hub=dst;
  evc_rx_list_insert(dst,link);
  link_ready=link->ready;
  link->ready=1;
  if(link_ready){
    evc_link_dec_refcnt(link);
  }

  return 0;
}

/**
 * evc_link_init_standalone - Initialize Standalone Link
 * @link:	pointer to the link
 * @rx_fnc:	pointer to the function invoked by event reception
 * @context:	context for the rx_fnc() function invocation
 *
 * Link reference count is set to 1 by this function
 * Return Value: negative value informs about failure.
 */
int evc_link_init_standalone(evc_link_t *link, evc_rx_fnc_t *rx_fnc, void *context)
{
  memset(link,0,sizeof(evc_link_t));
  evc_rx_list_init_detached(link);
  link->standalone=1;
  link->dst.standalone.rx_fnc=rx_fnc;
  link->dst.standalone.context=context;
  evc_link_inc_refcnt(link);
  return 0;
}

/**
 * evc_link_new_standalone - Allocates New Standalone Link
 * @rx_fnc:   callback function invoked if event is delivered
 * @context:  context provided to the callback function
 *
 * Link reference count is set to 1 by this function
 * Return Value: pointer to the new link or %NULL.
 */
evc_link_t *evc_link_new_standalone(evc_rx_fnc_t *rx_fnc, void *context)
{
  evc_link_t *link;
  
  link=malloc(sizeof(evc_link_t));
  if(link==NULL) return NULL;

  evc_link_init_standalone(link, rx_fnc, context);
  link->malloced=1;
  return link;
}

/**
 * evc_link_connect_standalone - Connects Standalone Link to Source Hubs
 * @link:	pointer to the non-connected initialized link
 * @src:	pointer to the source hub of type &evc_tx_hub_t
 * @prop:	propagation function corresponding to hub source
 *		and standalone rx_fnc() expected event arguments
 *
 * If ready flag is not set, link state is set to ready
 * and reference count is increased.
 * Return Value: negative return value indicates failure.
 */
int evc_link_connect_standalone(evc_link_t *link, evc_tx_hub_t *src,
           evc_prop_fnc_t *prop)
{
  int link_ready;

  if(!link || !src || !prop) return -1;
  if(!link->standalone) return -1;
  evc_link_inc_refcnt(link);
  link->delete_pend=0;
  link->propagate=prop;
  link->src.hub=src;
  evc_tx_list_insert(src,link);
  link_ready=link->ready;
  link->ready=1;
  if(link_ready){
    evc_link_dec_refcnt(link);
  }

  return 0;
}


static inline
void evc_link_do_delete(evc_link_t *link)
{
  evc_tx_list_del_item(link);
  link->src.hub=NULL;
  if(!link->standalone){
    evc_rx_list_del_item(link);
    link->dst.multi.hub=NULL;
  }
  link->delete_pend=0;
}


/**
 * evc_link_delete - Deletes Link from Hubs Lists
 * @link:	pointer to the possibly connected initialized link
 *
 * If ready flag is set, link ready flag is cleared and reference count
 * is decreased. This could lead to link disappear, if nobody is holding
 * reference.
 * Return Value: positive return value indicates immediate delete,
 *		zero return value informs about delayed delete.
 */
int evc_link_delete(evc_link_t *link)
{
  int res;
  link->delete_pend=1;
  if(!link->taken){
    evc_link_do_delete(link);
    res=1;
  } else {
    res=0;
  }
  if(link->ready){
    link->ready=0;
    evc_link_dec_refcnt(link);
  }
  return res;
}

/**
 * evc_link_dispose - Disposes Link
 * @link:	pointer to the possibly connected initialized link
 *
 * Deletes link from hubs, marks it as dead, calls final death propagate()
 * for the link and if link is @malloced, releases link occupied memory.
 */
void evc_link_dispose(evc_link_t *link)
{
  if(!link->dead){
    link->dead=1;
    if(!link->propagate(link,0)){
      evc_link_inc_refcnt(link);
      evc_link_delete(link);
      if(link->malloced) free(link);
      else if(link->refcnt>0) link->refcnt--;
    }
  }
}
 
/**
 * evc_tx_hub_init - Initializes Event Transmition Hub
 * @hub:	pointer to the &evc_tx_hub_t type hub
 *
 * Return Value: negative return value indicates failure.
 */
int evc_tx_hub_init(evc_tx_hub_t *hub)
{
  evc_tx_list_init_head(hub);
  return 0;
}

/**
 * evc_tx_hub_done - Initializes Event Transmition Hub
 * @hub:	pointer to the &evc_tx_hub_t type hub
 */
void evc_tx_hub_done(evc_tx_hub_t *hub)
{
  evc_link_t *link;
  ul_list_for_each_cut(evc_tx_list, hub, link){
    evc_link_inc_refcnt(link);
    link->src.hub=NULL;
    evc_link_delete(link);
    evc_link_dec_refcnt(link);
  }
}

static inline
int evc_prop_link_skip(evc_link_t *link)
{
  if(link->dead || link->blocked || link->delete_pend) return 1;
  if(!link->recursive && link->taken) return 1;
  return 0;
}

/**
 * evc_tx_hub_propagate - Propagate Event to Links Destinations
 * @hub:	pointer to the &evc_tx_hub_t type hub
 * @args:	pointer to the variable arguments list
 *
 * The function propagates event to the connected links,
 * it skips links marked as @dead, @blocked or @delete_pend.
 * If the link is not marked as @recursive, it ensures, that
 * link is not called twice.
 */
void evc_tx_hub_propagate(evc_tx_hub_t *hub, va_list args)
{
  evc_link_t *link, *next;
  
  link=evc_tx_list_first(hub);
  while(1){
    if(!link) return;
    if(!evc_prop_link_skip(link)) break;
    link=evc_tx_list_next(hub,link);
  }
  evc_link_inc_refcnt(link);	/*prevents dispose of the link*/
  link->taken++;		/*prevents delete of the link from the hub*/
  do{
    link->propagate(link, args);

    if(link->src.hub == hub){	/*check for correct hub relation*/
      next=link;
      while(1){
        next=evc_tx_list_next(hub,next);
        if(!next) break;
        if(!evc_prop_link_skip(next)) {
          evc_link_inc_refcnt(next); /*prevents dispose of the link*/
          next->taken++;	/*prevents delete of the link from the hub*/
          break;
        }
      }
    }else{
      /* link is already disconnected from the hub, 
         result of evc_tx_hub_done  */
      next=NULL;
    }
    if(!(--link->taken)){
      if(link->delete_pend)
        evc_link_delete(link);	/*process pending delete*/
    }
    evc_link_dec_refcnt(link);	/*can lead to dispose of the link*/
  }while((link=next));
}

/**
 * evc_tx_hub_emit - Emits Event to Hub
 * @hub:	pointer to the &evc_tx_hub_t type hub
 *
 * The function hands over arguments to evc_tx_hub_propagate()
 * as &va_list.
 */
void evc_tx_hub_emit(evc_tx_hub_t *hub, ...)
{
  va_list args;

  va_start (args, hub);
  evc_tx_hub_propagate(hub, args);
  va_end (args);
}

int evc_tx_hub_eol_weakptr(evc_tx_hub_t *hub, void **weakptr)
{
  evc_link_t *link, *next;
  int ret=0;
  
  link=evc_tx_list_first(hub);
  while(link){
    next=evc_tx_list_next(hub,link);
    if(link->standalone && (link->dst.standalone.weakptr==weakptr)){
      link->dst.standalone.weakptr=NULL;
      evc_link_delete(link);
      ret++;
    }
    link=next;
  }
  return ret;
}


/**
 * evc_rx_hub_init - Initializes Event Receiption Hub
 * @hub:	pointer to the &evc_rx_hub_t type hub
 * @rx_fnc:	pointer to the function invoked by event reception
 * @context:	context for the rx_fnc() function invocation
 *
 * Return Value: negative return value indicates failure.
 */
int evc_rx_hub_init(evc_rx_hub_t *hub, evc_rx_fnc_t *rx_fnc, void *context)
{
  evc_rx_list_init_head(hub);
  hub->rx_fnc=rx_fnc;
  hub->context=context;
  return 0;
}

/**
 * evc_rx_hub_done - Finalize Event Receiption Hub
 * @hub:	pointer to the &evc_rx_hub_t type hub
 */
void evc_rx_hub_done(evc_rx_hub_t *hub)
{
  evc_link_t *link;
  ul_list_for_each_cut(evc_rx_list, hub, link){
    evc_link_inc_refcnt(link);
    link->dst.multi.hub=NULL;
    evc_link_delete(link);
    evc_link_dec_refcnt(link);
  }
}

/*===========================================================*/
/* arguments propagation functions */

typedef int evc_fnc_propagate_i_p_l(void *, long);

int evc_link_propagate_i_p_l(evc_link_t *link, va_list args)
{
  void *context;
  evc_fnc_propagate_i_p_l *fnc;
  
  if(link->dead){
    if(link->standalone && link->dst.standalone.weakptr){
      *link->dst.standalone.weakptr=NULL;
      link->dst.standalone.weakptr=NULL;
    }
    return 0;
  }
  if(link->standalone){
    fnc=(evc_fnc_propagate_i_p_l*)link->dst.standalone.rx_fnc;
    context=link->dst.standalone.context;
  }else{
    if(!link->dst.multi.hub) return 0;
    fnc=(evc_fnc_propagate_i_p_l*)link->dst.multi.hub->rx_fnc;
    context=link->dst.multi.hub->context;
  }
  if(fnc)
    fnc(context, va_arg(args, long));
  return 0;
}

