/*
  event.c
*/
#include <stdlib.h>
#include <string.h>
#include "iiimcfint.h"

#define IIIMCF_EVENT_QUEUE_INITIAL_SLOTS_SIZE 4
#define IIIMCF_EVENT_STACK_INITIAL_SLOTS_SIZE 4

/* #define IIIMCF_EVENT_STACK */

enum IIIMCF_EVENT_STATE_FLAGS {
	IIIMCF_EVENT_STATE_MUSTIGNORE = (1 << 0),
	IIIMCF_EVENT_STATE_DISPATCHING = (1 << 1),
	IIIMCF_EVENT_STATE_INQUEUE = (1 << 2),
	IIIMCF_EVENT_STATE_BROADCASTING = (1 << 3)
};
#define IIIMCF_EVENT_SET_STATE(pe, flag) ((pe)->state |= (flag))
#define IIIMCF_EVENT_RESET_STATE(pe, flag) ((pe)->state &= (~(flag)))
#define IIIMCF_EVENT_IS_DISCARDABLE(pe) ((pe)->state == 0)

static void
delete_event(
    IIIMCF_event_rec *pe
)
{
    switch (pe->type) {
      case IIIMCF_EVENT_TYPE_AUX_START:
      case IIIMCF_EVENT_TYPE_AUX_SETVALUES:
      case IIIMCF_EVENT_TYPE_AUX_GETVALUES:
      case IIIMCF_EVENT_TYPE_AUX_DRAW:
      case IIIMCF_EVENT_TYPE_AUX_DONE:
       iiimcf_delete_aux_event(pe);
       break;

      default:
       break;
    }
    free(pe);
}

/********************************************************************************
		           event queue and stack
********************************************************************************/

static int
expand_event_queue(
    IIIMCF_context_rec *pc
)
{
    int nsize;
    IIIMCF_event_rec **ppev;
    if (pc->evqueue_size == 0) {
	nsize = IIIMCF_EVENT_QUEUE_INITIAL_SLOTS_SIZE;
	ppev = (IIIMCF_event_rec**) malloc(sizeof(*ppev) * nsize);
	if (!ppev) return 0;
	memset(ppev, 0, sizeof(*ppev) * nsize);
	pc->ppevqueue = ppev;
	pc->ppev_pro = ppev;
	pc->ppev_con = ppev;
    } else {
	int osize = pc->evqueue_size;
	IIIMCF_event_rec **ppev_ncon, **ppev_npro;

	nsize = osize * 2;
	ppev = (IIIMCF_event_rec**) realloc(pc->ppevqueue, sizeof(*ppev) * nsize);
	if (!ppev) return 0;
	if (pc->ppev_con == (pc->ppev_pro + 1)) {
	    /*       p c                               p         c
		     v v                               v         v
	      +-----------------------+         +-------------------------------+
	      |X|X|X|X|X|X|X|X|X|X|X|X|   ==>   |X|X|X|X| | | | |X|X|X|X|X|X|X|X|
	      +-----------------------+	        +-------------------------------+
                                                 ^               ^
                                                 ppev            ppev + nsize
                                                             - (osize - (ppev_con - ppevqueue))
	    */
	    ppev_ncon = ppev + nsize - (osize - (pc->ppev_con - pc->ppevqueue));
	    ppev_npro = ppev + (pc->ppev_pro - pc->ppevqueue);
	    memmove(ppev_ncon, ppev_npro + 1, sizeof(*ppev) * (nsize - (ppev_ncon - ppev)));
	    memset(ppev_npro + 1, 0, sizeof(*ppev) * (ppev_ncon - ppev_npro - 1));
	} else if ((pc->ppev_pro == pc->ppevqueue + osize)
		   && (pc->ppev_con == pc->ppevqueue)) {
	    /* c                       p         c                       p
	       v                       v         v                       v
	      +-----------------------+         +-------------------------------+
	      |X|X|X|X|X|X|X|X|X|X|X|X|   ==>   |X|X|X|X|X|X|X|X|X|X|X|X| | | | |
	      +-----------------------+         +-------------------------------+
	    */
	    ppev_ncon = ppev;
	    ppev_npro = ppev + osize;
	    memset(ppev_npro, 0, sizeof(*ppev) * (nsize - osize));
	} else {
	    /* queue has still some empty rooms!!! */
	    abort();
	}
	pc->ppevqueue = ppev;
	pc->ppev_pro = ppev_npro;
	pc->ppev_con = ppev_ncon;
    }

    pc->evqueue_size = nsize;
    return 1;
}

IIIMF_status
iiimcf_store_event(
    IIIMCF_context_rec *pc,
    IIIMCF_event_rec *pe
)
{
    int size = pc->evqueue_size;

    if ((!pc->ppevqueue)
	|| ((pc->ppev_pro + 1) == pc->ppev_con)) {
	if (!expand_event_queue(pc))
	    return IIIMF_STATUS_MALLOC;
    }
    *pc->ppev_pro = pe;
    pc->ppev_pro++;
    if (pc->ppev_pro == pc->ppevqueue + size) {
	if (pc->ppev_con == pc->ppevqueue) {
	    if (!expand_event_queue(pc))
		return IIIMF_STATUS_MALLOC;
	} else {
	    pc->ppev_pro = pc->ppevqueue;
	}
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMCF_event_rec*
iiimcf_get_event(
    IIIMCF_context_rec *pc,
    int removep
)
{
    IIIMCF_event_rec *pe;

    if (pc->ppev_con == pc->ppev_pro) return NULL;

    pe = *pc->ppev_con;

    if (removep) {
	*pc->ppev_con = NULL;
	if (pc->ppev_con == (pc->ppevqueue + pc->evqueue_size - 1))
	    pc->ppev_con = pc->ppevqueue;
	else
	    pc->ppev_con++;
    }

    return pe;
}

#ifdef IIIMCF_EVENT_STACK

static int
expand_event_stack(
    IIIMCF_context_rec *pc
)
{
    int nsize = pc->evstack_size;
    IIIMCF_event_rec **ppev;

    ASSERT((pc->ppevstack + nsize)  == pc->ppev_sp);

    if (nsize == 0) nsize = IIIMCF_EVENT_STACK_INITIAL_SLOTS_SIZE;
    else nsize *= 2;

    ppev = (IIIMCF_event_rec**) realloc(pc->ppevstack, sizeof(*ppev) * nsize);
    if (!ppev) return 0;
    memset(ppev + pc->evstack_size, 0, sizeof(*ppev) * (nsize - pc->evstack_size));
    pc->ppev_sp = ppev + pc->evstack_size;
    pc->ppevstack = ppev;
    pc->evstack_size = nsize;

    return 1;
}

IIIMF_status
iiimcf_push_event(
    IIIMCF_context_rec *pc,
    IIIMCF_event_rec *pe
)
{
    int nsize = pc->evstack_size;

    if (pc->ppev_sp == (pc->ppevstack + nsize)) {
	if (!expand_event_stack(pc))
	    return IIIMF_STATUS_MALLOC;
    }
    *pc->ppev_sp = pe;
    pc->ppev_sp++;

    return IIIMF_STATUS_SUCCESS;
}

IIIMCF_event_rec*
iiimcf_pop_event(
    IIIMCF_context_rec *pc
)
{
    IIIMCF_event_rec *pe;

    if (pc->ppevstack == pc->ppev_sp) return NULL;

    --pc->ppev_sp;
    pe = *pc->ppev_sp;
    *pc->ppev_sp = NULL;

    return pe;
}

#endif /* IIIMCF_EVENT_STACK */

/********************************************************************************
		           internal services
********************************************************************************/

IIIMCF_event_rec*
iiimcf_make_event(
    IIIMCF_event_type type
)
{
    IIIMCF_event_rec *pe;

    pe = (IIIMCF_event_rec*) malloc(sizeof(*pe));
    if (!pe) return NULL;

    memset(pe, 0, sizeof(*pe));
    pe->type = type;

    return pe;
}

IIIMF_status
iiimcf_store_simple_event(
    IIIMCF_context_rec *pc,
    IIIMCF_event_type type
)
{
    IIIMCF_event_rec *pe;

    pe = iiimcf_make_event(type);
    if (!pe) return IIIMF_STATUS_MALLOC;

    return iiimcf_store_event(pc, pe);
}


IIIMF_status
iiimcf_delete_event_storage(
    IIIMCF_context_rec *pc
)
{
    int i;
    IIIMCF_event_rec **ppe;

    if (pc->ppevqueue) {
	for (ppe = pc->ppevqueue, i = 0; i < pc->evqueue_size; i++, ppe++) {
	    if (*ppe) delete_event(*ppe);
	}
	free(pc->ppevqueue);
	pc->ppevqueue = pc->ppev_pro = pc->ppev_con = NULL;
	pc->evqueue_size = 0;
    }
#ifdef IIIMCF_EVENT_STACK
    if (pc->ppevstack) {
	for (ppe = ppevstack; ppe < pc->ppev_sp; ppe++) {
	    if (*ppe) delete_event(*ppe);
	}
	free(pc->ppevstack);
	pc->ppevstack = pc->ppev_sp = NULL;
	pc->evstack_size = 0;
    }
#endif

    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
broadcast(
    IIIMCF_context_rec *pc,
    IIIMCF_event_rec *pe,
    IIIMCF_component_rec *pcom,
    IIIMCF_component_rec *pcom_parent
)
{
    IIIMF_status st1, st2;

    st2 = IIIMF_STATUS_SUCCESS;
    for (; pcom; pcom = pcom->pnext) {
	st1 = (*pcom->func)(pc, pe, pcom, pcom_parent);
	if ((st1 != IIIMF_STATUS_SUCCESS)
	    && (st1 != IIIMF_STATUS_COMPONENT_INDIFFERENT))
	    st2 = st1;
	if (pcom->pchild) {
	    st1 = broadcast(pc, pe, pcom->pchild, pcom);
	    if ((st1 != IIIMF_STATUS_SUCCESS)
		&& (st1 != IIIMF_STATUS_COMPONENT_INDIFFERENT))
		st2 = st1;
	}
    }
    return st2;
}

IIIMF_status
iiimcf_broadcast_event(
    IIIMCF_context_rec *pc,
    IIIMCF_event_rec *pe
)
{
    IIIMF_status st;

    IIIMCF_EVENT_SET_STATE(pe, IIIMCF_EVENT_STATE_BROADCASTING);
    IIIMCF_SET_STATE(pc, IIIMCF_CONTEXT_BROADCASTING);
    st = broadcast(pc, pe, pc->ph->proot_component, NULL);
    IIIMCF_RESET_STATE(pc, IIIMCF_CONTEXT_BROADCASTING);
    IIIMCF_EVENT_RESET_STATE(pe, IIIMCF_EVENT_STATE_BROADCASTING);

    if (IIIMCF_EVENT_IS_DISCARDABLE(pe)) delete_event(pe);

    return st;
}

/********************************************************************************
			            APIs
********************************************************************************/

IIIMF_status
iiimcf_get_event_type(
    IIIMCF_event event,
    IIIMCF_event_type *pevent_type
)
{
    IIIMCF_event_rec *pe = (IIIMCF_event_rec*) event;
 
   *pevent_type = pe->type;
    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_create_keyevent(
    const IIIMCF_keyevent *pkeyevent,
    IIIMCF_event *pevent
)
{
    IIIMCF_event_rec *pe;

    pe = iiimcf_make_event(IIIMCF_EVENT_TYPE_KEYEVENT);
    if (!pe) return IIIMF_STATUS_MALLOC;

    pe->v.keyevent = *pkeyevent;

    *pevent = pe;

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_get_keyevent_value(
    IIIMCF_event event,
    IIIMCF_keyevent *pkeyevent
)
{
    IIIMCF_event_rec *pe = (IIIMCF_event_rec*) event;
    if (pe->type != IIIMCF_EVENT_TYPE_KEYEVENT)
	return IIIMF_STATUS_ARGUMENT;

    *pkeyevent = pe->v.keyevent;
    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
forward_keyevent(
    IIIMCF_context_rec *pc,
    IIIMCF_event_rec *pe
)
{
    IIIMF_status st;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMP_data_s *pds = ph->data_s;
    IIIMCF_keyevent *pk = &pe->v.keyevent;
    IIIMP_keyevent ikev;
    IIIMP_keyevent_list *pikl;
    IIIMP_contents *pcon;
    IIIMP_message *pmes;

    if (IIIMCF_IS_ENABLED(pc, IIIMCF_CONTEXT_AUTOMATIC_TRIGGER_NOTIFY)) {
        if (ph->server_protocol_version >= 3)
	  st = iiimcf_process_hotkey_keyevent(pc, pk);
        else if (ph->server_protocol_version < 3)
	  st = iiimcf_process_trigger_keyevent(pc, pk);
	if (st == IIIMF_STATUS_SUCCESS) return st;
	if (st != IIIMF_STATUS_NOT_TRIGGER_KEY) return st;
    }

    if (!IIIMCF_IS_STATIC_EVENT_FLOW(ph)
	&& !IIIMCF_IS_ENABLED(pc, IIIMCF_CONTEXT_CONVERSION_MODE))
	  return IIIMF_STATUS_EVENT_NOT_FORWARDED;

    ikev.keycode = pk->keycode;
    ikev.keychar = pk->keychar;
    ikev.modifier = pk->modifier;
    ikev.time_stamp = pk->time_stamp;

    pikl = iiimp_keyevent_list_new(pds, 1, &ikev);
    if (!pikl) return IIIMF_STATUS_MALLOC;

    pcon = iiimp_contents_keyevent_list_new(pds, pikl);
    if (!pcon) {
	iiimp_keyevent_list_delete(pds, pikl);
	return IIIMF_STATUS_MALLOC;
    }

    pmes = iiimp_forward_event_new(pds, ph->im_id, pc->ic_id, pcon);
    if (!pmes) {
	iiimp_contents_delete(pds, pcon);
	return IIIMF_STATUS_MALLOC;
    }
    st = iiimcf_request_message(ph, pmes, pc, IM_FORWARD_EVENT_REPLY, NULL);
    
    return st;
}

static IIIMF_status
forward_seticfocus(
    IIIMCF_context_rec *pc
)
{
    IIIMF_status st;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMP_data_s *pds = ph->data_s;
    IIIMP_message *pmes;

    pmes = iiimp_seticfocus_new(pds, ph->im_id, pc->ic_id);
    if (!pmes) return IIIMF_STATUS_MALLOC;
    st = iiimcf_request_message(ph, pmes, pc, IM_SETICFOCUS_REPLY, NULL);
    
    return st;
}

IIIMF_status
iiimcf_create_seticfocus_event(
    IIIMCF_event *pevent
)
{
    IIIMCF_event_rec *pe;

    pe = iiimcf_make_event(IIIMCF_EVENT_TYPE_SETICFOCUS);
    if (!pe) return IIIMF_STATUS_MALLOC;
    *pevent = pe;

    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
forward_unseticfocus(
    IIIMCF_context_rec *pc
)
{
    IIIMF_status st;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMP_data_s *pds = ph->data_s;
    IIIMP_message *pmes;

    pmes = iiimp_unseticfocus_new(pds, ph->im_id, pc->ic_id);
    if (!pmes) return IIIMF_STATUS_MALLOC;
    st = iiimcf_request_message(ph, pmes, pc, IM_UNSETICFOCUS_REPLY, NULL);
    
    return st;
}

IIIMF_status
iiimcf_create_unseticfocus_event(
    IIIMCF_event *pevent
)
{
    IIIMCF_event_rec *pe;

    pe = iiimcf_make_event(IIIMCF_EVENT_TYPE_UNSETICFOCUS);
    if (!pe) return IIIMF_STATUS_MALLOC;
    *pevent = pe;

    return IIIMF_STATUS_SUCCESS;
}

/*
 * Forwarding Event to IIIMSF.
 * event is deleted unless its state is marked as DISCARDABLE
 */
IIIMF_status
iiimcf_forward_event(
    IIIMCF_context context,
    IIIMCF_event event
)
{
    IIIMF_status st;
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_event_rec *pe = (IIIMCF_event_rec*) event;

    if (IIIMCF_IS_IC_INVALID(pc)) {
	st = iiimcf_enable_context(pc);
	if (st != IIIMF_STATUS_SUCCESS) {
            if (IIIMCF_EVENT_IS_DISCARDABLE(pe)) delete_event(pe);
	    return IIIMF_STATUS_IC_INVALID;
        }
    }

    IIIMCF_RESET_STATE_CHANGE(pc,
			      IIIMCF_STATE_PREEDIT_CHANGED
			      | IIIMCF_STATE_LOOKUP_CHOICE_CHANGED
			      | IIIMCF_STATE_STATUS_CHANGED
			      | IIIMCF_STATE_COMMIT_REQUIRED);
    switch (pe->type) {
      case IIIMCF_EVENT_TYPE_KEYEVENT:
       st = forward_keyevent(pc, pe);
       break;
      case IIIMCF_EVENT_TYPE_TRIGGER_NOTIFY:
       st = iiimcf_forward_trigger_notify(pc, pe->v.number);
       break;
      case IIIMCF_EVENT_TYPE_AUX_SETVALUES:
       st = iiimcf_forward_aux_setvalues(pc, pe);
       break;
      case IIIMCF_EVENT_TYPE_AUX_GETVALUES:
       st = iiimcf_forward_aux_getvalues(pc, pe);
       break;
      case IIIMCF_EVENT_TYPE_SETICFOCUS:
       st = forward_seticfocus(pc);
       break;
      case IIIMCF_EVENT_TYPE_UNSETICFOCUS:
       st = forward_unseticfocus(pc);
       break;
      default:
       st = IIIMF_STATUS_EVENT_NOT_FORWARDED;
    }
    if (IIIMCF_EVENT_IS_DISCARDABLE(pe)) delete_event(pe);

    return st;
}

IIIMF_status
iiimcf_receive_forwarded_event(
    IIIMCF_context_rec *pc,
    IIIMP_message *pmes
)
{
    IIIMF_status st;
    IIIMP_contents *pcon = pmes->v.forward_event.contents;
    ASSERT(pmes->opcode == IM_FORWARD_EVENT);

    switch (pcon->type) {
      case IIIMP_CONTENTS_KEYEVENT:
      {
	  int i, n;
	  IIIMCF_event_rec *pev;
	  IIIMCF_keyevent kev;
	  IIIMP_keyevent_list *pkl = pcon->value.keyevent_list;
	  IIIMP_keyevent *pimkev = pkl->keyevent;
	  
	  n = pkl->count;
	  for (i = 0; i < n; i++, pimkev++) {
	      kev.keycode = pimkev->keycode;
	      kev.keychar = pimkev->keychar;
	      kev.modifier = pimkev->modifier;
	      kev.time_stamp = pimkev->time_stamp;
	      st = iiimcf_create_keyevent(&kev, (IIIMCF_event*) &pev);
	      if (st != IIIMF_STATUS_SUCCESS) return st;
	      st = iiimcf_store_event(pc, pev);
	      if (st != IIIMF_STATUS_SUCCESS) {
		      delete_event(pev);
		      return st;
	      }
	  }
      }

      case IIIMP_CONTENTS_STRING:
      case IIIMP_CONTENTS_TEXT:
       /* currently ignore */
       break;
      default:
       abort();
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_dispatch_event(
    IIIMCF_context context,
    IIIMCF_event event
)
{
    IIIMF_status st1, st2;
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_event_rec *pe = (IIIMCF_event_rec*) event;
    IIIMCF_component_rec *pcom, *pcom_parent;

    /* When broadcasting event, event dispatchment
       must be inhibited because if we allow it, a component
       may receive the same broadcasting event more than once.
       Eventually, we return immediately with success.  */
    if (IIIMCF_IS_BROADCASTING(pc)) {
	if (IIIMCF_EVENT_IS_DISCARDABLE(pe)) delete_event(pe);
	return IIIMF_STATUS_SUCCESS;
    }

    pcom_parent = pc->pcurrent_component;
    if (pcom_parent) pcom = pcom_parent->pchild;
    else pcom = pc->ph->proot_component;

    IIIMCF_EVENT_SET_STATE(pe, IIIMCF_EVENT_STATE_DISPATCHING);

    st2 = IIIMF_STATUS_COMPONENT_INDIFFERENT;
    for (; pcom; pcom = pcom->pnext) {
	pc->pcurrent_component = pcom;
	st1 = (*pcom->func)(pc, pe, pcom, pcom_parent);
	if (st1 == IIIMF_STATUS_SUCCESS) {
	    if (st2 == IIIMF_STATUS_COMPONENT_INDIFFERENT) {
		st2 = IIIMF_STATUS_SUCCESS;
	    }
	} else if (st1 == IIIMF_STATUS_COMPONENT_FAIL) {
	    st2 = st1;
	} else if (st1 != IIIMF_STATUS_COMPONENT_INDIFFERENT) {
	    /* fatal error.  immediately quit.*/
	    st2 = st1;
	    break;
	}
    }
    pc->pcurrent_component = pcom_parent;

    IIIMCF_EVENT_RESET_STATE(pe, IIIMCF_EVENT_STATE_DISPATCHING);

    if (IIIMCF_EVENT_IS_DISCARDABLE(pe)) delete_event(pe);

    return st2;
}

IIIMF_status
iiimcf_get_next_event(
    IIIMCF_context context,
    IIIMCF_event *pevent
)
{
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_event_rec *pe;

    pe = iiimcf_get_event(pc, 1);
    if (!pe) return IIIMF_STATUS_NO_EVENT;
    *pevent = pe;
    IIIMCF_EVENT_RESET_STATE(pe, IIIMCF_EVENT_STATE_INQUEUE);
    IIIMCF_EVENT_SET_STATE(pe, IIIMCF_EVENT_STATE_MUSTIGNORE);

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_peek_next_event(
    IIIMCF_context context,
    IIIMCF_event *pevent
)
{
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_event_rec *pe;

    pe = iiimcf_get_event(pc, 0);
    if (!pe) return IIIMF_STATUS_NO_EVENT;
    *pevent = pe;
    IIIMCF_EVENT_SET_STATE(pe, IIIMCF_EVENT_STATE_INQUEUE);

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_ignore_event(
    IIIMCF_event event
)
{
    IIIMCF_event_rec *pe = (IIIMCF_event_rec*) event;

    IIIMCF_EVENT_RESET_STATE(pe, IIIMCF_EVENT_STATE_MUSTIGNORE);
    if (IIIMCF_EVENT_IS_DISCARDABLE(pe)) delete_event(pe);

    return IIIMF_STATUS_SUCCESS;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
