/*

  Copyright (c) 2003,2004 uim Project http://uim.freedesktop.org/

  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
  3. Neither the name of authors nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  SUCH DAMAGE.

*/

#include "config.h"
#ifdef HAVE_M17NLIB
#include <stdlib.h>
#include <string.h>
#include <m17n.h>
#include "context.h"

static int m17nlib_ok;
static MConverter *converter;

static int nr_input_methods;
static struct im_ {
  char *lang;
  char *name;
  MInputMethod *im;
} *im_array;

static int max_input_contexts;
static struct ic_ {
  MInputContext *mic;
  char *str;
} *ic_array;

static int
unused_ic_id()
{
  int i;
  for (i = 0; i < max_input_contexts; i++) {
    if (!ic_array[i].mic) {
      return i;
    }
  }
  ic_array = realloc(ic_array, sizeof(struct ic_) * (max_input_contexts+1));
  ic_array[max_input_contexts].mic = NULL;
  max_input_contexts++;
  return max_input_contexts - 1;
}

static char *
remap_lang_name(char *lang)
{
  static struct lang_map_ {
    char *lib_lang;
    char *lang;
  } lang_map[] = {
    {"Japanese", "ja"},
    {"Amharic", "am"},
    {"Assamese", "as"},
    {"Bengali", "bn"},
    {"Tibetan", "bo"},
    {"Greek", "el"},
    {"Arabic", "ar"},
    /*    {"Farsi", ""},*/
    {"Gujarati", "gu"},
    {"Hebrew", "he"},
    {"Hindi", "hi"},
    {"Croatian", "hr"},
    {"Kazakh", "kk"},
    /*    {"Caombodia", ""},*/
    {"Kannada", "kn"},
    {"Korean", "ko"},
    {"Laothian", "lo"},
    {"Malayalam", "ml"},
    {"Oriya", "or"},
    {"Punjabi", "pa"},/* Panjabi ? */
    {"Russian", "ru"},
    {"Slovak", "sl"},/* Slovenia ? */
    {"Serbian", "sr"},
    {"Tamil", "ta"},
    {"Telugu", "te"},
    {"Thai", "th"},
    {"Vietnamese", "vi"},
    {"Chinese", "zh"},
    {NULL, NULL}
  };

  struct lang_map_ *l;
  for (l = lang_map; l->lib_lang; l++) {
    if (!strcmp(lang, l->lib_lang)) {
      return l->lang;
    }
  }
  return NULL;
}

static void
pushback_input_method(MInputMethod *im,
		      char *lib_lang, char *name)
{
  char *lang = remap_lang_name(lib_lang);
  if (!lang) {
    return ;
  }

  im_array = realloc(im_array, 
		     sizeof(struct im_) * (nr_input_methods + 1));
  im_array[nr_input_methods].lang = strdup(lang);
  im_array[nr_input_methods].name = strdup(name);
  im_array[nr_input_methods].im = im;
  nr_input_methods++;
}

static void
preedit_start_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"preedit start\n");
}

static void 
preedit_draw_cb(MInputContext *ic, MSymbol command)
{
  char buf[1024];
  fprintf(stderr,"preedit draw\n");

  mconv_rebind_buffer(converter, buf, 1024);
  mconv_encode(converter, ic->preedit);
  buf[converter->nbytes] = 0;

  fprintf(stderr,"%s\n",buf);

}

static void 
preedit_done_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"preedit done\n");
}

static void
status_start_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"status start\n");
}

static void 
status_draw_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"status draw\n");
}

static void 
status_done_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"status done\n");
}

static void
candidates_start_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"candidate start\n");
}

static void 
candidates_draw_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"candidate draw\n");
}

static void 
candidates_done_cb(MInputContext *ic, MSymbol command)
{
  fprintf(stderr,"candidate done\n");
}

static MPlist *
register_callbacks(MPlist *callback_list)
{
  if(!callback_list)
    callback_list = mplist();

  mplist_add(callback_list, Minput_preedit_start, preedit_start_cb);
  mplist_add(callback_list, Minput_preedit_draw,  preedit_draw_cb);
  mplist_add(callback_list, Minput_preedit_done,  preedit_done_cb);
  mplist_add(callback_list, Minput_status_start,  status_start_cb);
  mplist_add(callback_list, Minput_status_draw,   status_draw_cb);
  mplist_add(callback_list, Minput_status_done,   status_done_cb);
  mplist_add(callback_list, Minput_candidates_start, candidates_start_cb);
  mplist_add(callback_list, Minput_candidates_draw,  candidates_draw_cb);
  mplist_add(callback_list, Minput_candidates_done,  candidates_done_cb);

  return callback_list;
}

static LISP
init_m17nlib()
{
  MPlist *imlist, *elm;
  MSymbol utf8 = msymbol("utf8");
  M17N_INIT();
  nr_input_methods = 0;
  im_array = NULL;
  max_input_contexts = 0;
  ic_array = NULL;

  imlist = mdatabase_list(msymbol("input-method"), Mnil, Mnil, Mnil);
  for (elm = imlist; mplist_key(elm) != Mnil; elm = mplist_next(elm)) {
    MDatabase *mdb = mplist_value(elm);
    MSymbol *tag = mdatabase_tag(mdb);
    if (tag[1] != Mnil) {
      MInputMethod *im = minput_open_im(tag[1], tag[2], NULL);
      if (im) {
	MSymbol lang = msymbol_get(im->language, Mlanguage);
	pushback_input_method(im, msymbol_name(lang),
			      msymbol_name(im->name));

	  im->driver.callback_list = register_callbacks(im->driver.callback_list);
      }
    }
  }
  m17n_object_unref(imlist);
  converter = mconv_buffer_converter(utf8, NULL, 0);
  if (!converter) {
    return NIL;
  }
  m17nlib_ok = 1;
  return siod_true_value();
}

void
uim_quit_m17nlib()
{
  if (converter) {
    mconv_free_converter(converter);
    converter = NULL;
  }
  if (m17nlib_ok) {
    M17N_FINI();
    m17nlib_ok = 0;
  }
  free(im_array);
  free(ic_array);
}

static LISP
get_nr_input_methods()
{
  return intcons(nr_input_methods);
}

static LISP
get_input_method_name(LISP nth_)
{
  int nth = get_c_int(nth_);
  if (nth < nr_input_methods) {
    char *name = alloca(strlen(im_array[nth].name) + 20);
    sprintf(name, "m17n-%s-%s", im_array[nth].lang, im_array[nth].name);
    return rintern(name);
  }
  return NIL;
}

static LISP
get_input_method_lang(LISP nth_)
{
  int nth = get_c_int(nth_);
  if (nth < nr_input_methods) {
    char *lang = im_array[nth].lang;
    return strcons(strlen(lang), lang);
  }
  return NIL;
}

static MInputMethod *
find_im_by_name(char *name)
{
  int i;
  if (strncmp(name, "m17n-", 5) != 0) {
    return NULL;
  }
  name = &name[5];
  for (i = 0; i < nr_input_methods; i++) {
    char buf[100];
    sprintf(buf, "%s-%s", im_array[i].lang, im_array[i].name);
    if (!strcmp(name, buf)) {
      return im_array[i].im;
    }
  }
  return NULL;
}


static LISP
alloc_id(LISP name_)
{
  int id = unused_ic_id();
  char *name = uim_get_c_string(name_);
  MInputMethod *im = find_im_by_name(name);
  if (im) {
    ic_array[id].mic = minput_create_ic(im, NULL);
    ic_array[id].str = NULL;
  }
  return intcons(id);
}

static LISP
free_id(LISP id_)
{
  int id = get_c_int(id_);
  if (id < max_input_contexts) {
    struct ic_ *ic = &ic_array[id];
    if (ic->mic) {
      minput_destroy_ic(ic->mic);
      ic->mic = NULL;
      if (ic->str) {
	free(ic->str);
	ic->str = NULL;
      }
    }
  }
  return NIL;
}

static MSymbol
get_key_sym(int ch)
{
  if (ch < 127) {
    char buf[2];
    buf[1] = 0;
    buf[0] = ch;
    return msymbol(buf);
  }
  return Mnil;
}

static LISP
push_key(LISP id_, LISP key_, LISP mod_)
{
  int id = get_c_int(id_);
  int ch;
  char buf[1024];
  MSymbol key;
  MText *produced;
  MInputContext *ic = ic_array[id].mic;
  if NINTNUMP(key_)
	       return siod_true_value();
  ch = get_c_int(key_);
  key = get_key_sym(ch);
  if (key == Mnil) {
    return siod_true_value();
  }
  if (minput_filter(ic, key, NULL)) {
    return NIL;
  }
  produced = mtext();
  minput_lookup(ic, key, NULL, produced);
  mconv_rebind_buffer(converter, buf, 1024);
  mconv_encode(converter, produced);
  buf[converter->nbytes] = 0;
  ic_array[id].str = strdup(buf);
  m17n_object_unref(produced);

  return NIL;
}

static LISP
get_str(LISP id_)
{
  int id = get_c_int(id_);
  char *str = ic_array[id].str;
  if (str) {
    LISP l = strcons(strlen(str), str);
    free(ic_array[id].str);
    ic_array[id].str = NULL;
    return l;
  }
  return NIL;
}

void
uim_init_m17nlib()
{
  init_subr_0("m17nlib-lib-init", init_m17nlib);
  init_subr_0("m17nlib-lib-nr-input-methods", get_nr_input_methods);
  init_subr_1("m17nlib-lib-nth-input-method-lang", get_input_method_lang);
  init_subr_1("m17nlib-lib-nth-input-method-name", get_input_method_name);
  init_subr_1("m17nlib-lib-alloc-context", alloc_id);
  init_subr_1("m17nlib-lib-free-context", free_id);
  init_subr_3("m17nlib-lib-push-key", push_key);
  init_subr_1("m17nlib-lib-get-str", get_str);
}

#endif /* HAVE_M17NLIB */
