#include <config.h>
#include <stdio.h>
#include <IMProtocolStruct.h>
#include "LE.hh"
#include "IMLock.hh"
#include "IMLog.hh"
#include "IMInputContext.hh"
#include "IMConnection.hh"
#include "IMDesktop.hh"

/********************************************************************************
                                     LEBase
********************************************************************************/

string
LEBase::get_lename(
    string &test_filename
)
{
    string::size_type spos, epos;
#ifdef WIN32
    epos = test_filename.rfind(".dll");
#else
    epos = test_filename.rfind(".so");
#endif
    if (epos == string::npos) {
	return string();
    }
#ifdef WIN32
    spos = test_filename.rfind("\\");
    if (epos == string::npos) {
	epos = test_filename.rfind("/");
    }
#else
    spos = test_filename.rfind("/");
#endif
    if (epos == string::npos) {
	spos = 0;
    } else {
	spos++;
    }
    if (epos - spos <= 0) return string();

    return test_filename.substr(spos, epos - spos);
}

bool
LEBase::reload()
{
    if (sunim_default)
        return true;
    /* some one use this LE */
    if(iml_if_context_ref_count > 0) {
        need_reload = true;
	return false;
    }
    /* clear */
    delete_all (imobjectdesclist);
    imobjectdesclist.clear();
    imdesclist.clear();
    langlist.clear();
    closeif();
    if (!loadif()) error = true;
    need_reload = false;
    LOG_DEBUG("Reloading LE(%s) is done", lename.c_str());
    return true;
}

void
LEBase::add_imobjectdesc(
    IMObjectDescriptorStruct *pol
)
{
    int i;
    if (!pol) return; /* empty! */
    for (i = 0; pol->leid; i++, pol++) {
	IMObjectWithDesc *pobj = new IMObjectWithDesc(*pol);
	pobj->log_obj();
	// TODO! we have to throw exception!
	if (!pobj) break;
	imobjectdesclist.push_back(pobj);
    }
}

bool
LEBase::loadif()
{
    string try_lename;

    if (sunim_default)
	try_lename = filename;
    else
	try_lename = get_lename(filename);
    
    if (try_lename.empty()) return false;

    {
	IMLock lock(get_leif_sync_object());
	iml_if = if_OpenIF(dirname.c_str(), try_lename.c_str(), "", False);
    }

    if (!iml_if) return false;
    if (!iml_if->lename) {
	if (sunim_default) {
	    lename = try_lename;
	    lename_hrn = "Default IM";
	} else {
	    return false;
	}
    } else {
	lename = string(iml_if->lename->id);
	lename_hrn = u16string(iml_if->lename->name);
    }
    need_thread_lock = iml_if->need_thread_lock;

    LOG_DEBUG("LE(%s) is loading.", lename.c_str());
    LOG_DEBUG("    Path=%s\n"
	      "    version=%s\n"
	      "    locale=%s\n"
	      "    need_thread_lock=%s",
	      iml_if->ifpath_name ? iml_if->ifpath_name : "(NULL)",
	      iml_if->if_version ? iml_if->if_version : "(NULL)",
	      iml_if->locale ? iml_if->locale : "(NULL)",
	      need_thread_lock ? "true" : "false");

    if (iml_if->locale_list) {
	IMLocale *piml;
	string l;
	for (piml = iml_if->locale_list; piml->id; piml++) {
	    langlist.push_back(IMLang(piml->id, u16string(piml->name)));
	    l = l.append(piml->id).append(", ");
	}
	LOG_DEBUG("    langs=%s", l.c_str());
    }
    if (langlist.empty()) {
	LOG_ERROR("LE:%s support no language. Skip it.", lename.c_str());
	return false;
    }

    imdesclist.push_back(IMDescriptor(u16string(lename.c_str()),    // IMNAME
				      lename_hrn,                   // HRN
				      "domainname-should-be-set.",  // domainanme
				      langlist,                     // Supporting langs
				      iml_if->hkm ? iml_if->hkm->hkps : 0));
    // LE's profile ID.
    le_dlmap.insert(make_pair((iml_desktop_t *)NULL, langlist));
    le_ddmap.insert(make_pair((iml_desktop_t *)NULL, imdesclist));

    add_imobjectdesc(iml_if->object_list);

    ifm = iml_if->ifm;

    return true;
}

iml_if_t*
LEBase::openif(const char* real_locale)
{
    if (iml_if_context) {
	iml_if_context_ref_count++;
	return iml_if_context;
    }

    if (!real_locale) {
	real_locale = langlist.begin()->get_id();
    }
    IMLock lock(get_leif_sync_object());
    
    iml_if_context = if_OpenIF(dirname.c_str(), lename.c_str(), real_locale, True);
    iml_if_context_ref_count = 1;
    return iml_if_context;
}

bool
LEBase::closeif()
{
    if (iml_if) {
	IMLock lock(get_leif_sync_object());
	if_CloseIF(iml_if, False);
	iml_if = NULL;
    }
    if (iml_if_context) {
	IMLock lock(get_leif_sync_object());
	if_CloseIF(iml_if_context, True);
	iml_if_context = NULL;
    }
    return true;
}

const IMLangList*
LEBase::get_langlist()
{
    return &langlist;
}

const IMDescriptorList*
LEBase::get_imdesclist()
{
    return &imdesclist;
}

const bool
LEBase::update_imdesclist(
    IMLEName *LEname,
    IMLocale *Locales,
    int nLocales,
    iml_desktop_t **desktop,
    IMLangList &ull,
    IMDescriptorList &udl
)
{
    lename = string(LEname->id);
    lename_hrn = u16string(LEname->name);

    if (Locales) {
	IMLocale *piml;
	string l;

	for (piml=Locales; piml->id; piml++) {
	    ull.push_back(IMLang(piml->id, u16string(piml->name)));
	    l = l.append(piml->id).append(", ");
	}
	LOG_DEBUG("LEBase::update_imdesclist: langs=%s",l.c_str());
    }
    if (ull.empty()) {
	LOG_ERROR("LE:%s support no language. Skip it.", lename.c_str());
	return false;
    }
    
    udl.push_back(IMDescriptor(u16string(lename.c_str()),          // IMNAME
			       lename_hrn,                   // HRN
			       "domainname-should-be-set.",  // domainanme
			       ull,                         // Supporting langs.
			       iml_if->hkm->hkps
			       ));
    *desktop = imd; 
    le_dlmap.insert(make_pair(imd, ull));
    le_ddmap.insert(make_pair(imd, udl));

    return true;
}

const void 
LEBase::set_nsmap_config(
    IMNsMapStruct *nsms,
    int num_nsm_entries
)
{
    iml_if->ns_map = nsms;
    iml_if->num_nsm_entries = num_nsm_entries;
}

const IMObjectWithDescList*
LEBase::get_objectdesclist()
{
    return &imobjectdesclist;
}

IMHotkeyManagerStruct*
LEBase::get_hotkey_manager()
{
  return iml_if->hkm;
}

LEContext*
LEBase::create_lecontext(
    IMInputContext *pic,
    const char* real_locale
)
{
    iml_if_t *iml_if_ctx = openif(real_locale);
    if (!iml_if_ctx) return NULL;
    LEContext* plec = new LEContext(pic, this, iml_if_ctx);
    if (!plec) return NULL;
    if (plec->errorp()) {
	delete plec;
	return NULL;
    }
    return plec;
}

void
LEBase::remove_lecontext(
    LEContext *lec
)
{
    iml_if_context_ref_count--;
    if (iml_if_context_ref_count == 0) {
	if (iml_if_context) {
	    IMLock lock(get_leif_sync_object());
	    if_CloseIF(iml_if_context, True);
	    iml_if_context = NULL;
	}
	if (need_reload) {
	    reload();
	}
    }
}

bool
LEBase::open_iml_desktop(
    IMDesktop *pdesk,
    iml_desktop_t *piml_desk
)
{
    vector<IMArg> args;
    pdesk->get_iml_desktop_args(args);
    imd = piml_desk;
    LOG_DEBUG("Open desktop (%s, %s).",
	      pdesk->get_desktop_display_name().c_str(),
	      lename.c_str());
    IMLock lock(get_leif_sync_object());
    if (iml_if->ifm->if_OpenDesktop(piml_desk, &args[0], args.size()) == False)
	return false;
    return true;
}

bool
LEBase::close_iml_desktop(
    iml_desktop_t *piml_desk
)
{
    LOG_DEBUG("Close desktop (%s).",
	      lename.c_str());
    IMLock lock(get_leif_sync_object());
    if (iml_if->ifm->if_CloseDesktop(piml_desk) == False)
	return false;
    LEDesktopLangMap::iterator itdl;
    for (itdl = le_dlmap.begin(); itdl != le_dlmap.end(); ++itdl) {
      if (itdl->first == piml_desk)
        le_dlmap.erase(itdl);
    }
    LEDesktopDescMap::iterator itdd;
    for (itdd = le_ddmap.begin(); itdd != le_ddmap.end(); ++itdd) {
      if (itdd->first == piml_desk)
        le_ddmap.erase(itdd);
    }
    return true;
}

LEBase::LEBase(
    const string &x_dirname,
    const string &x_filename
) : error(false), dirname(x_dirname), filename(x_filename), sunim_default(false), need_reload(false), need_thread_lock(false)
{
    iml_if = NULL;
    iml_if_context = NULL;
    iml_if_context_ref_count = 0;
    imd = NULL;
    
    if (!loadif()) error = true;
}

LEBase::LEBase(
    enum BUILTIN_OPTION option
)
{
    error = false;
    iml_if = NULL;
    iml_if_context = NULL;
    iml_if_context_ref_count = 0;

    if (option == SUNIM_DEFAULT) {
	dirname = "/";
	filename = "sunim_default";
	langlist.push_back(IMLang("C", "C"));
	sunim_default = true;
    } else {
	ERROR_INTERNAL("Invalid LEBase option");
    }
    if (!loadif()) error = true;
}

LEBase::~LEBase()
{
    closeif();
    delete_all(imobjectdesclist);
}

/********************************************************************************
                                     LEContext
********************************************************************************/

void
LEContext::bind_imlexec(
    IMLExec *pimlex
)
{
    // later, we should use TLS or something.
    s->SessionContext = pimlex;
    LOG_DEBUG("Bound imlexec:(%lx -> %lx)", (long) s, (long) pimlex);
}

bool
LEContext::set_values(
    IMArgList args,
    int num_args
)
{
    /* called from iiimd core, but ignore */
    if (!pbase->xsunim_p()
	&& (num_args == 1)
	&& (args[0].id == SC_REALIZE)) {
	return true;
    }

    /* at engine switching on en_US.UTF-8, SC_TRIGGER_ON_NOTIFY require
       realization. */
    if ((num_args == 1) && (args[0].id == SC_TRIGGER_ON_NOTIFY)) {
	if (!realize()) return false;
    }

    IMLock lock(get_leif_sync_object());
    return if_SetSCValues(s, args, num_args);
}

bool
LEContext::realize()
{
    if ((!realized) && (!pbase->xsunim_p())) {
	IMArg arg;
	IMSetArg(arg, SC_REALIZE, 0);
	IMLock lock(get_leif_sync_object());
	if (!if_SetSCValues(s, &arg, 1)) return false;
	realized = true;
    }
    return true;
}

bool
LEContext::send_event(
    IMLExec* pimlex,
    IMInputEvent* pimevent
)
{
    bind_imlexec(pimlex);
    IMLock lock(get_leif_sync_object(), need_thread_lock_p());
    if_SendEvent(s, pimevent);

    return true;
}
 
bool
LEContext::send_event_getvalues(
    IMLExec* pimlex,
    IMInputEvent* pimevent
)
{
    bind_imlexec(pimlex);
    IMLock lock(get_leif_sync_object(), need_thread_lock_p());
    if_SendEvent_AuxGet(s, pimevent);

    return true;
}

bool
LEContext::send_event_getvalues_finished(
    IMLExec* pimlex
)
{
    bind_imlexec(pimlex);
    IMLock lock(get_leif_sync_object(), need_thread_lock_p());
    if (s)
	{
	    s->If->m->iml_delete(s);
	}

    return true;
}

bool
LEContext::toggle_conversion(
    IMLExec *pimlex,
    bool flag
)
{
    IMArg arg;
    bind_imlexec(pimlex);

    if (flag)
	IMSetArg(arg, SC_TRIGGER_ON_NOTIFY, 0);
    else
	IMSetArg(arg, SC_TRIGGER_OFF_NOTIFY, 0);

    return set_values(&arg, 1);
}

bool
LEContext::reset(
    IMLExec *pimlex
)
{
    bind_imlexec(pimlex);
    IMLock lock(get_leif_sync_object(), need_thread_lock_p());
    if_ResetSC(s);

    return true;
}

bool
LEContext::toggle_focus(
    IMLExec *pimlex,
    bool flag
)
{
    bind_imlexec(pimlex);

    /* Before set forcus, require realization. */
    if (flag && !pbase->xsunim_p()) {
	if (!realize()) return false;
    }

    IMLock lock(get_leif_sync_object(), need_thread_lock_p());
    if (flag)
	if_SetSCFocus(s);
    else
	if_UnsetSCFocus(s);

    return true;
}

void
LEContext::destroy()
{
    bind_imlexec(NULL);
    delete this;
}

bool
LEContext::destroy(
    IMLExec *pimlex
)
{
    bind_imlexec(pimlex);
    delete this;
    return true;
}

bool
LEContext::initialize()
{
    IMConnection *pimc = get_inputcontext()->get_imconnection();
    IMDesktop *pdesk = pimc->get_desktop();

    vector<IMArg> args;
    pdesk->get_iml_desktop_args(args);
    iml_desktop = pdesk->request_iml_desktop(*this);
    if (!iml_desktop) return false;

    {
	IMLock lock(get_leif_sync_object());
	s = iml_construct_session(iml_desktop, &args[0], args.size());
	LOG_DEBUG("Create session context(%lx)", (long) s);
    }
    if (!s) return false;

    add_session_to_desktop(s);

    // inhibit IML inst. issuing.
    bind_imlexec(NULL);
    // Set additional values from the desktop.
    args.clear();
    pdesk->get_lecontext_args(args);
    get_inputcontext()->get_lecontext_args(args);
    if (!set_values(&args[0], args.size())) return false;

    return true;
}

LEContext::LEContext(
    IMInputContext *x_pic,
    LEBase *x_pbase,
    iml_if_t *iif
) :
    pic(x_pic), pbase(x_pbase), iml_if(iif)
{
    s = NULL;
    iml_desktop = NULL;
    realized = false;
    if (initialize())
	error = false;
    else
	error = true;
}

LEContext::~LEContext()
{
    IMConnection *pimc = get_inputcontext()->get_imconnection();
    IMDesktop *pdesk = pimc->get_desktop();

    {
	IMLock lock(get_leif_sync_object());
	if (s) {
	    LOG_DEBUG("Destroy session context(%lx)", (long) s);
	    if_DestroySC_WithoutDesktopDestruction(s);
	}
    }

    if (iml_desktop)
	pdesk->release_iml_desktop(*this, iml_desktop);

    pbase->remove_lecontext(this);
}

/*
 * This function expects to be invoked from X*LookupString() basically. The
 * function value is meaningless if it's not invoked from X*LookupString()
 */

iml_inst*
iml_execute_iml_wrapper(
    iml_session_t * s,
    iml_inst **rrv
)
{
    IMLExec *pimlex = (IMLExec*) s->SessionContext;

#ifdef DEBUG
    {
	int op;
	iml_inst *pcur;

	for (pcur = *rrv; pcur != (iml_inst*)0; pcur = pcur->next) {
	    op = pcur->opcode & ~IMM_CB_RESULT_REQUIRED;
	    if (!pimlex)
		LOG_DEBUG("Missing IML inst. (%d)", op);
	    else
		LOG_DEBUG("Issuing IML inst. (%d)", op);
	}
    }
#endif

    if (!pimlex) return NULL;
    return pimlex->push_insts(rrv);
}

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