#include <stdio.h>
#include <dlfcn.h>

#include "le_info.h"
#include "imbean.h"

#define LE_CONFIG_FILE            "le_config.xml"

#define CATAGORY_IME_MODULES      "all_engines"
#define CATAGORY_IMM_KEYBINDINGS  "keybindings"

LeResult le_info_destroy(LeInfoRec * le_info);
LeResult le_info_load_ime_modules(LeInfoRec * le_info,
				  IbmlCatagory * ibml_catagory);
LeResult le_info_load_imm_keybindings(LeInfoRec * le_info,
				      IbmlCatagory * ibml_catagory);
LeResult le_info_load_config_file(LeInfoRec * le_info,
				  char *le_config_file);

LeInfoRec *le_info_new()
{
    LeResult result;

    LeInfoRec *le_info = NULL;
    char *le_config_file = LE_CONFIG_FILE;

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

    le_info->num_ime_modules = 0;
    le_info->ime_modules = NULL;

    le_info->num_imm_keybindings = 0;
    le_info->imm_keybindings = NULL;

    le_info->time_stamp = time(0);

    result = le_info_load_config_file(le_info, le_config_file);
    if (result == LE_FAIL) {
        le_info_destroy(le_info);
        return (NULL);
    }

    return (le_info);
}

LeResult le_info_destroy(LeInfoRec * le_info)
{
    int i;

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

    /* free ime modules */
    if (le_info->ime_modules) {
	for (i = 0; i < le_info->num_ime_modules; i++) {
	    ImeModuleContextRec *ime_module_context = le_info->ime_modules[i];
	    if (ime_module_context == NULL)
		continue;
	    ime_module_context_destroy(ime_module_context);
	}
	free((char *) le_info->ime_modules);
    }

    /* free imm keybindings */
    if (le_info->imm_keybindings) {
	for (i = 0; i < le_info->num_imm_keybindings; i++) {
	    ImmKeybindingRec *imm_keybinding = le_info->imm_keybindings[i];
	    if (imm_keybinding == NULL)
		continue;
	    imm_keybinding_destroy(imm_keybinding);
	}
	free((char *) le_info->imm_keybindings);
    }

    free((char *) le_info);

    return (LE_OK);
}

LeResult le_info_print(LeInfoRec * le_info)
{
#if DEBUG
    int i;

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

    printf("time_stamp:  %d\n", le_info->time_stamp);

    /* print ime modules */
    if (le_info->ime_modules) {
	for (i = 0; i < le_info->num_ime_modules; i++) {
	    ImeModuleContextRec *ime_module_context = le_info->ime_modules[i];
	    if (ime_module_context == NULL)
		continue;
	    ime_module_context_print(ime_module_context);
	}
    }

    /* print imm keybindings */
    if (le_info->imm_keybindings) {
	for (i = 0; i < le_info->num_imm_keybindings; i++) {
	    ImmKeybindingRec *imm_keybinding = le_info->imm_keybindings[i];
	    if (imm_keybinding == NULL)
		continue;
	    imm_keybinding_print(imm_keybinding);
	}
    }

    return (LE_OK);
#endif
}

char *le_info_get_full_file_path(char *file_path)
{
    int len;
    char *full_file_path = NULL;

    if (!file_path || !*file_path)
	return (NULL);

    if (file_path[0] == '/') {
	len = strlen(file_path);

	full_file_path = (char *) calloc(len + 1, sizeof(char));
	if (full_file_path == NULL)
	    return (NULL);

	snprintf(full_file_path, len + 1, "%s", file_path);
    } else {
	len = strlen(LE_BASE_DIR) + 1 + strlen(file_path);

	full_file_path = (char *) calloc(len + 1, sizeof(char));
	if (full_file_path == NULL)
	    return (NULL);

	snprintf(full_file_path, len + 1, "%s/%s", LE_BASE_DIR, file_path);
    }

    return (full_file_path);
}

LeResult le_info_load_config_file(LeInfoRec * le_info,
				  char *le_config_file)
{
    IbmlData *ibml_data;
    IbmlCatagory *ibml_catagory;
    char *le_config_file_with_full_path = NULL;
    int i;

    LeResult result = LE_OK;

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

    le_config_file_with_full_path =
	(char *) le_info_get_full_file_path(le_config_file);
    if (!le_config_file_with_full_path || !*le_config_file_with_full_path)
	return (LE_FAIL);

    ibml_data = (IbmlData *) imbean_config_new_from_file(le_config_file_with_full_path);
    free((char *) le_config_file_with_full_path);

    if (ibml_data == NULL)
	return (LE_FAIL);

    for (i = 0; i < ibml_data->num_catagories; i++) {
	ibml_catagory = ibml_data->catagories[i];

	if (!ibml_catagory->scope || !*ibml_catagory->scope)
	    continue;

	if (!strcasecmp(ibml_catagory->scope, CATAGORY_IME_MODULES)) {
	    result = le_info_load_ime_modules(le_info, ibml_catagory);
	} else if (!strcasecmp(ibml_catagory->scope, CATAGORY_IMM_KEYBINDINGS)) {
	    result = le_info_load_imm_keybindings(le_info, ibml_catagory);
	}
    }

    ibml_data_free(ibml_data);
    return (result);
}

LeResult le_info_load_ime_modules(LeInfoRec * le_info,
				  IbmlCatagory * ibml_catagory)
{
#define IME_MODULE_OBJECT_FILE "object_file"
#define IME_MODULE_ARGS        "args"
#define IME_MODULE_ENABLED     "enabled"

    int num_ime_modules;
    ImeModuleContextRec **ime_modules;

    IbmlElement *ibml_element;
    IbmlProperty *ibml_property;

    int i, j;

    char *id, *scope;
    char *name, *value;

    if (le_info == NULL)
	return (LE_FAIL);
    if (ibml_catagory == NULL)
	return (LE_FAIL);
    if (ibml_catagory->num_elements <= 0)
	return (LE_FAIL);

    ime_modules =
	(ImeModuleContextRec **) calloc(ibml_catagory->num_elements,
					sizeof(ImeModuleContextRec *));
    if (ime_modules == NULL) {
	return (LE_FAIL);
    }

    num_ime_modules = 0;
    for (i = 0; i < ibml_catagory->num_elements; i++) {
	ImeModuleContextRec *ime_module_context = NULL;
	ImeModuleRec *ime_module = NULL;
	char *base_dir = LE_BASE_DIR;
	char *object_file_path = NULL;
	char *ime_args = NULL;
	char *enable_str = NULL;
	ImeResult result;

	ibml_element = ibml_catagory->elements[i];
	if (!ibml_element)
	    continue;

	id = (char *) ibml_element->id;
	scope = (char *) ibml_element->scope;
	DEBUG_printf("id:%s, scope:%s\n", id ? id : "NULL",
		     scope ? scope : "NULL");
	if (!id || !*id)
	    continue;

	for (j = 0; j < ibml_element->num_properties; j++) {
	    ibml_property = ibml_element->properties[j];
	    if (!ibml_property)
		continue;

	    name = (char *) ibml_property->name;
	    if (!name || !*name)
		continue;

	    value = (char *) ibml_property->value;
	    if (!value || !*value)
		continue;

	    if (!strcasecmp(name, IME_MODULE_OBJECT_FILE)) {
		object_file_path = (char *) value;
	    } else if (!strcasecmp(name, IME_MODULE_ARGS)) {
		ime_args = (char *) value;
	    } else if (!strcasecmp(name, IME_MODULE_ENABLED)) {
		enable_str = (char *) value;
	    }
	}

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

	object_file_path =
	    (char *) le_info_get_full_file_path(object_file_path);
	if (!object_file_path || !*object_file_path)
	    continue;

	ime_module_context = (ImeModuleContextRec *) ime_module_context_new();
	if (ime_module_context == NULL) {
	    free((char *) object_file_path);
	    continue;
	}

	ime_module = (ImeModuleRec *) ime_module_new();
	if (ime_module == NULL) {
	    free((char *) ime_module_context);
	    free((char *) object_file_path);
	    continue;
	}

	result =
	    ime_module_load_object_file(ime_module, base_dir,
					object_file_path, ime_args);
	free((char *) object_file_path);
	if (result == IME_FAIL) {
	    free((char *) ime_module_context);
	    ime_module_destroy(ime_module);
	    continue;
	}

	ime_module_context->enabled = 1;
	if (enable_str && *enable_str) {
	    if (!strcasecmp(enable_str, "0") ||
		!strcasecmp(enable_str, "false"))
		ime_module_context->enabled = 0;
	}

	ime_module_context->ime_module = ime_module;
	ime_modules[num_ime_modules] = ime_module_context;
	num_ime_modules++;
    }

    le_info->num_ime_modules = num_ime_modules;
    le_info->ime_modules = ime_modules;

    return (LE_OK);
}

LeResult le_info_load_imm_keybindings(LeInfoRec * le_info,
				      IbmlCatagory * ibml_catagory)
{
#define IMM_KEYBINDING_KEYCODE   "keycode"
#define IMM_KEYBINDING_MODIFIER  "modifier"

    int num_imm_keybindings;
    ImmKeybindingRec **imm_keybindings, imm_keybinding;

    IbmlElement *ibml_element;
    IbmlProperty *ibml_property;

    int i, j;

    char *id, *scope;
    char *name, *value;

    if (le_info == NULL)
	return (LE_FAIL);
    if (ibml_catagory == NULL)
	return (LE_FAIL);
    if (ibml_catagory->num_elements <= 0)
	return (LE_FAIL);

    imm_keybindings =
	(ImmKeybindingRec **) calloc(ibml_catagory->num_elements,
				     sizeof(ImmKeybindingRec *));
    if (imm_keybindings == NULL) {
	return (LE_FAIL);
    }

    num_imm_keybindings = 0;
    for (i = 0; i < ibml_catagory->num_elements; i++) {
	ImmKeybindingRec *imm_keybinding = NULL;
	char *keycode = NULL;
	char *modifier = NULL;
	char *key_type = NULL;
	LeResult result;

	ibml_element = ibml_catagory->elements[i];
	if (!ibml_element)
	    continue;

	id = (char *) ibml_element->id;
	scope = (char *) ibml_element->scope;
	DEBUG_printf("id:%s, scope:%s\n", id ? id : "NULL",
		     scope ? scope : "NULL");
	if (!id || !*id)
	    continue;

	key_type = id;
	for (j = 0; j < ibml_element->num_properties; j++) {
	    ibml_property = ibml_element->properties[j];
	    if (!ibml_property)
		continue;

	    name = (char *) ibml_property->name;
	    if (!name || !*name)
		continue;

	    value = (char *) ibml_property->value;
	    if (!value || !*value)
		continue;

	    if (!strcasecmp(name, IMM_KEYBINDING_KEYCODE)) {
		keycode = (char *) value;
	    } else if (!strcasecmp(name, IMM_KEYBINDING_MODIFIER)) {
		modifier = (char *) value;
	    }
	}

	if (!keycode || !*keycode)
	    continue;
	if (!modifier || !*modifier)
	    continue;

	imm_keybinding = (ImmKeybindingRec *) imm_keybinding_new();
	if (imm_keybinding == NULL) {
	    continue;
	}

	result =
	    imm_keybinding_set_value(imm_keybinding, keycode, modifier,
				     key_type);
	if (result == IME_FAIL) {
	    imm_keybinding_destroy(imm_keybinding);
	    continue;
	}

	imm_keybindings[num_imm_keybindings] = imm_keybinding;
	num_imm_keybindings++;
    }

    le_info->num_imm_keybindings = num_imm_keybindings;
    le_info->imm_keybindings = imm_keybindings;

    return (LE_OK);
}
