#include <stdio.h>

#include "le_info.h"

LeResult le_info_destroy(LeInfoRec * le_info);
LeResult le_info_init_m17n_input_methods(LeInfoRec * le_info);
LeResult le_info_done_m17n_input_methods(LeInfoRec * le_info);

extern void le_session_ui_callbacks(MInputContext *ic, MSymbol command);

LeInfoRec *le_info_new()
{
    LeResult result;
    LeInfoRec *le_info = NULL;

    le_info = (LeInfoRec *) calloc(1, sizeof(LeInfoRec));
    if (le_info == NULL)
	return NULL;

    result = le_info_init_m17n_input_methods(le_info);
    if (result == LE_FAIL) {
        le_info_destroy(le_info);
        return NULL;
    }

    return le_info;
}

LeResult le_info_destroy(LeInfoRec * le_info)
{
    if (le_info == NULL)
	return LE_FAIL;

    le_info_done_m17n_input_methods(le_info);

    free((char *) le_info);

    return LE_OK;
}

LeResult le_info_print(LeInfoRec * le_info)
{
#ifdef DEBUG
    if (le_info == NULL)
	return LE_FAIL;

    return LE_OK;
#endif
}

#define M17N_IM_LIST_NUM_ALLOC  10
int le_info_pushback_m17n_input_method(LeInfoRec *le_info,
                                       M17nImInfoRec *m17n_im)
{
    int i, num_input_methods;

    if (le_info == NULL || m17n_im == NULL)
        return LE_FAIL;

    if (le_info->input_methods == NULL) {
        le_info->input_methods = (M17nImInfoRec **) calloc (M17N_IM_LIST_NUM_ALLOC,
                                                            sizeof(M17nImInfoRec *));
        if (le_info->input_methods == NULL)
            return LE_FAIL;
    }

    num_input_methods = le_info->num_input_methods;
    if ((num_input_methods + 1) % M17N_IM_LIST_NUM_ALLOC == 0) {
        int num = num_input_methods + 1 + M17N_IM_LIST_NUM_ALLOC;

        le_info->input_methods = (M17nImInfoRec **)realloc(le_info->input_methods,
                                                  num * sizeof(M17nImInfoRec *));
        if (le_info->input_methods == NULL)
            return LE_FAIL;

        for (i = num_input_methods; i < num; i++)
            le_info->input_methods[i] = NULL;
    }

    le_info->input_methods[num_input_methods] = m17n_im;
    le_info->num_input_methods ++;

    return LE_OK;
}

int le_info_compare_m17n_input_method (const void *elt1, const void *elt2)
{
    const M17nImInfoRec *im1 = elt1;
    const M17nImInfoRec *im2 = elt2;
    MSymbol lang1, lang2;

    if (im1->language == Mnil)
        return 1;
    if (im1->language == im2->language)
        return strcmp (msymbol_name (im1->name), msymbol_name (im2->name));
    if (im1->language == Mt)
        return 1;
    if (im2->language == Mt)
        return -1;

    return strcmp (msymbol_name (im1->language), msymbol_name (im2->language));
}

LeResult le_info_init_m17n_input_methods(LeInfoRec * le_info)
{
    MPlist *imlist, *pl;
    MInputMethod *im = NULL;

    int num_input_methods;
    M17nImInfoRec *input_methods;

    M17N_INIT ();
    if (merror_code != MERROR_NONE) {
        fprintf (stderr, "Fail to initialize the m17n library!\n");
	return LE_FAIL;
    }

    le_info->Miiim_ic = msymbol ("iiim_ic");
    le_info->Mkey_english_native = msymbol ("iiim_key_english_native");
    le_info->Mkey_switch_next_im = msymbol ("iiim_key_switch_next_im");

    le_info->converter = mconv_buffer_converter(Mcoding_utf_16, NULL, 0);
    if (!le_info->converter)
        return LE_FAIL;

    imlist = (MPlist *)mdatabase_list (msymbol ("input-method"), Mnil, Mnil, Mnil);
    if (imlist == (MPlist *)Mnil) {
        mconv_free_converter (le_info->converter);
	return LE_FAIL;
    }

    num_input_methods = mplist_length (imlist);
    if (num_input_methods <= 0) {
        mconv_free_converter (le_info->converter);
        m17n_object_unref (imlist);
	return LE_FAIL;
    }

    for (pl = imlist; mplist_key (pl) != Mnil; pl = mplist_next (pl)) {
        MDatabase *mdb = mplist_value (pl);
        MSymbol *tag = mdatabase_tag (mdb);

        if (tag[1] != Mnil) {
            M17nImInfoRec *input_method;

            input_method = (M17nImInfoRec *)calloc(1, sizeof(M17nImInfoRec));
            if (input_method == NULL)
                continue;

            input_method->language = tag[1];
            input_method->name = tag[2];
            input_method->im = NULL;

            DEBUG_printf("lang: %s, name: %s\n",
                         msymbol_name(tag[1]), msymbol_name(tag[2]));
            le_info_pushback_m17n_input_method(le_info, input_method);
        }
    }

    m17n_object_unref (imlist);

    qsort(le_info->input_methods, le_info->num_input_methods,
          sizeof(le_info->input_methods[0]), le_info_compare_m17n_input_method);

    DEBUG_printf("num_input_methods: %d\n", le_info->num_input_methods);

    return LE_OK;
}

LeResult le_info_done_m17n_input_methods(LeInfoRec *le_info)
{
    int i;
    int num_input_methods;
    M17nImInfoRec **input_methods;

    if (le_info == NULL)
        return LE_OK;

    input_methods = le_info->input_methods;
    num_input_methods = le_info->num_input_methods;
    if (input_methods) {
        for (i = 0; i < num_input_methods; i++) {
             if (input_methods[i]->im)
                 minput_close_im (input_methods[i]->im);
        }
        free ((char *) input_methods);
    }

    mconv_free_converter (le_info->converter);

    M17N_FINI ();

    return LE_OK;
}

LeResult le_info_register_ui_callbacks(MInputMethod *im)
{
    MPlist *callback_list;

    if (im == (MInputMethod *)Mnil)
        return LE_FAIL;

    if (!im->driver.callback_list)
        im->driver.callback_list = (MPlist *)mplist();

    callback_list = im->driver.callback_list;

    mplist_add(callback_list, Minput_preedit_start, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_preedit_draw, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_preedit_done, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_status_start, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_status_draw, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_status_done, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_candidates_start, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_candidates_draw, (void *)le_session_ui_callbacks);
    mplist_add(callback_list, Minput_candidates_done, (void *)le_session_ui_callbacks);

    return LE_OK;
}

MInputMethod *le_info_get_input_method_for_locale(LeInfoRec *le_info, char *locale)
{
    int i;

    if (le_info == NULL || locale == NULL)
        return ((MInputMethod *)Mnil);

    /* find input method which match the given locale */
    for (i = 0; i < le_info->num_input_methods; i ++) {
        M17nImInfoRec *input_method = le_info->input_methods[i];
        MSymbol m_lang, m_name;
        char *lang;

        if (input_method == NULL)
            continue;

        m_lang = input_method->language;
        m_name = input_method->name;
        if (m_lang == Mt)
            continue;

        lang = msymbol_name(m_lang);
        if (!lang || !*lang)
            continue;

        if (!strncasecmp(locale, lang, strlen(lang))) {
            if (input_method->im == (MInputMethod *)Mnil) {
                input_method->im = minput_open_im(m_lang, m_name, NULL);
                le_info_register_ui_callbacks(input_method->im);
            }
                
            if (input_method->im != (MInputMethod *)Mnil)
                return (input_method->im);
        }
    }

    return ((MInputMethod *)Mnil);
}

MInputMethod *le_info_get_next_input_method_for_locale(LeInfoRec *le_info,
                                                       char *locale,
                                                       MInputMethod *im)
{
    int i;

    MSymbol m_lang, m_name;
    M17nImInfoRec *im_info_first = NULL;
    M17nImInfoRec *im_info_current = NULL;
    M17nImInfoRec *im_info_matched = NULL;

    if (le_info == NULL || locale == NULL)
        return ((MInputMethod *)Mnil);

    DEBUG_printf("le_info_get_next_input_method_for_locale: locale: %s, im: %p\n", locale, im);
    /* find input method which match the given locale */
    for (i = 0; i < le_info->num_input_methods; i ++) {
        M17nImInfoRec *input_method = le_info->input_methods[i];
        char *lang;

        if (input_method == NULL)
            continue;

        m_lang = input_method->language;
        m_name = input_method->name;

        lang = msymbol_name(m_lang);
        if (!lang || !*lang)
            continue;

        DEBUG_printf("input_method[%d]: lang: %s, name: %s, im: %p\n",
                i, lang, msymbol_name(m_name), input_method->im);

        if (strncasecmp(locale, lang, strlen(lang)) && m_lang != Mt)
            continue;

        if (im == (MInputMethod *) Mnil) {
            im_info_matched = input_method;
            break;
        }

        if (im_info_first == NULL)
            im_info_first = input_method;

        if (input_method->im == im) {
            im_info_current = input_method;
        } else if (im_info_current != NULL) {
            im_info_matched = input_method;
            break;
        }
    }

    DEBUG_printf("im_info_matched: %p, im_info_first: %p\n", im_info_matched, im_info_first);
    if (im_info_matched == NULL && im_info_first != NULL)
        im_info_matched = im_info_first;

    if (im_info_matched != NULL) {
        if (im_info_matched->im == (MInputMethod *)Mnil) {
            m_lang = im_info_matched->language;
            m_name = im_info_matched->name;

            im_info_matched->im = minput_open_im(m_lang, m_name, NULL);
            le_info_register_ui_callbacks(im_info_matched->im);
        }
        return (im_info_matched->im);
    }
                
    return ((MInputMethod *)Mnil);
}


char *disabled_langs[] = {
    "zh",
#if 0
    "ko",
    "ja"
#endif
};

int le_info_check_language_disabled(LeInfoRec *le_info, char *lang)
{
    int i, num;

    if (!le_info || !lang || !*lang)
        return 0;

    num = sizeof(disabled_langs)/sizeof(char *);
    for (i = 0; i < num; i ++) {
         if (!strcasecmp(lang, disabled_langs[i]))
             return 1;
    }

    return 0;
}

typedef struct {
    char *m17n_lang_name;
    char *standard_lang_name;
} language_name_pair_t;

language_name_pair_t lang_name_pairs[] = {
    { "am", "am_ET" },
    { "as", "" },
    { "bn", "" },
    { "bo", "" },
    { "dv", "" },
    { "el", "el_GR" },
    { "en", "en" },
    { "ar", "ar" },
    { "fa", "" },
    { "gu", "gu_IN" },
    { "he", "he" },
    { "hi", "hi_IN" },
    { "hr", "" },
    { "hy", "" },
    { "ja", "ja" },
    { "ka", "" },
    { "kk", "" },
    { "km", "" },
    { "kn", "kn_IN" },
    { "ko", "ko" },
    { "lo", "" },
    { "ml", "ml_IN" },
    { "my", "" },
    { "or", "" },
    { "pa", "pa_IN" },
    { "ru", "ru_RU" },
    { "si", "" },
    { "sk", "" },
    { "sr", "" },
    { "ta", "ta_IN" },
    { "te", "te_IN" },
    { "th", "th_TH" },
    { "vi", "vi_VN" },
    { "zh", "zh_CN" },
};

char *le_info_get_standard_language_name(char *lang)
{
    int i, num;

    if (!lang || !*lang)
        return lang;

    num = sizeof(lang_name_pairs)/sizeof(language_name_pair_t);
    for (i = 0; i < num; i ++) {
         char *standard_lang_name;
         char *m17n_lang_name;

         m17n_lang_name = lang_name_pairs[i].m17n_lang_name;
         standard_lang_name =  lang_name_pairs[i].standard_lang_name;

         if (!m17n_lang_name || !*m17n_lang_name)
             continue;

         if (!standard_lang_name || !*standard_lang_name)
             continue;

         if (!strcasecmp(lang, m17n_lang_name)) {
             if (!standard_lang_name || !*standard_lang_name)
                 return lang;
             else
                 return standard_lang_name;
         }
    }

    return lang;
}
