#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include "LEMgr.hh"
#include "IMInputContext.hh"
#include "IMLog.hh"
#include "IMUtil.hh"

void
LEMgr::listup_LEs()
{
    LEDirectoryInfo di;
    DirectoryInfoVec *pdv;

    if (di.refresh(lepath.c_str())) {
        pdv = di.get_directory_info();
        DirectoryInfoVec::const_iterator i = pdv->begin();
        for (; i != pdv->end();++i) {
	    if (loading_preferred_les) {
		/* check if this LE is preferred */
		if (iiim_le_xmlconf_is_empty_module(pxmllecfg)) {
		    LOG_DEBUG("No LEs are registered. disabling the loading preferred LE feature at this moment.");
		    loading_preferred_les = false;
		} else {
		    string lename = i->dirname + i->filename;
		    IIIMLELanguageList *langlist, *langtmp;
		    IIIMLEInfoList *lelist, *letmp;

		    langlist = iiim_le_xmlconf_get_lang_list(pxmllecfg);
		    for (langtmp = langlist; langtmp != NULL; langtmp = langtmp->next) {
			lelist = iiim_le_xmlconf_get_le_info_list(pxmllecfg, langtmp->language);

			for (letmp = lelist; letmp != NULL; letmp = letmp->next) {
			    if (!strcmp(letmp->data->lename, lename.c_str())) {
				LOG_DEBUG("Matched %s", lename.c_str());
				goto matched;
			    }
			}
		    }
		    LOG_DEBUG("Not matched %s. skipping...", lename.c_str());
		    continue;
		  matched:;
		}
	    }
    	    LEBase *pleb = new LEBase(i->dirname, i->filename);
	    // memory error.
	    if (!pleb) return;
	    if (pleb->errorp()) {
	        delete pleb;
	        continue;
	    }
	    lemap.insert(make_pair(i->filename, pleb));
        }
    }
    psunim_default_lebase = new LEBase(LEBase::SUNIM_DEFAULT);
    if (!psunim_default_lebase || psunim_default_lebase->errorp()) {
	LOG_CRITICAL("Could not create default sunim LE.  Exit.");
	// TODO! we should use exception.
	exit(255);
    }
    LEBaseMap::iterator itl;
    LEBase* ple;
    IMHotkeyManagerStruct *hkm;
    IMHotkeyProfileStruct *hkps;
    int j, nhkp = 0;

    le_hotkey_profile_map.clear();
    for (itl = lemap.begin();itl != lemap.end();++itl) {
        ple = itl->second;
        hkm = ple->get_hotkey_manager();
        if (hkm) {
            nhkp = hkm->num_hotkey_profiles;
            if (hkm->hkps && nhkp) {
                for (j=0; j<nhkp; j++) {
                    hkps = &(hkm->hkps[j]);
	            le_hotkey_profile_map.insert(make_pair(hkps, itl->first));
                }
            }
        }
    }

}

void
LEMgr::check_new_LEs()
{
    LEDirectoryInfo di;
    DirectoryInfoVec::iterator i;
    DirectoryInfoVec *pdv;

    if (!di.refresh(lepath.c_str())) return;
    pdv = di.get_directory_info();
    for (i = pdv->begin(); i != pdv->end(); ++i) {
	if (lemap.find(i->filename) != lemap.end()) continue;
	LEBase *pleb = new LEBase(i->dirname, i->filename);
	if (!pleb) return;
	if (pleb->errorp()) {
	    delete pleb;
	    continue;
	}
	lemap.insert(make_pair(i->filename, pleb));
    }
    return;
}

void
LEMgr::check_reloaded_LEs()
{
    LENameVec::iterator itl;
    LEBaseMap::iterator pos;
    LEBase *ple;
    bool reload_f = false;

    if (reload_les.empty()) return;

    for (itl = reload_les.begin(); itl != reload_les.end();) {
        pos = lemap.find(*itl);
	if (pos == lemap.end()) {
	    itl = reload_les.erase(itl);
	    continue;
	}
	ple = pos->second;

	// remove if reloading LE is failed.
	if (ple->errorp()) {
	    lemap.erase(pos);
	    reload_f = true;
	    itl = reload_les.erase(itl);
	    delete ple;
	    continue;
	}
	// succeed to reload
	if (!ple->reloadp()) {
	    itl = reload_les.erase(itl);
	    reload_f = true;
	    continue;
	}
	++itl;
    }

    if (reload_f) {
        initialize_ledata();
    }
}

void
LEMgr::reload()
{
    LEBaseMap::iterator itl;
    LEBase* ple;

    check_new_LEs();
    for (itl = lemap.begin(); itl != lemap.end(); ) {
        ple = itl->second;
        if (!ple->reload()) {
	    /* TODO */
            if (ple->errorp()) {
	        lemap.erase(itl++);
	        delete ple;
	        continue;
	    }

	    LENameVec::iterator pos;
	    pos = find(reload_les.begin(), reload_les.end(), itl->first);
	    if (pos == reload_les.end())
		reload_les.push_back(itl->first);
	}
	++itl;
    }
    /* reload XML config file */
    iiim_le_xmlconf_load_file(pxmllecfg);
    /* re-initialized */
    initialize_ledata();
}

bool
LEMgr::initialize_ledata()
{
    LEBaseMap::iterator itl;
    LEBase* ple;
    iml_desktop_t *imdesktop = (iml_desktop_t *)NULL;
    const IMLangList *piml;
    const IMDescriptorList *pimd;
    const IMObjectWithDescList *pimodl;

    langlist.clear();
    imdesclist.clear();
    imobjectdesclist.clear();

    for (itl = lemap.begin();itl != lemap.end();++itl) {
	ple = itl->second;
        if (!imdesktop) {
	    imdesktop = ple->get_imldesktop();
        }
	piml = ple->get_langlist();
	if (piml) {
	    langlist.insert(langlist.end(), piml->begin(), piml->end());
	}
	pimd = ple->get_imdesclist();
	if (pimd) {
	    imdesclist.insert(imdesclist.end(), pimd->begin(), pimd->end());
	}
	pimodl = ple->get_objectdesclist();
	if (pimodl) {
	    imobjectdesclist.insert(imobjectdesclist.end(),
				    pimodl->begin(), pimodl->end());
	}
    }

    langlist.sort();
    langlist.unique();

    lemgr_dlmap.insert(make_pair(imdesktop, langlist));
    lemgr_ddmap.insert(make_pair(imdesktop, imdesclist));

    ledata_inited = true;
    return true;
}

const IMLangList*
LEMgr::get_all_langlist(
    iml_desktop_t *curr_desktop
)
{
    if (!ledata_inited) {
        initialize_ledata();
    } else {
        check_reloaded_LEs();
    }

    LEMgrDesktopLangMap::iterator itdl;

    for (itdl = lemgr_dlmap.begin();itdl != lemgr_dlmap.end();++itdl) {
	if (itdl->first == curr_desktop) {
	    return &itdl->second;
	}
    }
    
    return &langlist;
}

const IMDescriptorList*
LEMgr::get_all_imdesclist(
    iml_desktop_t *curr_desktop
)
{
    if (!ledata_inited) {
        initialize_ledata();
    } else {
        check_reloaded_LEs();
    }
    LEMgrDesktopDescMap::iterator itdd;

    for (itdd = lemgr_ddmap.begin(); itdd != lemgr_ddmap.end();++itdd) {
	if (itdd->first == curr_desktop) {
	    return &itdd->second;
	}
    }
    return &imdesclist;
}

const bool
LEMgr::update_imdesclist(
    IMLEName *LEname,
    IMLocale *Locales,
    int nLocales
)
{
    LEBaseMap::iterator itl;
    LEBase* ple;
    string curr_lename = string(LEname->id);
    bool result = false; 
    iml_desktop_t *curr_desktop;
    IMLangList uimll;
    IMDescriptorList uimdl;
    const IMLangList *piml;
    const IMDescriptorList *pimd;

    langlist.clear();
    imdesclist.clear();
    for (itl = lemap.begin();itl != lemap.end();++itl) {
	ple = itl->second;
	if (!itl->first.find(curr_lename)) {
	    LOG_DEBUG("LEMgr::update_imdesclist: filename [%s], curr_lename [%s]\n",itl->first.c_str(), curr_lename.c_str());
	    if (result = ple->update_imdesclist(LEname, Locales, nLocales, &curr_desktop, uimll, uimdl)) {
                langlist.insert(langlist.end(), uimll.begin(), uimll.end());
		lemgr_dlmap.insert(make_pair(curr_desktop, langlist));
                imdesclist.insert(imdesclist.end(), uimdl.begin(), uimdl.end());
		lemgr_ddmap.insert(make_pair(curr_desktop, imdesclist));
	    } 
	} else {
	    piml = ple->get_langlist();
	    if (piml) {
		langlist.insert(langlist.end(), piml->begin(), piml->end());
	    }
	    pimd = ple->get_imdesclist();
	    if (pimd) {
		imdesclist.insert(imdesclist.end(), pimd->begin(), pimd->end());
	    }
	}
    }
    return result; 
}

const IMObjectWithDescList*
LEMgr::get_all_imobjectdesclist()
{
    if (!ledata_inited) {
        initialize_ledata();
    } else {
        check_reloaded_LEs();
    }
    return &imobjectdesclist;
}

IMHotkeyProfileStruct*
LEMgr::get_hotkey_profiles(int *count_profiles)
{
    IMHotkeyProfileStruct *hkps;
    size_t count = le_hotkey_profile_map.size();
    size_t n;
    LEHotkeyProfileMap::iterator itl;

    if (count == 0)
	return NULL;

    LOG_DEBUG("LEMgr: get_hotkey_profiles: Number of Profiles [%d]\n", count);
    hkps = new IMHotkeyProfileStruct[count];
    *count_profiles = count;
    for (n = 0, itl = le_hotkey_profile_map.begin(); n < count && itl != le_hotkey_profile_map.end(); ++itl, n++)
	hkps[n] = *(itl->first);

    return hkps;
}

LEContext*
LEMgr::choose_LE(
    IMInputContext *pic
)
{
    const ICAttribute &attr = pic->get_icattr();
    LEBaseMap::iterator itl;
    LEBase* ple = NULL;
    const IMLangList *pimll;
    const IMLang *piml = NULL;
    const string *preq_lang = NULL;
    const u16string *preq_imdesc = NULL;

    if (!attr.get_inputlanguage().empty()) {
	preq_lang = attr.get_inputlanguage().get_string();
	if (!preq_lang) goto notfound;
    }
    if (!attr.get_inputmethod().empty()) {
	preq_imdesc = &attr.get_inputmethod();
    }

    if (preq_lang)
	LOG_DEBUG("requested lang = %s", preq_lang->c_str());
    if (preq_imdesc)
	LOG_DEBUG("requested desc = %s", preq_imdesc->get_string()->c_str());
    if (pxmllecfg != NULL && preq_lang != NULL && preq_imdesc == NULL) {
	/* choose LE from the order described in XML config */
	IIIMLEInfoList *list, *ltmp;

	LOG_DEBUG("searching LEs with lang %s", preq_lang->c_str());
	list = iiim_le_xmlconf_get_le_info_list(pxmllecfg, preq_lang->c_str());

	for (ltmp = list; ltmp != NULL; ltmp = ltmp->next) {
	    IIIMLEInfo *info = ltmp->data;
	    char *fname = new char[strlen(info->lename) + 1];
	    string filename;
	    string pathname;

	    strcpy(fname, info->lename);
	    filename = basename(fname);
	    strcpy(fname, info->lename);
	    pathname = dirname(fname);
	    /* assume that pathname should be /usr/lib/iiim/le/<LE name>/ or so
	       and lepath should be /usr/lib/iiim/le or so */
	    strcpy(fname, pathname.c_str());
	    pathname = dirname(fname);
	    delete [] fname;

	    LOG_DEBUG("searching module... %s", filename.c_str());
	    if (lepath[lepath.size()-1] == '/') {
		pathname += '/';
	    }
	    if (lepath != pathname)
		continue;
	    if ((itl = lemap.find(filename)) == lemap.end()) {
		continue;
	    } else {
		ple = itl->second;
		break;
	    }
	}
	if (ple == NULL) {
	    LOG_DEBUG("Not found the requested module. trying to find out with the traditional way...");
	    goto retry_traditional;
	}

	return ple->create_lecontext(pic, preq_lang->c_str());
    } else if (pxmllecfg != NULL && preq_lang == NULL && preq_imdesc == NULL) {
	/* probably the client library couldn't find any LEs they want to use */
	goto notfound;
    } else {
retry_traditional:
	for (itl = lemap.begin();itl != lemap.end();++itl) {
	    ple = itl->second;

	    // choose LE by language.
	    if (preq_lang) {
		IMLangList::const_iterator itlang;
		pimll = ple->get_langlist();
		if (!pimll) continue;
		itlang = find(pimll->begin(), pimll->end(), *preq_lang);
		if (itlang != pimll->end()) {
		    piml = &*itlang;
		} else {
		    continue;
		}
	    }

	    // choose LE by imname.
	    if (preq_imdesc) {
		IMDescriptorList::const_iterator itimd;
		const IMDescriptorList *pimd = ple->get_imdesclist();
		if (!pimd) continue;
		itimd = find_if(pimd->begin(), pimd->end(),
				IMDescriptorMatchPredicate(preq_imdesc));
		if (itimd != pimd->end()) {
		    break;
		} else {
		    continue;
		}
	    }
	    break;
	}
	if (itl == lemap.end()) goto notfound;

	if (piml) {
	    return ple->create_lecontext(pic, piml->get_id());
	}
    }

    return ple->create_lecontext(pic, NULL);

notfound:
    LOG_DEBUG("Using sumim_default");
    return psunim_default_lebase->create_lecontext(pic, NULL);
}

void
LEMgr::set_nsmap_config(
    IMNsMapStruct *nsm,
    int count
)
{
    LEBaseMap::iterator itl;
    LEBase* ple;

    for (itl = lemap.begin();itl != lemap.end();++itl) {
	ple = itl->second;
	ple->set_nsmap_config(nsm, count);
    }
}

LEMgr::LEMgr(
    const char* x_lepath
)
{
    lepath = x_lepath;
    pxmllecfg = NULL;
    ledata_inited = false;
    listup_LEs();
}

LEMgr::LEMgr(
    const char *x_lepath,
    IIIMLEXMLConf &xml
)
{
    lepath = x_lepath;
    pxmllecfg = &xml;
    ledata_inited = false;
    loading_preferred_les = false;
    listup_LEs();
}

LEMgr::LEMgr(
    const char *x_lepath,
    IIIMLEXMLConf &xml,
    bool preferred_loading
)
{
    lepath = x_lepath;
    pxmllecfg = &xml;
    ledata_inited = false;
    loading_preferred_les = preferred_loading;
    listup_LEs();
}

LEMgr::~LEMgr()
{
    delete_all(lemap);
    delete psunim_default_lebase;
    lemgr_dlmap.clear();
    lemgr_ddmap.clear();
}

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