/*

  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 <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "config.h"
#include <uim/uim.h>
#include <uim/uim-scm.h>
#include "context.h"
#include "gettext.h"

extern long siod_verbose_level;
extern char *uim_return_str;
extern char *uim_return_str_list[10];
/* duplicate definition */
#define UIM_RETURN_STR_LIST_SIZE ((sizeof(uim_return_str_list) \
                                   / sizeof(uim_return_str_list[0])) - 2)

extern void uim_init_skk_dic();
extern void uim_quit_skk_dic();
extern void uim_init_anthy();
extern void uim_quit_anthy();
extern void uim_init_prime();
extern void uim_quit_prime();

#ifdef HAVE_CANNA_RK_H
extern void uim_init_canna();
extern void uim_quit_canna();
#endif /* HAVE_CANNA_RK_H */

#ifdef HAVE_M17NLIB
extern void uim_init_m17nlib();
extern void uim_quit_m17nlib();
#endif /* HAVE_M17NLIB */

#define CONTEXT_ARRAY_SIZE 512
static uim_context context_array[CONTEXT_ARRAY_SIZE];
struct uim_im *uim_im_array;
int uim_nr_im;
static int uim_initialized;
static int uim_siod_fatal;
static FILE *uim_output = NULL;

FILE *
uim_scm_get_output(void) {
  return uim_output;
}

void
uim_scm_set_output(FILE *fp) {
  uim_output = fp;
}

void
uim_set_preedit_cb(uim_context uc,
		   void (*clear_cb)(void *ptr),
		   void (*pushback_cb)(void *ptr,
				       int attr,
				       const char *str),
		   void (*update_cb)(void *ptr))
{
  uc->preedit_clear_cb = clear_cb;
  uc->preedit_pushback_cb = pushback_cb;
  uc->preedit_update_cb = update_cb;
}

static void
get_context_id(uim_context is)
{
  int i;
  for (i = 0; i < CONTEXT_ARRAY_SIZE; i++) {
    if (!context_array[i]) {
      context_array[i] = is;
      is->id = i;
      return;
    }
  }
}

static void
put_context_id(uim_context is)
{
  context_array[is->id] = NULL;
}

uim_context
uim_create_context(void *ptr,
		   const char *enc,
		   const char *lang,
		   const char *engine,
		   struct uim_code_converter *conv,
		   void (*commit_cb)(void *ptr, const char *str))
{
  uim_context uc;
  int buf_len;
  char *buf;

  if (!conv) {
    conv = uim_iconv;
  }

  if (uim_siod_fatal || !conv) {
    return NULL;
  }

  uc = malloc(sizeof(*uc));
  if (!uc) {
    return NULL;
  }
  get_context_id(uc);
  uc->ptr = ptr;
  uc->is_enable = 1;
  uc->commit_cb = commit_cb;
  if (!enc) {
    enc = "UTF-8";
  }
  uc->encoding = strdup(enc);
  uc->conv_if = conv;
  uc->conv = NULL;
  /**/
  uc->nr_modes = 0;
  uc->modes = NULL;
  uc->mode = 0;
  /**/
  uc->propstr = NULL;
  uc->proplabelstr = NULL;
  /**/
  uc->preedit_clear_cb = NULL;
  uc->preedit_pushback_cb = NULL;
  uc->preedit_update_cb = NULL;
  /**/
  uc->mode_list_update_cb = NULL;
  /**/
  uc->mode_update_cb = NULL;
  /**/
  uc->prop_list_update_cb  = NULL;
  uc->prop_label_update_cb = NULL;
  /**/
  uc->candidate_selector_activate_cb = NULL;
  uc->candidate_selector_select_cb = NULL;
  uc->candidate_selector_shift_page_cb = NULL;
  uc->candidate_selector_deactivate_cb = NULL;
  /**/
  uc->nr_candidates = 0;
  uc->candidate_index = 0;
  /**/
  uc->cb_q.first_cb = NULL;
  uc->cb_q.tail_cb = NULL;
  uc->cb_q.flushing = 0;

  if (!lang) {
    lang = "()";
  }
  if (!engine) {
    engine = "()";
  }
  buf_len = strlen(lang) + strlen(engine) + 40;
  buf = malloc(buf_len);
  snprintf(buf, buf_len - 1,
	   "(create-context %d '%s '%s)", uc->id, lang, engine);
  uim_eval_string(uc, buf);

  free(buf);

  return uc;
}

void
uim_reset_context(uim_context uc)
 {
   char buf[30];
   snprintf(buf, 29, "(reset-handler %d)", uc->id);
   uim_eval_string(uc, buf);
}

void
uim_release_context(uim_context uc)
{
  char buf[30];
  int i;

  if(!uc)
    return;

  snprintf(buf, 29, "(release-context %d)", uc->id);
  uim_eval_string(uc, buf);
  put_context_id(uc);
  if (uc->conv) {
    uc->conv_if->release(uc->conv);
  }
  for (i = 0; i < uc->nr_modes; i++) {
    free(uc->modes[i]);
  }
  free(uc->propstr);
  free(uc->modes);
  free(uc->encoding);
  free(uc);
}

uim_context
uim_find_context(int id)
{
  return context_array[id];
}

int
uim_get_nr_modes(uim_context uc)
{
  return uc->nr_modes;
}

const char *
uim_get_mode_name(uim_context uc, int nth)
{
  if (nth < uc->nr_modes) {
    return uc->modes[nth];
  }
  return NULL;
}

void
uim_set_mode_list_update_cb(uim_context uc,
			    void (*update_cb)(void *ptr))
{
  uc->mode_list_update_cb = update_cb;
}


void
uim_set_prop_list_update_cb(uim_context uc,
			    void (*update_cb)(void *ptr, const char *str))
{
  uc->prop_list_update_cb = update_cb;
}


void
uim_set_prop_label_update_cb(uim_context uc,
			     void (*update_cb)(void *ptr, const char *str))
{
  uc->prop_label_update_cb = update_cb;
}


void
uim_prop_activate(uim_context uc, const char *str)
{
  char buf[100];
  
  if(!str)
    return;
      
  snprintf(buf, 99, "(prop-handler %d 'prop-activate \"%s\")", uc->id, str);
  uim_eval_string(uc, buf);
}

/** Update custom value from property message.
 * Update custom value from property message. All variable update is
 * validated by custom APIs rather than arbitrary sexp
 * evaluation. Custom symbol \a custom is quoted in sexp string to be
 * restricted to accept symbol literal only. This prevents arbitrary
 * sexp evaluation.
 */
void
uim_prop_update_custom(uim_context uc, const char *custom, const char *val)
{
  if(!custom || !val)
    return;

  UIM_EVAL_FSTRING3(uc, "(prop-handler %d 'prop-update-custom '%s %s)",
		    uc->id, custom, val);
}

int
uim_get_current_mode(uim_context uc)
{
  return uc->mode;
}

void
uim_set_mode(uim_context uc, int mode)
{
  char buf[100];
  uc->mode = mode;
  snprintf(buf, 99, "(mode-handler %d %d)", uc->id, mode);
  uim_eval_string(uc, buf);
}

void
uim_set_mode_cb(uim_context uc, void (*update_cb)(void *ptr,
						  int mode))
{
  uc->mode_update_cb = update_cb;
}

void
uim_prop_list_update(uim_context uc)
{
  if(uc && uc->propstr)
    uc->prop_list_update_cb(uc->ptr, uc->propstr);
}

void
uim_prop_label_update(uim_context uc)
{
  if(uc && uc->proplabelstr)
    uc->prop_label_update_cb(uc->ptr, uc->proplabelstr);
}

int
uim_get_nr_im(uim_context uc)
{
  int i, nr = 0;

  if(!uc)
    return 0;

  for (i = 0; i < uim_nr_im; i++) {
    if (uc->conv_if->is_convertible(uc->encoding, uim_im_array[i].encoding)) {
      nr ++;
    }
  }
  return nr;
}

static struct uim_im *
get_nth_im(uim_context uc, int nth)
{
  int i,n=0;
  for (i = 0; i < uim_nr_im; i++) {
    if (uc->conv_if->is_convertible(uc->encoding, uim_im_array[i].encoding)) {
      if (n == nth) {
	return &uim_im_array[i];
      }
      n++;
    }
  }
  return NULL;
}

const char *
uim_get_im_name(uim_context uc, int nth)
{
  struct uim_im *im = get_nth_im(uc, nth);

  if(im) {
    return im->name;
  }
  return NULL;
}

const char *
uim_get_im_language(uim_context uc, int nth)
{
  struct uim_im *im = get_nth_im(uc, nth);

  if(im) {
    return im->lang;
  }
  return NULL;
}

const char *
uim_get_im_encoding(uim_context uc, int nth)
{
  struct uim_im *im = get_nth_im(uc, nth);

  if(im) {
    return im->encoding;
  }
  return NULL;
}

static int
load_conf()
{
  struct passwd *pw;
  char *fn, *cmd;
  FILE *fp;
  long svl = siod_verbose_level;
  long ret;

  fn = getenv("LIBUIM_USER_SCM_FILE");

  if (!fn) {
    pw = getpwuid(getuid());
    fn = malloc(strlen(pw->pw_dir)+10);
    sprintf(fn, "%s/.uim", pw->pw_dir);
  } else {
    fn = strdup(fn);
  }

  fp = fopen(fn, "r");
  if (fp == NULL) {
    return -1;
  }
  fclose(fp);

  if (siod_verbose_level < 1) {
    siod_verbose_level = 1;	/* to show error message of SIOD */
  }
  cmd = malloc(strlen(fn) + 40);
  sprintf(cmd, "(*catch 'errobj (load \"%s\" #f #f))", fn);
  ret = repl_c_string(cmd, 0, 0);
  siod_verbose_level = svl;
  free(fn);
  free(cmd);
  return ret;
}

int uim_set_candidate_selector_cb(uim_context uc,
				  void (*activate_cb)(void *ptr, int nr, int display_limit),
				  void (*select_cb)(void *ptr, int index),
				  void (*shift_page_cb)(void *ptr, int direction),
				  void (*deactivate_cb)(void *ptr))
{
  uc->candidate_selector_activate_cb = activate_cb;
  uc->candidate_selector_select_cb = select_cb;
  uc->candidate_selector_deactivate_cb = deactivate_cb;
  uc->candidate_selector_shift_page_cb = shift_page_cb;

  return 0;
}

uim_candidate
uim_get_candidate(uim_context uc, int index, int accel_enumeration_hint)
{
  char buf[100];
  uim_candidate cand = malloc(sizeof(*cand));
  snprintf(buf, 99, "(get-candidate %d %d %d)", uc->id, index, accel_enumeration_hint);
  uim_eval_string(uc, buf);

  if (uim_return_str_list[0] && uim_return_str_list[1]) {
    cand->str = uc->conv_if->convert(uc->conv, uim_return_str_list[0]);
    cand->heading_label = uc->conv_if->convert(uc->conv, uim_return_str_list[1]);    
  } else {
    cand->str = NULL;
    cand->heading_label = NULL;
  }

  return cand;
}

const char *
uim_candidate_get_cand_str(uim_candidate cand)
{
  return cand->str;
}

const char *
uim_candidate_get_heading_label(uim_candidate cand)
{
  return cand->heading_label;
}

void
uim_candidate_free(uim_candidate cand)
{
  free(cand->str);
  free(cand->heading_label);
  /* free(cand->annotation); */
  free(cand);
}

int uim_get_candidate_index(uim_context uc)
{
  return 0;
}

void
uim_set_candidate_index(uim_context uc, int nth)
{
  char buf[100];
  snprintf(buf, 99, "(set-candidate-index %d %d)", uc->id, nth);
  uim_eval_string(uc, buf);
  return ;
}

static void
exit_hook(void)
{
  uim_siod_fatal = 1;
}

static void
uim_init_scm()
{
  int i;
  char *scm_files;
  char *siod_argv[] =
    {
      "siod",
      "-h100000:10",
      "-o1000",
      "-s200000",
      "-n128",
      "-v0",
    };
  char verbose_argv[4] = "-v4";
  char *env;

  if ((env = getenv("LIBUIM_VERBOSE"))) {
    if (isdigit(*env)) {
      if (isdigit(*(env+1)))
	verbose_argv[2] = '9';	/* SIOD's max verbose level is 5 */
      else
	verbose_argv[2] = *env;
    }
    siod_argv[5] = verbose_argv;
  }
  /* init siod */
  siod_init (6, siod_argv, 1, uim_output);
  set_fatal_exit_hook(exit_hook);
  /**/
  uim_init_im_subrs();
  uim_init_util_subrs();
  uim_init_key_subrs();
  uim_init_table_subrs();
  uim_init_anthy();
  uim_init_prime();
  uim_init_skk_dic();
#ifdef HAVE_CANNA_RK_H
  uim_init_canna();
#endif /* HAVE_CANNA_RK_H */
#ifdef HAVE_M17NLIB
  uim_init_m17nlib();
#endif /* HAVE_M17NLIB */

  scm_files = getenv("LIBUIM_SCM_FILES");
  if (scm_files) {
    siod_set_lib_path(scm_files);
  } else {
    siod_set_lib_path(SCM_FILES);
  }

  uim_scm_load_file("im.scm");
  uim_scm_load_file("loader.scm");
  if (getenv("LIBUIM_VANILLA") ||
      load_conf() == -1) {
    uim_scm_load_file("default.scm");
  }
  if (getenv("LIBUIM_VERBOSE")) {
    siod_verbose_level = 10;
  }

  uim_return_str = NULL;
  for (i = 0; i < UIM_RETURN_STR_LIST_SIZE; i++){
    uim_return_str_list[i] = NULL;
  }
}

int
uim_init(void)
{
  if (uim_initialized) {
    return 0;
  }
  if (!uim_output) {
    uim_output = stderr;
  }
  uim_im_array = NULL;
  uim_nr_im = 0;
  uim_init_scm();
  uim_initialized = 1;
  return 0;
}

void
uim_quit(void)
{
  int i;

  if (!uim_initialized) {
    return;
  }

  /* release still active contexts */
  for (i = 0; i < CONTEXT_ARRAY_SIZE; i++) {
    if (context_array[i]) {
      uim_release_context(context_array[i]);
    }
  }
  /**/
  uim_quit_skk_dic();
  uim_quit_anthy();
  uim_quit_prime();
#ifdef HAVE_CANNA_RK_H
  uim_quit_canna();
#endif /* HAVE_CANNA_RK_H */
#ifdef HAVE_M17NLIB
  uim_quit_m17nlib();
#endif /* HAVE_M17NLIB */
  siod_quit();
  uim_output = NULL;
  uim_initialized = 0;
}
