/*
  context.c
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

#include "iiimcfint.h"

IIIMCF_context_rec*
iiimcf_lookup_context(
    IIIMCF_handle_rec *ph,
    IIIMP_card16 ic_id
)
{
    int i;
    IIIMCF_context_rec *pc;
    IIIMCF_context_rec **pptbl;

    pptbl = ph->ppcontext_table;
    i = ic_id % ph->context_table_size;

    for (pc = pptbl[i]; pc; pc = pc->pnext) {
	if (pc->ic_id == ic_id) return pc;
    }

    return NULL;
}

static IIIMF_status
add_context(
    IIIMCF_handle_rec *ph,
    IIIMCF_context_rec *pc
)
{
    int i;
    IIIMCF_context_rec *pc2;
    IIIMCF_context_rec **pptbl;

    pptbl = ph->ppcontext_table;
    i = pc->ic_id % ph->context_table_size;

    IIIMCF_LOCK_HANDLE(ph);
    pc2 = pptbl[i];
    if (!pc2) {
	pptbl[i] = pc;
    } else {
	pc->pnext = pc2;
	pptbl[i] = pc;
    }
    IIIMCF_UNLOCK_HANDLE(ph);

    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
remove_context(
    IIIMCF_context_rec *pc
)
{
    int i;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMCF_context_rec *pc1, *pc2;
    IIIMCF_context_rec **pptbl;

    pptbl = ph->ppcontext_table;
    i = pc->ic_id % ph->context_table_size;

    IIIMCF_LOCK_HANDLE(ph);
    for (pc1 = pptbl[i], pc2 = NULL; pc1; pc1 = pc1->pnext) {
	if (pc1 == pc) {
	    if (pc2) {
		pc2->pnext = pc1->pnext;
	    } else {
		pptbl[i] = pc1->pnext;
	    }
	    IIIMCF_UNLOCK_HANDLE(ph);
	    return IIIMF_STATUS_SUCCESS;
	}
	pc2 = pc1;
    }
    IIIMCF_UNLOCK_HANDLE(ph);

    return IIIMF_STATUS_IC_INVALID;
}

static IIIMF_status
reset_context(
    IIIMCF_context_rec *pc,
    int full
)
{
    IIIMF_status st;
    IIIMCF_event_rec *pe;

    /* Broadcast to all components that pc must be reset.  */
    pe = iiimcf_make_event(IIIMCF_EVENT_TYPE_RESET);
    if (pe) {
	st = iiimcf_broadcast_event(pc, pe);
    } else {
	st = IIIMF_STATUS_MALLOC; 
    }

    iiimcf_destruct_text(&pc->preedit_text);
    memset(&pc->preedit_text, 0, sizeof(pc->preedit_text));
    pc->preedit_caret_position = 0;
    iiimcf_destruct_text(&pc->status_text);
    memset(&pc->status_text, 0, sizeof(pc->status_text));
    iiimcf_destruct_text(&pc->committed_text);
    memset(&pc->committed_text, 0, sizeof(pc->committed_text));
    iiimcf_destruct_lookup_choice(&pc->lookup_choice);
    memset(&pc->lookup_choice, 0, sizeof(pc->lookup_choice));
    /* Should we clear AUX state? */

    IIIMCF_RESET_STATE(pc,
		       IIIMCF_CONTEXT_PREEDIT_ENABLED
		       | IIIMCF_CONTEXT_LOOKUP_CHOICE_ENABLED
		       | IIIMCF_CONTEXT_STATUS_ENABLED
		       | IIIMCF_CONTEXT_COMMITTED_TEXT_ENABLED);
    
    if (full) {
	/* Notice that conversion mode is persistent even
	   after connection shutdown.  So avoid resetting
	   IIIMCF_CONTEXT_CONVERSION_MODE.  */
	IIIMCF_RESET_STATE(pc, IIIMCF_CONTEXT_CONVERSION_MODE);
    }

    IIIMCF_SET_STATE_CHANGE(pc,
			    IIIMCF_STATE_INVALIDATED
			    | IIIMCF_STATE_STATUS_CHANGED
			    | IIIMCF_STATE_LOOKUP_CHOICE_CHANGED
			    | IIIMCF_STATE_PREEDIT_CHANGED);

    /* remove all events from the queue */
    iiimcf_delete_event_storage(pc);

    return st;
}

static IIIMF_status
invalidate_context(
    IIIMCF_context_rec *pc
)
{
    IIIMF_status st;

    if (IIIMCF_IS_IC_INVALID(pc)) return IIIMF_STATUS_SUCCESS;
    st = reset_context(pc, 0);
    remove_context(pc);
    pc->ic_id = -1;

    return st;
}

static void
free_icattribute(
    IIIMCF_context_rec *pc
)
{
    if (pc->icattr.lang_id) free(pc->icattr.lang_id);
    if (pc->icattr.imname) free(pc->icattr.imname);
}

static IIIMF_status
create_icattribute(
    IIIMCF_context_rec *pc,
    IIIMCF_attr attr
)
{
    IIIMF_status st;
    IIIMCF_language_rec *plang;
    IIIMCF_input_method_rec *pim;

    memset(&pc->icattr, 0, sizeof(IIIMCF_ICAttribute_rec));

    st = iiimcf_attr_get_ptr_value(attr, IIIMCF_ATTR_INPUT_LANGUAGE, (void**) &plang);
    if (st == IIIMF_STATUS_SUCCESS) {
	pc->icattr.lang_id = strdup(plang->lang_id);
	if (!pc->icattr.lang_id) return IIIMF_STATUS_MALLOC;
    } else if (st == IIIMF_STATUS_NO_ATTR_VALUE) {
        /* client doesn't specify language */
#ifdef HAVE_SETLOCALE
        /* when we have setlocale function, try to detect most prefferable language from current locale */
        const char *lang = setlocale(LC_CTYPE, NULL);
        int i = 0;
        IIIMCF_language_rec **pplangs = pc->ph->pplangs;
        if (lang) {
            for (; i < pc->ph->num_langs; ++i) {
                if (!strncmp(pplangs[i]->lang_id, lang, strlen(pplangs[i]->lang_id))) {
                    pc->icattr.lang_id = strdup(pplangs[i]->lang_id);
                    st = IIIMF_STATUS_SUCCESS;
                    break;
                }
            }
        } else {
            goto error;
        }
#else
	goto error;
#endif
    } else {
        goto error;
    }

    st = iiimcf_attr_get_ptr_value(attr, IIIMCF_ATTR_INPUT_METHOD, (void**) &pim);
    if (st == IIIMF_STATUS_SUCCESS) {
	int len = iiimcf_string_length(pim->imname);
	pc->icattr.imname = (IIIMP_card16*) malloc(sizeof(IIIMP_card16) * len);
	if (!pc->icattr.imname) return IIIMF_STATUS_MALLOC;
	memcpy(pc->icattr.imname, pim->imname, len * sizeof(IIIMP_card16));
	pc->icattr.imname_len = len;
    } else if (st == IIIMF_STATUS_NO_ATTR_VALUE) {
	const char *imname;
	/* Using IIIMCF_ATTR_INPUT_METHOD_NAME is strongly depricated!!!
	   This attribute is for backward-compatibility.
	   Notice that it accepts only US-ASCII. */
	st = iiimcf_attr_get_string_value(attr,
					  IIIMCF_ATTR_INPUT_METHOD_NAME,
					  &imname);
	if (st == IIIMF_STATUS_SUCCESS) {
	    int i, len;
	    IIIMP_card16 *pu;

	    len = strlen(imname);
	    pu = (IIIMP_card16*) malloc(sizeof(IIIMP_card16) * len);
	    if (!pu) return IIIMF_STATUS_MALLOC;
	    pc->icattr.imname = pu;
	    pc->icattr.imname_len = len;
	    for (i = 0; i < len; i++) {
		if (((unsigned) *imname) > 0x7F) {
		    /* It's not US-ASCII! */
		    free(pc->icattr.imname);
		    return IIIMF_STATUS_ARGUMENT;
		}
		*pu++ = *imname++;
	    }
	} else if (st != IIIMF_STATUS_NO_ATTR_VALUE) {
	    goto error;
	}
    } else {
	goto error;
    }

    return IIIMF_STATUS_SUCCESS;

error:
    free_icattribute(pc);
    return st;
}

IIIMF_status
iiimcf_cleanup_context(
    IIIMCF_handle_rec *ph,
    int destroyp
)
{
    IIIMF_status st1, st2;
    IIIMCF_context_rec **ppc, *pc, *pc2;
    int i;

    st2 = IIIMF_STATUS_SUCCESS;
    for (ppc = ph->ppcontext_table, i = 0;
	 i < ph->context_table_size;
	 ppc++, i++) {
	for (pc = *ppc; pc; pc = pc2) {
	    pc2 = pc->pnext;
	    if (destroyp) {
		st1 = iiimcf_destroy_context(pc);
	    } else {
		st1 = invalidate_context(pc);
	    }
	    if (st1 != IIIMF_STATUS_SUCCESS) st2 = st1;
	}
    }

    return st2;
}

static IIIMF_status
create_iiimp_icattr(
    IIIMCF_context_rec *pc,
    IIIMP_icattribute **ppicattr
)
{
    IIIMF_status st;
    IIIMP_data_s *pds = pc->ph->data_s;
    IIIMP_string *pimstr;
    IIIMP_icattribute *pa, *pa_n;

    pa_n = NULL;
    if (pc->icattr.lang_id) {
	st = iiimf_data_string_ascii_new(pds, pc->icattr.lang_id, &pimstr);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	pa_n = pa = iiimp_icattribute_input_language_new(pds, pimstr);
	if (!pa) {
	    iiimp_string_delete(pds, pimstr);
	    return IIIMF_STATUS_MALLOC;
	}
    }
    if (pc->icattr.imname) {
	pimstr = iiimp_string_new(pds, pc->icattr.imname_len, pc->icattr.imname);
	if (!pimstr) {
	    iiimp_icattribute_list_delete(pds, pa_n);
	    return IIIMF_STATUS_MALLOC;
	}
	pa = iiimp_icattribute_input_method_name_new(pds, pimstr);
	if (!pa) {
	    iiimp_icattribute_list_delete(pds, pa_n);
	    iiimp_string_delete(pds, pimstr);
	    return IIIMF_STATUS_MALLOC;
	}
	pa->next = pa_n;
	pa_n = pa;
    }

    *ppicattr = pa_n;

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_enable_context(
    IIIMCF_context_rec *pc
)
{
    IIIMF_status st;
    IIIMP_message *pmes;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMP_icattribute *picattr;

    st = create_iiimp_icattr(pc, &picattr);
    if (st != IIIMF_STATUS_SUCCESS) return st;
    pmes = iiimp_createic_new(ph->data_s, ph->im_id, picattr);
    st = iiimcf_request_message(ph, pmes, NULL, IM_CREATEIC_REPLY, &pmes);
    if (st != IIIMF_STATUS_SUCCESS) return st;
    pc->ic_id = pmes->ic_id;
    iiimp_message_delete(ph->data_s, pmes);
    st = add_context(ph, pc);

    /* restore conversion mode */
    if (IIIMCF_IS_ENABLED(pc, IIIMCF_CONTEXT_CONVERSION_MODE)) {
	st = iiimcf_forward_trigger_notify(pc, 1);
	if (st != IIIMF_STATUS_SUCCESS) {
	    IIIMCF_RESET_STATE(pc, IIIMCF_CONTEXT_CONVERSION_MODE);
	    return st;
	}
    }

    return st;
}

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

IIIMF_status
iiimcf_create_context(
    IIIMCF_handle handle,
    IIIMCF_attr attr,
    IIIMCF_context *pcontext
)
{
    IIIMF_status st;
    IIIMCF_handle_rec *ph = (IIIMCF_handle_rec*) handle;
    IIIMCF_context_rec *pc;
    IIIMP_message *pmes;
    int auto_trigger_notify_flag;

    st = iiimcf_attr_get_integer_value(attr,
				       IIIMCF_ATTR_DISABLE_AUTOMATIC_TRIGGER_NOTIFY,
				       &auto_trigger_notify_flag);
    if (st == IIIMF_STATUS_NO_ATTR_VALUE) {
	auto_trigger_notify_flag = 1;
    } else if (st == IIIMF_STATUS_SUCCESS) {
	auto_trigger_notify_flag = !auto_trigger_notify_flag;
    } else {
	return st;
    }

    pc = (IIIMCF_context_rec*) malloc(sizeof(*pc));
    if (!pc) {
	iiimp_message_delete(ph->data_s, pmes);
	return IIIMF_STATUS_MALLOC;
    }
    memset(pc, 0, sizeof(*pc));
    pc->ph = ph;
    pc->ic_id = -1;
    st = create_icattribute(pc, attr);
    if (st != IIIMF_STATUS_SUCCESS) {
	free(pc);
	return st;
    }
    st = iiimcf_enable_context(pc);
    if (st != IIIMF_STATUS_SUCCESS) {
	free_icattribute(pc);
	free(pc);
	return st;
    }

    if (auto_trigger_notify_flag)
	IIIMCF_SET_STATE(pc, IIIMCF_CONTEXT_AUTOMATIC_TRIGGER_NOTIFY);
    else
	IIIMCF_RESET_STATE(pc, IIIMCF_CONTEXT_AUTOMATIC_TRIGGER_NOTIFY);

    *pcontext = pc;

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_seticvalues(
    IIIMCF_context context,
    IIIMCF_attr attr
)
{
    IIIMF_status st;
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMP_message *pmes;
    IIIMP_icattribute *picattr;
    int auto_trigger_notify_flag;

    st = iiimcf_attr_get_integer_value(attr,
                                       IIIMCF_ATTR_DISABLE_AUTOMATIC_TRIGGER_NOTIFY,
                                       &auto_trigger_notify_flag);
    if (st == IIIMF_STATUS_NO_ATTR_VALUE) {
        auto_trigger_notify_flag = 1;
    } else if (st == IIIMF_STATUS_SUCCESS) {
        auto_trigger_notify_flag = !auto_trigger_notify_flag;
    } else {
        return st;
    }
    st = create_icattribute(pc, attr);
    if (st != IIIMF_STATUS_SUCCESS)
	return st;

    st = create_iiimp_icattr(pc, &picattr);
    if (st != IIIMF_STATUS_SUCCESS) {
	free_icattribute(pc);
	return st;
    }

    pmes = iiimp_seticvalues_new(ph->data_s, ph->im_id, pc->ic_id, picattr);
    st = iiimcf_request_message(ph, pmes, NULL, IM_SETICVALUES_REPLY, &pmes);
    if (st != IIIMF_STATUS_SUCCESS) {
	free_icattribute(pc);
	return st;
    }

    /* restore conversion mode */
    if (IIIMCF_IS_ENABLED(pc, IIIMCF_CONTEXT_CONVERSION_MODE)) {
	st = iiimcf_forward_trigger_notify(pc, 1);
	if (st != IIIMF_STATUS_SUCCESS) {
	    IIIMCF_RESET_STATE(pc, IIIMCF_CONTEXT_CONVERSION_MODE);
	    return st;
	}
    }

    if (auto_trigger_notify_flag)
	IIIMCF_SET_STATE(pc, IIIMCF_CONTEXT_AUTOMATIC_TRIGGER_NOTIFY);
    else
	IIIMCF_RESET_STATE(pc, IIIMCF_CONTEXT_AUTOMATIC_TRIGGER_NOTIFY);

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_destroy_context(
    IIIMCF_context context
)
{
    IIIMF_status st1, st2;
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_handle_rec *ph = pc->ph;

    st2 = IIIMF_STATUS_SUCCESS;
    if (IIIMCF_IS_CONNECTED(ph)
	&& (!IIIMCF_IS_IC_INVALID(pc))) {
	IIIMP_message *pmes;

	pmes = iiimp_destroyic_new(ph->data_s, ph->im_id, pc->ic_id);
	if (pmes) {
	    st1 = iiimcf_request_message(ph, pmes, pc, IM_DESTROYIC_REPLY, NULL);
	    if (st1 != IIIMF_STATUS_SUCCESS) {
		st2 = st1;
	    }
	} else {
	    st2 = IIIMF_STATUS_MALLOC;
	}
    }

    /* Broadcast to all components that pc is now being destroyed.  */
    {
	IIIMCF_event_rec *pe;

	pe = iiimcf_make_event(IIIMCF_EVENT_TYPE_DESTROY);
	if (pe) {
	    st1 = iiimcf_broadcast_event(pc, pe);
	    if (st1 != IIIMF_STATUS_SUCCESS) st2 = st1;
	} else {
	    st2 = IIIMF_STATUS_MALLOC;
	}
    }

    if (IIIMCF_IS_IC_INVALID(pc))
	return IIIMF_STATUS_SUCCESS;

    remove_context(pc);
    iiimcf_delete_event_storage(pc);
    iiimcf_delete_all_aux_data(pc);
    iiimcf_destruct_text(&pc->preedit_text);
    iiimcf_destruct_text(&pc->status_text);
    iiimcf_destruct_text(&pc->committed_text);
    iiimcf_destruct_lookup_choice(&pc->lookup_choice);
    free_icattribute(pc);
    if (pc->attr) iiimcf_destroy_attr(pc->attr);

    free(pc);

    return st2;
}

IIIMF_status
iiimcf_is_UIstate_changed(
    IIIMCF_context context,
    int *pflag
)
{
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;

    *pflag = pc->state_change_flag;
    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_context_get_attr(
    IIIMCF_context context,
    IIIMCF_attr *pattr
)
{
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;

    if (!pc->attr) {
	IIIMF_status st;

	st = iiimcf_create_attr(&pc->attr);
	if (st != IIIMF_STATUS_SUCCESS) return st;
    }
    *pattr = pc->attr;

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_reset_context(
    IIIMCF_context context
)
{
    IIIMF_status st1, st2;
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;
    IIIMCF_handle_rec *ph = pc->ph;
    IIIMP_data_s *pds = ph->data_s;
    IIIMP_message *pmes;

    pmes = iiimp_resetic_new(pds, ph->im_id, pc->ic_id);
    if (!pmes) return IIIMF_STATUS_MALLOC;
    st1 = iiimcf_request_message(ph, pmes, pc, IM_RESETIC_REPLY, NULL);
    /* Even if requesting IM_RESETIC is fail,
       reset the internal state.  */
    st2 = reset_context(pc, 1);

    if (st1 != IIIMF_STATUS_SUCCESS) return st1;
    return st2;
}

IIIMF_status
iiimcf_get_ic_id(
    IIIMCF_context context,
    int *pic_id
)
{
    IIIMCF_context_rec *pc = (IIIMCF_context_rec*) context;

    *pic_id = pc->ic_id;

    if (pc->ic_id < 0)
	return IIIMF_STATUS_IC_INVALID;

    return IIIMF_STATUS_SUCCESS;
}

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