/* Copyright (C) 2004-2011 Edward Der-Hua Liu, Hsin-Chu, Taiwan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation version 2.1
 * of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "hime.h"
#include "gtab.h"
#include <signal.h>
#include <X11/extensions/XTest.h>
#include "gst.h"
#include "pho.h"
#include "im-client/hime-im-client-attr.h"
#include "hime-module.h"
#include "hime-module-cb.h"
#include "win-sym.h"

#define STRBUFLEN 64

extern Display *dpy;
#if USE_XIM
extern XIMS current_ims;
static IMForwardEventStruct *current_forward_eve;
#endif
extern gboolean win_kbm_inited;
extern int hime_show_win_kbm;
extern gboolean win_kbm_on;
void show_win_kbm();
void hide_win_kbm();
void update_win_kbm();

static char *callback_str_buffer;
Window focus_win;
static int timeout_handle;
char *output_buffer;
int output_bufferN;
static char *output_buffer_raw, *output_buffer_raw_bak;
static int output_buffer_rawN;
void set_wselkey(char *s);
void gtab_set_win1_cb();
void toggle_symbol_table();

gboolean old_capslock_on;

extern gboolean key_press_alt;
extern gboolean key_press_ctrl;

#if TRAY_ENABLED
int hime_tray_display;
#endif
void init_gtab(int inmdno);

char current_method_type()
{
//  dbg("default_input_method %d\n",default_input_method);
  if (!current_CS)
    return inmd[default_input_method].method_type;

//  dbg("current_CS->in_method %d\n", current_CS->in_method);
  return inmd[current_CS->in_method].method_type;
}

void send_fake_key_eve(KeySym key)
{
  KeyCode kc = XKeysymToKeycode(dpy, key);
  XTestFakeKeyEvent(dpy, kc, True, CurrentTime);
  XTestFakeKeyEvent(dpy, kc, False, CurrentTime);
}

void fake_shift()
{
  send_fake_key_eve(XK_Shift_L);
}

void swap_ptr(char **a, char **b)
{
  char *t = *a;
  *a = *b;
  *b = t;
}

int force_preedit=0;
void force_preedit_shift()
{
  send_fake_key_eve(XK_Shift_L);
  force_preedit=1;
}

void send_text_call_back(char *text)
{
  callback_str_buffer = (char *)realloc(callback_str_buffer, strlen(text)+1);
  strcpy(callback_str_buffer, text);
  fake_shift();
}

void output_buffer_call_back()
{
  swap_ptr(&callback_str_buffer, &output_buffer);

  if (output_buffer)
    output_buffer[0] = 0;
  output_bufferN = 0;

  fake_shift();
}

ClientState *current_CS;
static ClientState temp_CS;

void save_CS_current_to_temp()
{
  if (!hime_single_state)
    return;

//  dbg("save_CS_current_to_temp\n");
  temp_CS.b_half_full_char = current_CS->b_half_full_char;
  temp_CS.im_state = current_CS->im_state;
  temp_CS.in_method = current_CS->in_method;
  temp_CS.tsin_pho_mode = current_CS->tsin_pho_mode;
}


void save_CS_temp_to_current()
{
  if (!hime_single_state)
    return;

//  dbg("save_CS_temp_to_current\n");
  current_CS->b_half_full_char = temp_CS.b_half_full_char;
  current_CS->im_state = temp_CS.im_state;
  current_CS->in_method = temp_CS.in_method;
  current_CS->tsin_pho_mode = temp_CS.tsin_pho_mode;
}

int current_shape_mode()
{
// INFO: 1: Full 0: Half / Error: !current_CS
  return current_CS &&
    (
      current_CS->im_state == HIME_STATE_ENG_FULL ||
      (current_CS->im_state != HIME_STATE_DISABLED && current_method_type()!=method_type_TSIN && current_CS->b_half_full_char) ||
      (current_CS->im_state == HIME_STATE_CHINESE && current_method_type()==method_type_TSIN && tss.tsin_half_full)
    );
}

gboolean init_in_method(int in_no);


void clear_output_buffer()
{
  if (output_buffer)
    output_buffer[0] = 0;
  output_bufferN = 0;

  swap_ptr(&output_buffer_raw, &output_buffer_raw_bak);

  if (output_buffer_raw)
    output_buffer_raw[0] = 0;
  output_buffer_rawN = 0;
}

gboolean gb_output = FALSE;

void toggle_gb_output()
{
  gb_output = !gb_output;
}

/* Force to output Simplified Chinese */
void sim_output()
{
  gb_output = TRUE;
}

/* Force to output original string, usually are Traditional Chinese */
void trad_output()
{
  gb_output = FALSE;
}

static void append_str(char **buf, int *bufN, char *text, int len)
{
  int requiredN = len + 1 + *bufN;
  *buf = (char *)realloc(*buf, requiredN);
  (*buf)[*bufN] = 0;
  strcat(*buf, text);
  *bufN += len;
}

int trad2sim(char *str, int strN, char **out);
void add_ch_time_str(char *s);

void send_text(char *text)
{
  char *filter;

  if (!text)
    return;
  int len = strlen(text);

  add_ch_time_str(text);

  append_str(&output_buffer_raw, &output_buffer_rawN, text, len);

  char *utf8_gbtext = NULL;

  if (gb_output) {
    len = trad2sim(text, len, &utf8_gbtext);
    text = utf8_gbtext;
  }

//direct:
  filter = getenv("HIME_OUTPUT_FILTER");
  char filter_text[512];

  if (filter) {
    int pfdr[2], pfdw[2];

    if (pipe(pfdr) == -1) {
      dbg("cannot pipe r\n");
      goto next;
    }

    if (pipe(pfdw) == -1) {
      dbg("cannot pipe w\n");
      goto next;
    }

    int pid = fork();

    if (pid < 0) {
      dbg("cannot fork filter\n");
      goto next;
    }

    if (pid) {
      close(pfdw[0]);
      close(pfdr[1]);
      write(pfdw[1], text, len);
      close(pfdw[1]);
      int rn = read(pfdr[0], filter_text, sizeof(filter_text) - 1);
      filter_text[rn] = 0;
//      puts(filter_text);
      close(pfdr[0]);
      text = filter_text;
      len = rn;
    } else {
      close(pfdr[0]);
      close(pfdw[1]);
      dup2(pfdw[0], 0);
      dup2(pfdr[1], 1);
      if (execl(filter, filter, NULL) < 0) {
        dbg("execl %s err", filter);
        goto next;
      }
    }

  }
next:
  if (len) {
    append_str(&output_buffer, &output_bufferN, text, len);
  }

  free(utf8_gbtext);
}

void send_output_buffer_bak()
{
  send_text(output_buffer_raw_bak);
}

void send_utf8_ch(char *bchar)
{
  char tt[CH_SZ+1];
  int len = utf8_sz(bchar);

  memcpy(tt, bchar, len);
  tt[len]=0;

  send_text(tt);
}


void send_ascii(char key)
{
  send_utf8_ch(&key);
}

#if USE_XIM
void export_text_xim()
{
  char *text = output_buffer;

  if (!output_bufferN)
    return;

  XTextProperty tp;
#if 0
  char outbuf[512];
  utf8_big5(output_buffer, outbuf);
  text = outbuf;
  XmbTextListToTextProperty(dpy, &text, 1, XCompoundTextStyle, &tp);
#else
  Xutf8TextListToTextProperty(dpy, &text, 1, XCompoundTextStyle, &tp);
#endif

#if DEBUG && 0
  dbg("send_utf8_ch: %s\n", text);
#endif

  ((IMCommitStruct*)current_forward_eve)->flag |= XimLookupChars;
  ((IMCommitStruct*)current_forward_eve)->commit_string = (char *)tp.value;
  IMCommitString(current_ims, (XPointer)current_forward_eve);

  clear_output_buffer();

  XFree(tp.value);
}


static void bounce_back_key()
{
    IMForwardEventStruct forward_ev = *(current_forward_eve);
    IMForwardEvent(current_ims, (XPointer)&forward_ev);
}
#endif

void hide_win0();
void hide_win_gtab();
void hide_win_pho();

int current_in_win_x = -1, current_in_win_y = -1;  // request x/y

void reset_current_in_win_xy()
{
  current_in_win_x = current_in_win_y = -1;
}

HIME_module_callback_functions *module_cb1(ClientState *cs)
{
  return inmd[cs->in_method].mod_cb_funcs;
}

HIME_module_callback_functions *module_cb()
{
  if (!current_CS)
    return NULL;
  return module_cb1(current_CS);
}

void hide_in_win(ClientState *cs)
{
  if (!cs) {
#if 0
    dbg("hide_in_win: ic is null\n");
#endif
    return;
  }
#if 0
  dbg("hide_in_win %d\n", ic->in_method);
#endif

  if (timeout_handle) {
    g_source_remove(timeout_handle);
    timeout_handle = 0;
  }

  switch (current_method_type()) {
    case method_type_PHO:
      hide_win_kbm();
      hide_win_pho();
      break;
#if USE_TSIN
    case method_type_TSIN:
//      flush_tsin_buffer();
      hide_win_kbm();
      hide_win0();
      break;
#endif
    case method_type_MODULE:
      if (inmd[cs->in_method].mod_cb_funcs)
        module_cb1(cs)->module_hide_win();
      hide_win_gtab();
      break;
    default:
      hide_win_gtab();
  }

  reset_current_in_win_xy();
}

void show_win_pho();
void show_win0();
void show_win_gtab();
#if TRAY_ENABLED
void disp_tray_icon();
#endif

void check_CS()
{
  if (!current_CS) {
//    dbg("!current_CS");
    current_CS = &temp_CS;
//    temp_CS.input_style = InputStyleOverSpot;
//    temp_CS.im_state = HIME_STATE_CHINESE;
#if TRAY_ENABLED
    disp_tray_icon();
#endif
  }
  else {
#if 0
    if (hime_single_state)
      save_CS_temp_to_current();
    else
#endif
      temp_CS = *current_CS;
  }
}

gboolean force_show;
void show_input_method_name_on_gtab();

void show_in_win(ClientState *cs)
{
  if (!cs) {
#if 0
    dbg("show_in_win: ic is null");
#endif
    return;
  }

  switch (current_method_type()) {
    case method_type_PHO:
      show_win_pho();
      break;
#if USE_TSIN
    case method_type_TSIN:
      show_win0();
      break;
#endif
    case method_type_MODULE:
      if (!module_cb1(cs))
        return;
      module_cb1(cs)->module_show_win();
      show_input_method_name_on_gtab();
      break;
    default:
      show_win_gtab();
      show_input_method_name_on_gtab();
      break;
  }

  if (hime_show_win_kbm &&
      (current_CS->im_state == HIME_STATE_CHINESE) &&
      (current_method_type() != method_type_MODULE) &&
      (current_method_type() != method_type_SYMBOL_TABLE))
  {
    show_win_kbm();
    update_win_kbm();
  }

#if 0
  show_win_stautus();
#endif
}


void move_win_gtab(int x, int y);
void move_win0(int x, int y);
void move_win_pho(int x, int y);

void move_in_win(ClientState *cs, int x, int y)
{
  check_CS();

  if (current_CS && current_CS->fixed_pos) {
    x = current_CS->fixed_x;
    y = current_CS->fixed_y;
  } else
  if (hime_input_style == InputStyleRoot) {
    x = hime_root_x;
    y = hime_root_y;
  }

#if 0
  dbg("move_in_win %d %d\n",x, y);
#endif
#if 0
  if (current_in_win_x == x && current_in_win_y == y)
    return;
#endif
  current_in_win_x = x ; current_in_win_y = y;

  switch (current_method_type()) {
    case method_type_PHO:
      move_win_pho(x, y);
      break;
#if USE_TSIN
    case method_type_TSIN:
      move_win0(x, y);
      break;
#endif
    case method_type_MODULE:
      if (inmd[cs->in_method].mod_cb_funcs)
        module_cb1(cs)->module_move_win(x, y);
      move_win_gtab(x, y);
      break;
    default:
      if (!cs->in_method)
        return;
      move_win_gtab(x, y);
  }
}

static int xerror_handler(Display *d, XErrorEvent *eve)
{
  return 0;
}

void getRootXY(Window win, int wx, int wy, int *tx, int *ty)
{
  if (!win) {
	*tx = wx;
	*ty = wy;
	return;
  }

  Window ow;
  XErrorHandler olderr = XSetErrorHandler((XErrorHandler)xerror_handler);
  XTranslateCoordinates(dpy,win,root,wx,wy,tx,ty,&ow);
  XSetErrorHandler(olderr);
}


void move_IC_in_win(ClientState *cs)
{
#if 0
   dbg("move_IC_in_win %d,%d\n", cs->spot_location.x, cs->spot_location.y);
#endif
   Window inpwin = cs->client_win;

   if (!inpwin) {
     dbg("no inpwin\n");
     return;
   }

   // non focus win filtering is done in the client lib
   if (inpwin != focus_win && focus_win && !cs->b_hime_protocol) {
      return;
   }

   int inpx = cs->spot_location.x;
   int inpy = cs->spot_location.y;

   XWindowAttributes att;
   XGetWindowAttributes(dpy, inpwin, &att);
// chrome window is override_redirect
//   if (att.override_redirect)
//     return;

   if (inpx >= att.width)
     inpx = att.width - 1;
   if (inpy >= att.height)
     inpy = att.height - 1;
   int tx,ty;
   getRootXY(inpwin, inpx, inpy, &tx, &ty);

#if 0
   dbg("move_IC_in_win inpxy:%d,%d txy:%d,%d\n", inpx, inpy, tx, ty);
#endif

   move_in_win(cs, tx, ty+1);
}


void update_in_win_pos()
{
  check_CS();

//  dbg("update_in_win_pos %x %d\n", current_CS, current_CS->input_style);

  if (current_CS->input_style == InputStyleRoot) {
    Window r_root, r_child;
    int winx, winy, rootx, rooty;
    u_int mask;

    XQueryPointer(dpy, root, &r_root, &r_child, &rootx, &rooty, &winx, &winy, &mask);

    winx++; winy++;

    Window inpwin = current_CS->client_win;
#if 0
    dbg("update_in_win_pos\n");
#endif
    if (inpwin) {
      int tx, ty;
      Window ow;

      XTranslateCoordinates(dpy, root, inpwin, winx, winy, &tx, &ty, &ow);

      current_CS->spot_location.x = tx;
      current_CS->spot_location.y = ty;
    }
    move_in_win(current_CS, winx, winy);
  } else {
    move_IC_in_win(current_CS);
  }
}

void win_pho_disp_half_full();
void win_tsin_disp_half_full();
void win_gtab_disp_half_full();
#if TRAY_ENABLED
void load_tray_icon(), load_tray_icon_double();
#if TRAY_UNITY
void load_tray_appindicator();
#endif
gboolean is_exist_tray();
extern void destroy_tray_icon();
gboolean is_exist_tray_double();
extern void destroy_tray_double();
#if TRAY_UNITY
gboolean is_exist_tray_appindicator();
extern void destroy_tray_appindicator();
#endif
#endif //TRAY_ENABLED

#if TRAY_ENABLED
void destroy_tray()
{
// TODO: optimze it , e.g. struct
  if (is_exist_tray())
    destroy_tray_icon();
  if (is_exist_tray_double())
    destroy_tray_double();
#if TRAY_UNITY
  if (is_exist_tray_appindicator())
    destroy_tray_appindicator();
#endif
}

void destroy_other_tray()
{
  if (!hime_status_tray) {
    destroy_tray();
    return;
  }
  if (is_exist_tray() && hime_tray_display != HIME_TRAY_DISPLAY_SINGLE)
    destroy_tray_icon();
  if (is_exist_tray_double() && hime_tray_display != HIME_TRAY_DISPLAY_DOUBLE)
    destroy_tray_double();
#if TRAY_UNITY
  if (is_exist_tray_appindicator() && hime_tray_display != HIME_TRAY_DISPLAY_APPINDICATOR)
    destroy_tray_appindicator();
#endif
}


void disp_tray_icon()
{
  if (!hime_status_tray)
    return destroy_tray();
// dbg("disp_tray_icon\n");

  if (hime_tray_display == HIME_TRAY_DISPLAY_SINGLE)
    load_tray_icon();
  if (hime_tray_display == HIME_TRAY_DISPLAY_DOUBLE)
    load_tray_icon_double();
#if TRAY_UNITY
  if (hime_tray_display == HIME_TRAY_DISPLAY_APPINDICATOR)
  load_tray_appindicator();
#endif
}
#endif

void disp_im_half_full()
{
//  dbg("disp_im_half_full\n");
#if TRAY_ENABLED
  disp_tray_icon();
#endif

  switch (current_method_type()) {
    case method_type_PHO:
      win_pho_disp_half_full();
      break;
#if USE_TSIN
    case method_type_TSIN:
      win_tsin_disp_half_full();
      break;
#endif
    default:
      win_gtab_disp_half_full();
      break;
  }
}

void flush_tsin_buffer();
void reset_gtab_all();
void set_tsin_pho_mode0(ClientState *cs);

//static u_int orig_caps_state;

void init_state_chinese(ClientState *cs)
{
  cs->im_state = HIME_STATE_CHINESE;
  set_tsin_pho_mode0(cs);
  if (!cs->in_method)
    init_in_method(default_input_method);

  save_CS_current_to_temp();
}

gboolean output_gbuf();

// <Ctrl><Space> is pressed
void toggle_im_enabled()
{
//    dbg("toggle_im_enabled\n");
    check_CS();

    if (current_CS->in_method < 0)
      p_err("err found");


    if (current_CS->im_state != HIME_STATE_DISABLED) {
      if (current_CS->im_state == HIME_STATE_ENG_FULL) {
        current_CS->im_state = HIME_STATE_CHINESE;
        disp_im_half_full();
        save_CS_current_to_temp();
        return;
      }

      if (current_method_type() == method_type_TSIN) {
#if USE_TSIN
        flush_tsin_buffer();
#endif
      }
      else if (current_method_type () == method_type_MODULE)
      {
	  HIME_module_callback_functions *mod_cbs = module_cb();
	  if (mod_cbs) mod_cbs->module_flush_input();
      }
      else {
        output_gbuf();
        reset_gtab_all();
      }

      hide_in_win(current_CS);
#if 0
      hide_win_status();
#endif
      current_CS->im_state = HIME_STATE_DISABLED;

#if TRAY_ENABLED
      disp_tray_icon();
#endif
    } else {
      if (!current_method_type())
        init_gtab(current_CS->in_method);

      init_state_chinese(current_CS);
      reset_current_in_win_xy();
#if 1
      if ((inmd[current_CS->in_method].flag & FLAG_GTAB_SYM_KBM))
      {
        win_kbm_inited = 1;
        show_win_kbm();
      }
      else
        show_in_win(current_CS);

      update_in_win_pos();
#else
      update_in_win_pos();
      show_in_win(current_CS);
#endif

#if TRAY_ENABLED
      disp_tray_icon();
#endif
    }

    save_CS_current_to_temp();
}

void get_win_gtab_geom();
void get_win0_geom();
void get_win_pho_geom();

void update_active_in_win_geom()
{
  HIME_module_callback_functions *mod_cbs;
//  dbg("update_active_in_win_geom\n");
  switch (current_method_type()) {
    case method_type_PHO:
      get_win_pho_geom();
      break;
#if USE_TSIN
    case method_type_TSIN:
      get_win0_geom();
      break;
#endif
    case method_type_MODULE:
      mod_cbs = module_cb();
      if(mod_cbs && mod_cbs->module_get_win_geom)
        mod_cbs->module_get_win_geom();
      break;
    default:
      get_win_gtab_geom();
      break;
  }
}

extern GtkWidget *gwin_pho, *gwin0, *gwin_gtab, *gwin_sym;

gboolean win_is_visible()
{
  if (!current_CS)
    return FALSE;
  switch (current_method_type()) {
    case method_type_PHO:
      return gwin_pho && GTK_WIDGET_VISIBLE(gwin_pho);
#if USE_TSIN
    case method_type_TSIN:
      return gwin0 && GTK_WIDGET_VISIBLE(gwin0);
#endif
    case method_type_MODULE:
      if (!module_cb())
        return FALSE;
      return module_cb()->module_win_visible();
    case method_type_SYMBOL_TABLE:
      return gwin_sym && GTK_WIDGET_VISIBLE(gwin_sym);
    default:
      if (!gwin_gtab)
        return FALSE;
      return gwin_gtab && GTK_WIDGET_VISIBLE(gwin_gtab);
  }

  return FALSE;
}


void disp_gtab_half_full(gboolean hf);
void tsin_toggle_half_full();

// <Shift><Space> is pressed
void toggle_half_full_char()
{
  check_CS();

  if (!hime_shift_space_eng_full) {
    current_CS->b_half_full_char = 0;
    tss.tsin_half_full=0;
    disp_im_half_full();
    return;
  }


//  dbg("toggle_half_full_char\n");

  if (current_method_type() == method_type_TSIN && current_CS->im_state == HIME_STATE_CHINESE) {
    tsin_toggle_half_full();
  }
  else {
    switch (current_CS->im_state) {
      case HIME_STATE_ENG_FULL:
        current_CS->im_state = HIME_STATE_DISABLED;
        hide_in_win(current_CS);
	break;
      case HIME_STATE_DISABLED:
      {
	gint show_win_kbm = hime_show_win_kbm;
	hime_show_win_kbm = FALSE;
	toggle_im_enabled();
	current_CS->im_state = HIME_STATE_ENG_FULL;
	hime_show_win_kbm = show_win_kbm;
	break;
      }
      case HIME_STATE_CHINESE:
        current_CS->b_half_full_char = !current_CS->b_half_full_char;
	break;
    }

//    dbg("current_CS->in_method %d\n", current_CS->in_method);
    disp_im_half_full();
  }

  save_CS_current_to_temp();
//  dbg("half full toggle\n");
}

void init_tab_pp(gboolean init);
void init_tab_pho();

extern char *TableDir;
void set_gtab_input_method_name(char *s);
HIME_module_callback_functions *init_HIME_module_callback_functions(char *sofile);
time_t find_tab_file(char *fname, char *out_file);
gboolean tsin_page_up(), tsin_page_down();
int tsin_sele_by_idx(int);

static void tsin_set_win1_cb()
{
  set_win1_cb((cb_selec_by_idx_t)tsin_sele_by_idx, (cb_page_ud_t)tsin_page_up, (cb_page_ud_t)tsin_page_down);
}

void update_win_kbm_inited()
{
  if (win_kbm_inited)
    update_win_kbm();
}

gboolean init_in_method(int in_no)
{
  gboolean init_im = !(cur_inmd && (cur_inmd->flag & FLAG_GTAB_SYM_KBM));

  if (in_no < 0)
    return FALSE;

  check_CS();


  if (current_CS->in_method != in_no) {
    if (!(inmd[in_no].flag & FLAG_GTAB_SYM_KBM)) {
      if (current_method_type() == method_type_TSIN) {
        flush_tsin_buffer();
      } else
        output_gbuf();

      hide_in_win(current_CS);
    }
  }

  reset_current_in_win_xy();

//  dbg("switch init_in_method %x %d\n", current_CS, in_no);
  set_tsin_pho_mode0(current_CS);
  tsin_set_win1_cb();

  switch (inmd[in_no].method_type) {
    case method_type_PHO:
      current_CS->in_method = in_no;
      init_tab_pho();
      break;
    case method_type_TSIN:
      set_wselkey(pho_selkey);
      current_CS->in_method = in_no;
      init_tab_pp(init_im);
      break;
    case method_type_SYMBOL_TABLE:
      toggle_symbol_table();
      break;
    case method_type_MODULE:
    {
      HIME_module_main_functions gmf;
      init_HIME_module_main_functions(&gmf);
      if (!inmd[in_no].mod_cb_funcs) {
        char ttt[256];
        strcpy(ttt, inmd[in_no].filename);

        dbg("module %s\n", ttt);
        if (!(inmd[in_no].mod_cb_funcs = init_HIME_module_callback_functions(ttt))) {
          dbg("module not found\n");
          return FALSE;
        }
      }

      if (inmd[in_no].mod_cb_funcs->module_init_win(&gmf)) {
        current_CS->in_method = in_no;
        module_cb()->module_show_win();
        set_wselkey(pho_selkey);
      } else {
        return FALSE;
      }
      show_input_method_name_on_gtab();
      break;
    }
    case method_type_EN:
    {
      if (current_CS && current_CS->im_state==HIME_STATE_CHINESE)
        toggle_im_enabled();
      current_CS->in_method = in_no;
      hide_win_kbm();
      return TRUE;
    }
    default:
      init_gtab(in_no);
      if (!inmd[in_no].DefChars)
        return FALSE;
      if (inmd[in_no].flag & FLAG_GTAB_SYM_KBM) {
	if (win_kbm_on)
	{
          init_in_method(default_input_method);
	  hide_win_kbm();
	}
	else
	{
          // Show SYM_KBM
          hide_in_win(current_CS);
          win_kbm_inited = 1;
          show_win_kbm();
          current_CS->in_method = in_no;
	}
      }
      else {
	// in case WIN_SYN and SYM_KBM show at the same time.
        current_CS->in_method = in_no;
        hide_win_sym();
        win_sym_enabled=0;

        show_win_gtab();
	show_input_method_name_on_gtab();
      }

      // set_gtab_input_method_name(inmd[in_no].cname);
      break;
  }

  if (hime_init_full_mode)
  {
    switch (current_method_type())
    {
      case method_type_TSIN:
        if (tss.tsin_half_full==0) toggle_half_full_char();
        break;
      case method_type_MODULE:
      case method_type_SYMBOL_TABLE:
      case method_type_EN:
        break;
      default:
        if (current_CS->b_half_full_char==0) toggle_half_full_char();
        break;
    }
  }

#if TRAY_ENABLED
  disp_tray_icon();
#endif

  if (inmd[current_CS->in_method].selkey) {
    set_wselkey(inmd[current_CS->in_method].selkey);
    gtab_set_win1_cb();
//    dbg("aa selkey %s\n", inmd[current_CS->in_method].selkey);
  }

  update_in_win_pos();
  update_win_kbm_inited();

  if (hime_show_win_kbm)
  {
    if ((current_CS->im_state == HIME_STATE_ENG_FULL) ||
        (current_method_type() == method_type_MODULE) ||
        (current_method_type() == method_type_SYMBOL_TABLE))
      hide_win_kbm();
    else
    {
      show_win_kbm();
      update_win_kbm();
    }
  }

  return TRUE;
}


static void cycle_next_in_method()
{
//  int im_state = current_CS->im_state;

  if (current_method_type() == method_type_SYMBOL_TABLE)
  {
    hide_win_sym();
    win_sym_enabled=0;
  }

  int i;
  for(i=0; i < inmdN; i++) {
    int v = (current_CS->in_method + 1 + i) % inmdN;
    if (win_sym_enabled && inmd[v].method_type == method_type_SYMBOL_TABLE)
      continue;

    if (!inmd[v].in_cycle)
      continue;

    if (!inmd[v].cname || !inmd[v].cname[0])
      continue;

    if (!init_in_method(v))
      continue;

    return;
  }
}

void add_to_tsin_buf_str(char *str);
gboolean gtab_phrase_on();
void insert_gbuf_nokey(char *s);

gboolean full_char_proc(KeySym keysym)
{
  char *s = half_char_to_full_char(keysym);

  if (!s)
    return 0;

  char tt[CH_SZ+1];

  utf8cpy(tt, s);

  if (current_CS->im_state  == HIME_STATE_ENG_FULL) {
    send_text(tt);
    return 1;
  }

  if ((current_method_type() == method_type_TSIN) &&
      (current_CS->im_state == HIME_STATE_CHINESE))
    add_to_tsin_buf_str(tt);
  else
  if (gtab_phrase_on() && ggg.gbufN)
    insert_gbuf_nokey(tt);
  else
    send_text(tt);

  return 1;
}

int feedkey_pho(KeySym xkey, int kbstate);
int feedkey_pp(KeySym xkey, int state);
int feedkey_gtab(KeySym key, int kbstate);
int feed_phrase(KeySym ksym, int state);
void tsin_set_eng_ch(int nmod);
static KeySym last_keysym;

gboolean timeout_raise_window(gpointer data)
{
//  dbg("timeout_raise_window\n");
  timeout_handle = 0;
  show_in_win(current_CS);
  return FALSE;
}

extern Window xwin_pho, xwin0, xwin_gtab;
void win_kbm_disp_caplock();

#if !GTK_CHECK_VERSION(2,16,0)
gboolean get_caps_lock_state()
{
	XkbStateRec states;

	if (XkbGetState(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XkbUseCoreKbd, &states) == Success)
	{
		if (states.locked_mods & LockMask) return TRUE;
	}
	return FALSE;
}
#endif

void disp_win_kbm_capslock()
{
  if (!hime_show_win_kbm)
    return;

  gboolean o_state = old_capslock_on;
  old_capslock_on = gdk_keymap_get_caps_lock_state(gdk_keymap_get_default());

//  dbg("%x %x\n", old_capslock_on, o_state);

  if (o_state != old_capslock_on) {
    win_kbm_disp_caplock();
  }
}


void disp_win_kbm_capslock_init()
{
  old_capslock_on = gdk_keymap_get_caps_lock_state(gdk_keymap_get_default());
//  dbg("disp_win_kbm_capslock_init %d\n",old_capslock_on);

  if (hime_show_win_kbm)
    win_kbm_disp_caplock();
}

void toggle_symbol_table()
{
  if (current_CS->im_state == HIME_STATE_CHINESE) {
    if (!win_is_visible())
      win_sym_enabled=1;
    else
      win_sym_enabled^=1;
  } else
    win_sym_enabled=0;

  create_win_sym();
  if (win_sym_enabled) {
    force_show = TRUE;
    if (current_CS->im_state == HIME_STATE_CHINESE)
      show_in_win(current_CS);
    force_show = FALSE;
  }

  hide_win_kbm();
}

void destroy_phrase_save_menu();
int hime_switch_keys_lookup(int key);

gboolean check_key_press(KeySym key, u_int kev_state, gboolean return_value)
{
  if ((key==XK_Shift_L || key==XK_Shift_R) && key_press_alt) {
    key_press_ctrl = FALSE;
  } else if ((key==XK_Control_L || key==XK_Control_R) && key_press_ctrl) {
    key_press_alt = FALSE;
  } else {
    key_press_alt = FALSE;
    key_press_ctrl = FALSE;
  }

  return return_value;
}

// return TRUE if the key press is processed
gboolean ProcessKeyPress(KeySym keysym, u_int kev_state)
{
#if 0
  dbg("key press %x %x\n", keysym, kev_state);
#endif
  destroy_phrase_save_menu();

  disp_win_kbm_capslock();
  check_CS();

  if (current_CS->client_win)
    focus_win = current_CS->client_win;

  if (callback_str_buffer && strlen(callback_str_buffer)) {
    send_text(callback_str_buffer);
    callback_str_buffer[0]=0;
    return check_key_press(keysym, kev_state, TRUE);
  }

  if (force_preedit) {
    force_preedit=0;
    return check_key_press(keysym, kev_state, FALSE);
  }

  if (keysym == XK_space) {
#if 0
    dbg("state %x\n", kev->state);
    dbg("%x\n", Mod4Mask);
#endif
    if (
      ((kev_state & (ControlMask|Mod1Mask|ShiftMask))==ControlMask && hime_im_toggle_keys==Control_Space) ||
      ((kev_state & Mod1Mask) && hime_im_toggle_keys==Alt_Space) ||
      ((kev_state & ShiftMask) && hime_im_toggle_keys==Shift_Space) ||
      ((kev_state & Mod4Mask) && hime_im_toggle_keys==Windows_Space)
    ) {
      if (current_method_type() == method_type_TSIN) {
        tsin_set_eng_ch(1);
      }

      toggle_im_enabled();
      return check_key_press(keysym, kev_state, TRUE);
    }
  }

  if (keysym == XK_space && (kev_state & ShiftMask)) {
    if (last_keysym != XK_Shift_L && last_keysym != XK_Shift_R)
      return check_key_press(keysym, kev_state, FALSE);

    toggle_half_full_char();

    return check_key_press(keysym, kev_state, TRUE);
  }


  if ((kev_state & (Mod1Mask|ShiftMask)) == (Mod1Mask|ShiftMask)) {
    if (current_CS->im_state != HIME_STATE_DISABLED || hime_eng_phrase_enabled)
      return check_key_press(keysym, kev_state, feed_phrase(keysym, kev_state));
    else
      return check_key_press(keysym, kev_state, FALSE);
  }

//  dbg("state %x\n", kev_state);
  if ((kev_state & ControlMask) && (kev_state&(Mod1Mask|Mod5Mask))) {
    if (keysym == 'g' || keysym == 'r') {
      send_output_buffer_bak();
      return check_key_press(keysym, kev_state, TRUE);
    }

    if (!hime_enable_ctrl_alt_switch)
      return check_key_press(keysym, kev_state, FALSE);

    int kidx = hime_switch_keys_lookup(keysym);
    if (kidx < 0)
      return check_key_press(keysym, kev_state, FALSE);

    if (inmd[kidx].method_type == method_type_SYMBOL_TABLE) {
      toggle_symbol_table();
      return check_key_press(keysym, kev_state, TRUE);
    }

    if (!inmd[kidx].cname)
      return check_key_press(keysym, kev_state, FALSE);

    current_CS->im_state = HIME_STATE_CHINESE;
    init_in_method(kidx);

    return check_key_press(keysym, kev_state, TRUE);
  }

  last_keysym = keysym;

  if (current_CS->im_state == HIME_STATE_ENG_FULL && !(kev_state & (ControlMask|Mod1Mask))) {
    return check_key_press(keysym, kev_state, full_char_proc(keysym));
  }

  if (current_CS->im_state == HIME_STATE_DISABLED || current_CS->im_state == HIME_STATE_ENG_FULL) {
    return check_key_press(keysym, kev_state, FALSE);
  }

  if (!current_CS->b_hime_protocol) {
    if (((keysym == XK_Control_L || keysym == XK_Control_R)
                     && (kev_state & ShiftMask)) ||
        ((keysym == XK_Shift_L || keysym == XK_Shift_R)
                     && (kev_state & ControlMask))) {
       cycle_next_in_method();
       return check_key_press(keysym, kev_state, TRUE);
    }
  }

  if (current_CS->b_raise_window && keysym>=' ' && keysym < 127) {
    if (timeout_handle)
      g_source_remove(timeout_handle);
    timeout_handle = g_timeout_add(200, timeout_raise_window, NULL);
  }

  if (kev_state & ControlMask) {
    if (feed_phrase(keysym, kev_state))
      return check_key_press(keysym, kev_state, TRUE);
  }

  switch(current_method_type()) {
    case method_type_PHO:
      return check_key_press(keysym, kev_state, feedkey_pho(keysym, kev_state));
#if USE_TSIN
    case method_type_TSIN:
      return check_key_press(keysym, kev_state, feedkey_pp(keysym, kev_state));
#endif
    case method_type_MODULE:
    {
      if (!module_cb())
        return check_key_press(keysym, kev_state, FALSE);
      gboolean response = module_cb()->module_feedkey(keysym, kev_state);
      if (response)
	hide_win_gtab();
      else
	if (current_CS->b_half_full_char)
	  return check_key_press(keysym, kev_state, full_char_proc(keysym));
      return check_key_press(keysym, kev_state, response);
    }
    default:
      return check_key_press(keysym, kev_state, feedkey_gtab(keysym, kev_state));
  }

  return check_key_press(keysym, kev_state, FALSE);
}

int feedkey_pp_release(KeySym xkey, int kbstate);
int feedkey_gtab_release(KeySym xkey, int kbstate);

// return TRUE if the key press is processed
gboolean ProcessKeyRelease(KeySym keysym, u_int kev_state)
{
  disp_win_kbm_capslock();

  check_CS();
#if 0
  dbg_time("key release %x %x\n", keysym, kev_state);
#endif

 if (current_CS->im_state == HIME_STATE_DISABLED || current_CS->im_state == HIME_STATE_ENG_FULL) return FALSE;

#if 1
  if (current_CS->b_hime_protocol && (last_keysym == XK_Shift_L ||
  last_keysym == XK_Shift_R || last_keysym == XK_Control_L || last_keysym == XK_Control_R)) {
    if (((keysym == XK_Control_L || keysym == XK_Control_R)
          && (kev_state & ShiftMask)) ||
        ((keysym == XK_Shift_L || keysym == XK_Shift_R)
          && (kev_state & ControlMask))) {
       cycle_next_in_method();
       return TRUE;
    }
  }
#endif

  switch(current_method_type()) {
    case method_type_TSIN:
      return feedkey_pp_release(keysym, kev_state);
    case method_type_MODULE:
      if (!module_cb())
        return FALSE;
      return module_cb()->module_feedkey_release(keysym, kev_state);
    default:
      return feedkey_gtab_release(keysym, kev_state);
  }

  return FALSE;
}


#if USE_XIM
int xim_ForwardEventHandler(IMForwardEventStruct *call_data)
{
    current_forward_eve = call_data;

    if (call_data->event.type != KeyPress && call_data->event.type != KeyRelease) {
#if DEBUG || 1
        dbg("bogus event type, ignored\n");
#endif
        return True;
    }

    char strbuf[STRBUFLEN];
    KeySym keysym;

    bzero(strbuf, STRBUFLEN);
    XKeyEvent *kev = (XKeyEvent*)&current_forward_eve->event;
    XLookupString(kev, strbuf, STRBUFLEN, &keysym, NULL);

    int ret;
    if (call_data->event.type == KeyPress)
      ret = ProcessKeyPress(keysym, kev->state);
    else
      ret = ProcessKeyRelease(keysym, kev->state);

    if (!ret)
      bounce_back_key();

    export_text_xim();

    return False;
}
#endif

int skip_window(Window win)
{
  XWindowAttributes att;
  XGetWindowAttributes(dpy, win, &att);
#if 0
  dbg("hhh %d %d class:%d all:%x %x\n", att.width, att.height, att.class,
    att.all_event_masks, att.your_event_mask);
#endif
  if (att.override_redirect)
    return 1;

  return 0;
}

void hime_reset();

int hime_FocusIn(ClientState *cs)
{
//  dbg("hime_FocusIn\n");
  Window win = cs->client_win;

#if 0
  if (skip_window(win))
    return FALSE;
#endif

  reset_current_in_win_xy();

  if (cs) {
    Window win = cs->client_win;

    if (focus_win != win) {
#if 1
      hime_reset();
#endif
      hide_in_win(current_CS);
      focus_win = win;
    }
  }

  current_CS = cs;
  save_CS_temp_to_current();

//  dbg("current_CS %x %d %d\n", cs, cs->im_state, current_CS->im_state);

  if (win == focus_win) {
    if (cs->im_state != HIME_STATE_DISABLED) {
      show_in_win(cs);
      move_IC_in_win(cs);
    } else
      hide_in_win(cs);
  }

  if (inmd[cs->in_method].selkey)
    set_wselkey(inmd[cs->in_method].selkey);
  else {
    set_wselkey(pho_selkey);
    gtab_set_win1_cb();
    tsin_set_win1_cb();
  }

  update_win_kbm();
#if TRAY_ENABLED
  disp_tray_icon();
#endif
#if 0
  dbg_time("hime_FocusIn %x %x\n",cs, current_CS);
#endif
  return True;
}


#if USE_XIM
IC *FindIC(CARD16 icid);
void load_IC(IC *rec);
CARD16 connect_id;

int xim_hime_FocusIn(IMChangeFocusStruct *call_data)
{
    IC *ic = FindIC(call_data->icid);
    ClientState *cs = &ic->cs;
    connect_id = call_data->connect_id;

    if (ic) {
      hime_FocusIn(cs);

      load_IC(ic);
    }

#if DEBUG
    dbg("xim_hime_FocusIn %d\n", call_data->icid);
#endif
    return True;
}
#endif



static gint64 last_focus_out_time;

int hime_FocusOut(ClientState *cs)
{
  gint64 t = current_time();
//  dbg("hime_FocusOut\n");

  if (cs != current_CS)
     return FALSE;

#if 0
//  dbg("hime_FocusOut\n");
  if (skip_window(cs->client_win))
    return FALSE;
#endif
  if (t - last_focus_out_time < 100000) {
    last_focus_out_time = t;
    return FALSE;
  }

  last_focus_out_time = t;

  if (cs == current_CS) {
    hide_in_win(cs);
  }

  reset_current_in_win_xy();

  if (cs == current_CS)
    temp_CS = *current_CS;

#if 0
  dbg("focus out\n");
#endif

  return True;
}

int tsin_get_preedit(char *str, HIME_PREEDIT_ATTR attr[], int *cursor, int *sub_comp_len);
int gtab_get_preedit(char *str, HIME_PREEDIT_ATTR attr[], int *pcursor, int *sub_comp_len);
int int_get_preedit(char *str, HIME_PREEDIT_ATTR attr[], int *cursor, int *sub_comp_len);
int pho_get_preedit(char *str, HIME_PREEDIT_ATTR attr[], int *cursor, int *sub_comp_len);

int hime_get_preedit(ClientState *cs, char *str, HIME_PREEDIT_ATTR attr[], int *cursor, int *comp_flag)
{
//  dbg("hime_get_preedit %x\n", current_CS);
  if (!current_CS) {
//empty:
//    dbg("empty\n");
    str[0]=0;
    *cursor=0;
    return 0;
  }

  str[0]=0;
  *comp_flag=0;

//  cs->use_preedit = TRUE;

  switch(current_method_type()) {
    case method_type_PHO:
      return pho_get_preedit(str, attr, cursor, comp_flag);
#if USE_TSIN
    case method_type_TSIN:
      return tsin_get_preedit(str, attr, cursor, comp_flag);
#endif
    case method_type_MODULE:
      if (inmd[current_CS->in_method].mod_cb_funcs)
        return module_cb()->module_get_preedit(str, attr, cursor, comp_flag);
    default:
      return gtab_get_preedit(str, attr, cursor, comp_flag);
//      dbg("metho %d\n", current_CS->in_method);
  }

  return 0;
}

void pho_reset();
int tsin_reset();
void gtab_reset();

void hime_reset()
{
#if 1
  if (!current_CS)
    return;
//  dbg("hime_reset\n");

  switch(current_method_type()) {
    case method_type_PHO:
      pho_reset();
      return;
#if USE_TSIN
    case method_type_TSIN:
      tsin_reset();
      return;
#endif
    case method_type_MODULE:
      if (inmd[current_CS->in_method].mod_cb_funcs)
        module_cb()->module_reset();
      return;
    default:
      gtab_reset();
//      dbg("metho %d\n", current_CS->in_method);
  }
#endif
}


#if USE_XIM
int xim_hime_FocusOut(IMChangeFocusStruct *call_data)
{
    IC *ic = FindIC(call_data->icid);
    ClientState *cs = &ic->cs;

    hime_FocusOut(cs);

    return True;
}
#endif


gboolean hime_edit_display_ap_only()
{
  if (!current_CS)
    return FALSE;
//  dbg("hime_edit_display_ap_only %d\n", current_CS->use_preedit)
  return current_CS->use_preedit && hime_edit_display==HIME_EDIT_DISPLAY_ON_THE_SPOT;
}


gboolean hime_display_on_the_spot_key()
{
  return hime_edit_display_ap_only() && hime_on_the_spot_key;
}

void flush_edit_buffer()
{
//  dbg("flush_edit_buffer\n");
  if (!current_CS)
    return;
//  dbg("hime_reset\n");
  switch(current_method_type()) {
#if USE_TSIN
    case method_type_TSIN:
      flush_tsin_buffer();
      break;
#endif
    case method_type_MODULE:
      if (inmd[current_CS->in_method].mod_cb_funcs)
      module_cb()->module_flush_input();
      break;
    default:
      output_gbuf();
//      dbg("metho %d\n", current_CS->in_method);
  }
#if 0
  dbg("output_bufferN:%d\n", output_bufferN);
  if (output_bufferN) {
    output_buffer_call_back();
  }
#endif
}

void change_module_font_size()
{
  int i;
  for (i=0; i < inmdN; i++) {
    INMD *pinmd = &inmd[i];
    if (pinmd->method_type != method_type_MODULE || pinmd->disabled)
      continue;
    HIME_module_callback_functions *f = pinmd->mod_cb_funcs;
    if (!f)
      continue;
    if (!f->module_change_font_size)
      continue;
    f->module_change_font_size();
  }
}
