#include <stdio.h>

#include "le_info.h"
#include "le_session.h"
#include "le_desktop.h"

void le_session_ui_callbacks(MInputContext *ic, MSymbol command);

LeDesktopContextRec *le_session_get_desktop_context(iml_session_t * s);
LeSessionContextRec *le_session_get_session_context(iml_session_t * s);

extern LeInfoRec *le_info;

/*****************************************
      LeSessionContextRec
*****************************************/
LeSessionContextRec *le_session_context_new()
{
    LeSessionContextRec *le_session_context = NULL;

    le_session_context =
	(LeSessionContextRec *) calloc(1, sizeof(LeSessionContextRec));
    if (le_session_context == NULL)
	return NULL;

    le_session_context->current_conversion_status = CONVERSION_OFF;
    le_session_context->ic = NULL;
    le_session_context->locale = NULL;

    return le_session_context;
}

LeResult le_session_context_destroy(LeSessionContextRec *
				    le_session_context)
{
    if (le_session_context == NULL)
	return LE_FAIL;

    if (le_session_context->locale)
        free((char *) le_session_context->locale);

    if (le_session_context->ic)
        minput_destroy_ic(le_session_context->ic);

    free((char *) le_session_context);

    return LE_OK;
}

LeResult le_session_context_print(LeSessionContextRec * le_session_context)
{
    if (le_session_context == NULL)
	return LE_FAIL;

    return LE_OK;
}

LeResult le_session_context_push_into_m17n_ic(MInputContext *ic,
                        LeSessionContextRec *le_session_context)
{
    if (ic == (MInputContext *)Mnil)
        return LE_FAIL;

    mplist_push (ic->plist, le_info->Miiim_ic, le_session_context);
    return LE_OK;
}

LeSessionContextRec *le_session_context_get_from_m17n_ic(MInputContext *ic)
{
    LeSessionContextRec *le_session_context;
    MPlist *plist;

    if (ic == (MInputContext *)Mnil)
        return NULL;

    for (plist = ic->plist; plist && mplist_key (plist) != Mnil;
         plist = mplist_next (plist)) {
        if (mplist_key (plist) != le_info->Miiim_ic)
            continue;
        le_session_context = mplist_value (plist);
        if (le_session_context && le_session_context->ic == ic)
          return le_session_context;
    }
    return NULL;
}

/*****************************************
      LE Session for iml_session_t
*****************************************/
LeResult le_session_create(iml_session_t * s)
{
    LeSessionContextRec *le_session_context;

    le_session_context = (LeSessionContextRec *) le_session_context_new();
    if (le_session_context == NULL)
	return LE_FAIL;

    le_session_context->s = s;

    s->specific_data = (void *) le_session_context;

    return LE_OK;
}

LeResult le_session_destroy(iml_session_t * s)
{
    DEBUG_printf("le_session_destroy: s: 0x%x\n", s);

    LeSessionContextRec *le_session_context =
	le_session_get_session_context(s);

    if (le_session_context != NULL) {
	le_session_context_destroy(le_session_context);
    }

    s->specific_data = NULL;

    return LE_OK;
}

LeResult le_session_set_locale(iml_session_t * s, char *locale)
{
    LeResult result;
    LeSessionContextRec *le_session_context;

    MInputMethod *im;
    MInputContext *ic;

    DEBUG_printf("le_session_set_locale: locale: %s\n", locale);
    if (locale == NULL)
        return LE_FAIL;

    le_session_context = le_session_get_session_context(s);
    if (le_session_context == NULL) 
        return LE_FAIL;

    ic = le_session_context->ic;
    if (ic) minput_destroy_ic(ic);
    le_session_context->ic = (MInputContext *)Mnil;

    if (le_session_context->locale != NULL)
        free((char *) le_session_context->locale);
    le_session_context->locale = (char *)strdup(locale);

    im = (MInputMethod *)le_info_get_input_method_for_locale(le_info, locale);
    if (im == (MInputMethod *)Mnil)
        return LE_FAIL;

    DEBUG_printf("minput_create_ic: im: %p\n", im);
    ic = minput_create_ic(im, NULL);
    if (ic == (MInputContext *)Mnil)
        return LE_FAIL;

    le_session_context->im = (MInputMethod *)im;
    le_session_context->ic = (MInputContext *)ic;

    DEBUG_printf("mplist_push: ic: %p,  le_session_context: %p\n",
                  ic, le_session_context);
    result = le_session_context_push_into_m17n_ic(ic, le_session_context);

    return result;
}

LeResult le_session_switch_to_next_input_method(iml_session_t * s)
{
    LeResult result;
    LeSessionContextRec *le_session_context;

    MInputMethod *im;
    MInputContext *ic;

    char *locale;

    DEBUG_printf("le_session_switch_to_next_input_method \n");
    le_session_context = le_session_get_session_context(s);
    if (le_session_context == NULL) 
        return LE_FAIL;

    locale = le_session_context->locale;
    if (locale == NULL)
        return LE_FAIL;

    im = le_session_context->im;
    im = (MInputMethod *)le_info_get_next_input_method_for_locale(le_info, locale, im);
    if (im == (MInputMethod *)Mnil)
        return LE_FAIL;

    ic = le_session_context->ic;
    if (ic)  minput_destroy_ic(ic);
    le_session_context->ic = (MInputContext *)Mnil;

    ic = minput_create_ic(im, NULL);
    if (ic == (MInputContext *)Mnil)
        return LE_FAIL;

    le_session_context->im = (MInputMethod *)im;
    le_session_context->ic = (MInputContext *)ic;

    DEBUG_printf("mplist_push: ic: %p,  le_session_context: %p\n",
                  ic, le_session_context);
    result = le_session_context_push_into_m17n_ic(ic, le_session_context);

    le_session_ui_callbacks(ic, Minput_status_draw);

    return result;
}

LeResult le_session_set_focus_in(iml_session_t * s)
{
    LeSessionContextRec *le_session_context;
    MInputContext *ic;

    le_session_context = le_session_get_session_context(s);
    if (le_session_context == NULL) 
        return LE_FAIL;

    ic = le_session_context->ic;

    le_session_ui_callbacks(ic, Minput_preedit_draw);
    le_session_ui_callbacks(ic, Minput_status_draw);
    le_session_ui_callbacks(ic, Minput_candidates_draw);
}

LeResult le_session_set_focus_out(iml_session_t * s)
{
    LeSessionContextRec *le_session_context;
    MInputContext *ic;

    le_session_context = le_session_get_session_context(s);
    if (le_session_context == NULL) 
        return LE_FAIL;

    ic = le_session_context->ic;

    le_session_ui_callbacks(ic, Minput_preedit_done);
    le_session_ui_callbacks(ic, Minput_status_done);
    le_session_ui_callbacks(ic, Minput_candidates_done);
}

IMText *le_session_reset(iml_session_t * s)
{
    return ((IMText *) NULL);
}

/*****************************************
      LE Session Functions for iml_session_t
*****************************************/
LeDesktopContextRec *le_session_get_desktop_context(iml_session_t * s)
{
    return ((LeDesktopContextRec *) s->desktop->specific_data);
}

LeSessionContextRec *le_session_get_session_context(iml_session_t * s)
{
    return ((LeSessionContextRec *) s->specific_data);
}

ConversionStatus le_session_get_conversion_status(iml_session_t * s)
{
    LeSessionContextRec *le_session_context = NULL;

    le_session_context = le_session_get_session_context(s);
    if (le_session_context == NULL)
	return CONVERSION_OFF;

    return (le_session_context->current_conversion_status);
}

LeResult le_session_set_conversion_status(iml_session_t * s,
					  ConversionStatus
					  conversion_status)
{
    MInputContext *ic;

    LeSessionContextRec *le_session_context;
    le_session_context = le_session_get_session_context(s);

    if (le_session_context == NULL)
        return;

    ic = le_session_context->ic;

    if (conversion_status == CONVERSION_OFF) {
	le_iml_conversion_off(s);

	le_session_ui_callbacks(ic, Minput_preedit_done);
	le_session_ui_callbacks(ic, Minput_status_done);
	le_session_ui_callbacks(ic, Minput_candidates_done);
    } else {
	le_iml_conversion_on(s);

	le_session_ui_callbacks(ic, Minput_preedit_draw);
	le_session_ui_callbacks(ic, Minput_status_draw);
	le_session_ui_callbacks(ic, Minput_candidates_draw);
    }

    return LE_OK;
}

LeResult le_session_toggle_conversion_status(iml_session_t * s)
{
    LeSessionContextRec *le_session_context;
    ConversionStatus conversion_status;
    LeResult result;

    le_session_context = le_session_get_session_context(s);
    if (le_session_context == NULL)
	return LE_FAIL;

    conversion_status = le_session_context->current_conversion_status;
    DEBUG_printf("le_session_toggle_conversion_status: %d\n",
		 ~conversion_status);
    if (conversion_status == CONVERSION_OFF) {
	result = le_session_set_conversion_status(s, CONVERSION_ON);
    } else {
	result = le_session_set_conversion_status(s, CONVERSION_OFF);
    }

    return result;
}

LeResult le_session_set_as_desktop_current_session(iml_session_t * s)
{
    LeDesktopContextRec *le_desktop_context = NULL;

    le_desktop_context =
	(LeDesktopContextRec *) le_session_get_desktop_context(s);
    le_desktop_context_set_current_session(le_desktop_context, s);

    return LE_OK;
}


/*****************************************
      candidatas_info utilities
*****************************************/
typedef struct {
    int num_candidates;
    UTFCHAR **labels;
    UTFCHAR **candidates;
} candidates_info_t;

candidates_info_t *candidates_info_new()
{
    candidates_info_t *candidates_info = NULL;

    candidates_info = (candidates_info_t *) calloc(1, sizeof(candidates_info_t));

    return candidates_info;

}

void candidates_info_destroy(candidates_info_t * candidates_info)
{
    int i;

    if (candidates_info == NULL || candidates_info->candidates == NULL)
        return;

    for (i = 0; i < candidates_info->num_candidates; i++) {
         if (candidates_info->labels[i])
             free ((char *)candidates_info->labels[i]);

         if (candidates_info->candidates[i])
             free ((char *)candidates_info->candidates[i]);
    }

    free ((char *)candidates_info->candidates);

    return;
}

#define CANDIDATES_NUM_ALLOC   6
int candidates_info_pushback_candidate(candidates_info_t *candidates_info, UTFCHAR *candidate)
{
    int i, num_candidates;
    UTFCHAR *candidate_temp;
    UTFCHAR *label_temp;

    DEBUG_printf("candidates_info_pushback_candidate ==\n");
    if (candidates_info == NULL || candidate == NULL)
        return 0;

    if (candidates_info->labels == NULL) {
        candidates_info->labels = (UTFCHAR **)calloc (CANDIDATES_NUM_ALLOC,
                                                      sizeof(UTFCHAR *));
        if (candidates_info->labels == NULL)
            return 0;
    }

    if (candidates_info->candidates == NULL) {
        candidates_info->candidates = (UTFCHAR **)calloc (CANDIDATES_NUM_ALLOC,
                                                          sizeof(UTFCHAR *));
        if (candidates_info->candidates == NULL)
            return 0;
    }

    num_candidates = candidates_info->num_candidates;
    if ((num_candidates + 1) % CANDIDATES_NUM_ALLOC == 0) {
        int num = num_candidates + 1 + CANDIDATES_NUM_ALLOC;

        candidates_info->labels = (UTFCHAR **)realloc(candidates_info->labels,
                                                      num * sizeof(UTFCHAR *));
        if (candidates_info->labels == NULL)
            return 0;

        candidates_info->candidates = (UTFCHAR **)realloc(candidates_info->candidates,
                                                          num * sizeof(UTFCHAR *));
        if (candidates_info->candidates == NULL)
            return 0;

        for (i = num_candidates; i < num; i++) {
            candidates_info->labels[i] = NULL;
            candidates_info->candidates[i] = NULL;
        }
    }

    DEBUG_printf("candidate: len: %d\n", UTFCHARLen(candidate));
    candidate_temp = (UTFCHAR *) calloc (UTFCHARLen(candidate) + 1, sizeof (UTFCHAR));
    if (candidate_temp == NULL)
        return 0;

    label_temp = (UTFCHAR *) calloc (2, sizeof (UTFCHAR));
    if (label_temp == NULL)
        return 0;

    UTFCHARCpy(candidate_temp, candidate);
    label_temp[0] = ' ';
    label_temp[1] = 0;

    candidates_info->candidates[num_candidates] = candidate_temp;
    candidates_info->labels[num_candidates] = label_temp;
    candidates_info->num_candidates ++;

    return 1;
}

/*****************************************
      callbacks
*****************************************/
void le_session_ui_callbacks(MInputContext *ic, MSymbol command)
{
    iml_session_t *s;
    LeSessionContextRec *le_session_context;

    MConverter *converter;
    UTFCHAR buf[1024];

    int layout_type_vertical = 1;
    int layout_capacity = 10;

    le_session_context = le_session_context_get_from_m17n_ic(ic);
    DEBUG_printf("le_session_ui_callbacks == le_session_context: %p\n", le_session_context);
    if (le_session_context == NULL)
        return;

    s = le_session_context->s;
    if (s == NULL)
        return;

    converter = le_info->converter;

    if (command == Minput_preedit_start) {

        DEBUG_printf("    Minput_preedit_start\n");
        le_iml_preedit_start(s);

    } else if (command == Minput_preedit_done) {

        DEBUG_printf("    Minput_preedit_done\n");
        le_iml_preedit_enddraw(s);

    } else if (command == Minput_preedit_draw) {

        int len;
        int highlight_from, highlight_to;

        DEBUG_printf("    Minput_preedit_draw, caret_pos: %d\n", ic->cursor_pos);

        if (!ic->preedit) {
            DEBUG_printf("ic->preedit is NULL, call le_iml_preedit_enddraw\n");
            le_iml_preedit_enddraw(s);
            return;
        }

        memset(buf, 0, sizeof(buf));
        mconv_rebind_buffer(converter, (unsigned char *)buf, sizeof (buf));
        mconv_encode(converter, ic->preedit);
        buf[converter->nbytes] = 0;

        len = UTFCHARLen(buf);
        if (len > 0) {

            highlight_from = 0;
            highlight_to = 0;
            if (ic->candidate_from < ic->candidate_to &&
                ic->candidate_to <= len) {
                highlight_from = ic->candidate_from;
                highlight_to = ic->candidate_to;
            }

            le_iml_preedit_draw(s, buf, ic->cursor_pos, highlight_from, highlight_to);
        } else {
            le_iml_preedit_enddraw(s);
        }

    } else if (command == Minput_status_start) {

        DEBUG_printf("    Minput_status_start\n");
        le_iml_status_start(s);

    } else if (command == Minput_status_done) {

        DEBUG_printf("    Minput_status_done\n");
        buf[0] = 0;
        le_iml_status_draw(s, buf);
        le_iml_status_enddraw(s);

    } else if (command == Minput_status_draw) {

        DEBUG_printf("    Minput_status_draw\n");

        if (!ic->status)
            return;

        memset(buf, 0, sizeof(buf));
        mconv_rebind_buffer(converter, (unsigned char *)buf, sizeof (buf));
        mconv_encode(converter, ic->status);
        buf[converter->nbytes] = 0;
        if (buf[0]) {
            le_iml_status_draw(s, buf);
        }

    } else if (command == Minput_candidates_start) {
        LayoutInfo layout;

        DEBUG_printf("    Minput_candidates_start\n");
        layout.choice_per_window = layout_capacity;
        if (layout_type_vertical) {
            layout.ncolumns = 1;
            layout.nrows = layout_capacity;
            layout.drawUpDirection = DrawUpVertically;
        } else {
            layout.ncolumns = layout_capacity;
            layout.nrows = 1;
            layout.drawUpDirection = DrawUpHorizontally;
        }
        le_iml_lookup_start(s, &layout);

    } else if (command == Minput_candidates_done) {

        DEBUG_printf("    Minput_candidates_done\n");
        le_iml_lookup_enddraw(s);

    } else if (command == Minput_candidates_draw) {

        candidates_info_t *candidates_info;

        int cur, i, len;

        MPlist *group;
        MText *mt;

        DEBUG_printf("    Minput_candidates_draw\n");
        DEBUG_printf("       candidate_index: %d\n", ic->candidate_index);
        DEBUG_printf("       candidate_from : %d\n", ic->candidate_from);
        DEBUG_printf("       candidate_to   : %d\n", ic->candidate_to);
        DEBUG_printf("       candidate_show : %d\n", ic->candidate_show);

        if (!ic->candidate_list || !ic->candidate_show) {
            le_iml_lookup_enddraw(s);
            return;
        }

        i = 0;
        group = ic->candidate_list;
        while (1) {
            if (mplist_key(group) == Mtext)
                len = mtext_len (mplist_value (group));
            else
                len = mplist_length (mplist_value (group));
            if (i + len > ic->candidate_index)
                break;
            i += len;
            group = mplist_next (group);
        }

        cur = ic->candidate_index - i;

        candidates_info = (candidates_info_t *) candidates_info_new();
        if (candidates_info == NULL)
            return;

        if (mplist_key (group) == Mtext) {
            mt = (MText *) mplist_value (group);
            memset(buf, 0, sizeof(buf));
            mconv_rebind_buffer(converter, (unsigned char *)buf, sizeof (buf));
            mconv_encode(converter, mt);
            buf[converter->nbytes] = 0;

            DEBUG_printf("converter->nbytes: %d\n", converter->nbytes);
            for (i = 0; i < UTFCHARLen(buf); i++) {
                UTFCHAR candidate[2];

                candidate[0] = buf[i];
                candidate[1] = 0;
                candidates_info_pushback_candidate(candidates_info, candidate);
            }
        } else {
            MPlist *pl;

            for (pl = (MPlist *) mplist_value (group); mplist_key (pl) != Mnil; pl = mplist_next (pl)) {
                mt = (MText *) mplist_value (pl);
                memset(buf, 0, sizeof(buf));
                mconv_rebind_buffer(converter, (unsigned char *)buf, sizeof (buf));
                mconv_encode(converter, mt);
                buf[converter->nbytes] = 0;
                DEBUG_printf("converter->nbytes: %d\n", converter->nbytes);
                if (buf[0]) {
                    candidates_info_pushback_candidate(candidates_info, buf);
                }
            }
        }

        DEBUG_printf("num_candidates: %d\n", candidates_info->num_candidates);
        if (candidates_info->num_candidates == 0) {
            candidates_info_destroy(candidates_info);
            return;
        }

        le_iml_lookup_draw(s,
                       candidates_info->num_candidates,
                       candidates_info->candidates,
                       NULL, candidates_info->labels,
                       NULL, NULL, NULL, cur,
                       layout_type_vertical ? 1 : 0);

        candidates_info_destroy(candidates_info);

        return;
    }
}
