/* # skkinput (Simple Kana-Kanji Input)
 * main.c 
 * This file is part of skkinput.
 * Copyright (C) 1997
 * Takashi SAKAMOTO (sakamoto@yajima.kuis.kyoto-u.ac.jp)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with skkinput; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <signal.h>
#include <locale.h>
#include <sys/types.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <sys/socket.h>

#define main_c
#include "commondef.h"
#include "buffers.h"
#if defined(SUPPORT_KINPUT)
#include "Kinput.h"
#endif
#if defined(SUPPORT_XIMP)
#include "Ximp.h"
#endif
#if defined(SUPPORT_XIM)
#include "XIMServer.h"
#endif
#include "SeparateWin.h"
#include "OverWin.h"
#include "skkkey.h"
#include "config.h"
#include "version.h"
#include "MyError.h"
#include "HistMgr.h"
#include "FontMgr.h"

/*
 * main だけで用いる変数の型宣言。
 */
struct skkinputOptionDef {
  char *chara ;
  int  function_no ;
  int  need_argument ;
} ;

/*
 * メインだけで用いる定数定義。
 */
enum {
  OPTION_HOST = 0, OPTION_PORT, OPTION_USERDIC, OPTION_BAKDIC,
  OPTION_SKKDIC, OPTION_SKKREC, OPTION_HELP, OPTION_VERSION,
  OPTION_CONFIG, OPTION_XIM_OFF, OPTION_XIM_ON, OPTION_XIMP_OFF,
  OPTION_XIMP_ON,OPTION_AUTOSAVE, OPTION_KINPUT_ON, OPTION_KINPUT_OFF,
  OPTION_IPv4, OPTION_IPv6,
} ;


/*
 * プロトタイプ宣言。
 */
static int skkinput_checkOptions( int *argc, char *argv[], int flag ) ;
static int skkinput_usage( void ) ;
static void skkinput_Quit( void ) ;
static void skkinput_redraw_allskkinputs( void ) ;
static void skkinput_CheckEvent( Display *display, XEvent *xevent ) ;
static int skkinputIOErrorHandler( Display *display ) ;

/*
 * 子供ウィジェットから呼ばれるコールバック関数のプロトタイプ宣言。
 */
static void setupInputWindow
( Widget gw, caddr_t client, caddr_t caller ) ;
static void closeProtocolServer
( Widget gw, caddr_t client, caddr_t caller ) ;
static void destroy_protocolServers( void ) ;

/*
 * 一定時間の経過毎に呼び出される辞書のセーブを行うべきかどうかを判定する関数。
 */
static void skkinput_CheckAutoSaveJisyo( XtPointer closure, XtIntervalId *id ) ;
static Boolean skkinput_AutoSaveJisyo( XtPointer closure ) ;

/*
 * 他所様のソースにある関数で、main.c から参照されているもののプロトタ
 * イプ宣言。
 */
/* skkconfig.c */
extern void initSkkinputDousaketteiVariables( void ) ; 
extern int  skkinput_readConfigFile( char *config_file ) ;
extern int  skkinput_setSkkInputValues( Widget gw ) ;
/* skksvect.c */
extern void initVectorHashTable( void ) ;
/* skkldrec.c */
extern void initHenkanKakuteiHashTable( void ) ;
extern void clearHenkanKakuteiHash( void ) ;
/* skksoc.c */
extern int  skkinput_StartCommunication( char *server_name, char *service_name, int pf ) ;
extern int  skkinput_CloseCommunication( void ) ;
/* skkldic.c */
extern int  set_localjisyo
( unsigned char *jisyo_path,  unsigned char *jisyobak_path,
  unsigned char *record_path, unsigned char *master_localjisyo ) ;
extern int  close_localjisyo( void ) ;
extern void skkinput_updateLocalJisyo( void ) ;
extern void skkinput_autosaveLocalJisyo( void ) ;
extern void skkinput_readAutoSavedLocalJisyo( void ) ;
/* mydispatch.c */
extern int mydispatch_event( Display *disp, XEvent *xevent ) ;

extern void initialize_myerrorhandler( void ) ;
extern int remove_allmyeventhandler( Display *disp, Window src_win ) ;

/*
 * このソース何でしか利用されないグローバル変数の宣言。
 */
/* X Tool Kit が見る アプリケーションコンテキスト。*/
static XtAppContext app_context ;

static Widget toplevel ;
#if defined(SUPPORT_KINPUT)
static Widget kinput_protocol ;
static Boolean support_kinput_protocol = True ;
#endif
#if defined(SUPPORT_XIM)
static Widget xim_protocol ;
static Boolean support_xim_protocol = True ;
#endif
#if defined(SUPPORT_XIMP)
static Widget ximp_protocol ;
static Boolean support_ximp_protocol = True ;
#endif
static jmp_buf noprotocol_env ;

/* X Tool Kit が勝手に必要としてくれる Option の設定。*/
static XrmOptionDescRec options[] = {
  { "-fontset",	"*fontSet",		XrmoptionSepArg,	NULL },
  { "-mfontset","*minibufferFontSet",	XrmoptionSepArg,	NULL },
  { "-rv",	"*reverseVideo",	XrmoptionNoArg,		"TRUE" },
  { "+rv",	"*reverseVideo",	XrmoptionNoArg,		"FALSE" },
  { "-fg",	"*foreground",		XrmoptionSepArg,	NULL },
  { "-bg",	"*background",		XrmoptionSepArg,	NULL },
  { "-bd",	"*borderColor",		XrmoptionSepArg,	NULL },
  { "-width",	"*separate.width", 	XrmoptionSepArg,	NULL },
  { "-height",	"*separate.height",	XrmoptionSepArg,	NULL },
  { "-mwidth",	"*minibuffer_width", 	XrmoptionSepArg,	NULL },
  { "-mfc",	"*changeMinibufferFont", XrmoptionNoArg,	"TRUE" },
  { "+mfc",	"*changeMinibufferFont", XrmoptionNoArg,	"FALSE" },
  { "-nc",      "*controlHaTugiDeYukou", XrmoptionNoArg,	"TRUE" },
  { "+nc",      "*controlHaTugiDeYukou", XrmoptionNoArg,	"FALSE" },
  { "-ns",      "*shiftHaTugiDeYukou",	XrmoptionNoArg,		"TRUE" },
  { "+ns",      "*shiftHaTugiDeYukou",	XrmoptionNoArg,		"FALSE" },
  { "-sc",	"*southCursor",		XrmoptionNoArg,		"TRUE" },
  { "+sc",	"*southCursor",		XrmoptionNoArg,		"FALSE" },
} ;

typedef struct {
  String fontSet ;
  String mfontSet ;
} toplevelResourceRec, *toplevelResourcePtr ;

static XtResource toplevel_resources[] = {
  { XtNfontset, XtCFontset, XtRString, sizeof( String ),
    XtOffset( toplevelResourcePtr, fontSet ), XtRImmediate, 
    DEFAULT_FONTSET },
  { XtNmfontset, XtCFontset, XtRString, sizeof( String ),
    XtOffset( toplevelResourcePtr, mfontSet ), XtRImmediate, 
    DEFAULT_FONTSET },
} ;

static toplevelResourceRec toplevel_value ;

/* 変換サーバが持っているプロトコルの数。*/
static int number_of_protocols ;

/* "~/.skkinput" がデフォルトである設定ファイル。*/
static char *skkinput_config_name ;

/*
 * skkinput のほぼ全域で利用されるようなグローバル変数の宣言。
 */
/* その他、skkinput が必要とする…skkInputWidget 毎には必要としない情報。*/
char *skkserv_host ;
char *skkserv_service ;
int skkserv_pf = PF_UNSPEC ;
char *skkinput_local_jisyo_name ;
char *skkinput_backup_jisyo_name ;
char *skk_local_jisyo_name ;
char *skkinput_record_name ;
int  skk_egg_like_newline ;
int  skkinput_chatadaptermode ;
int  skkinput_search_skk_jisyo ;
int  skkinput_keep_record ;
int  skkinput_date_ad ;
int  skkinput_number_style ;
int  skkinput_delete_implies_kakutei ;
int  skkinput_use_numeric_conversion ;
int  skkinput_dabbrev_like_completion ;
int  skkinput_tab_width ;
int  skkinput_rjj_like_input ;

Boolean  skkinput_jisyo_dirty ;
Boolean  skkinput_prev_jisyo_dirty ;

unsigned long skkinput_j_count_kakutei ;
unsigned long skkinput_j_count_touroku ;

/* ローマ字仮名変換の規則。*/
struct skk_rom_kana_rule *skkinput_rom_kana_rule_list ;

/*
 * グローバル変数 --- オートセーブの設定。
 */
/* 自動セーブするか否かの判定。*/
Boolean			skkinput_autosave_jisyo_dirty ;
Boolean			skkinput_autosave_key_dirty ;
/* 自動セーブチェックの期間。単位はミリ秒。*/
unsigned long		skkinput_autosave_interval ;
/* 自動セーブ中であるか否か。*/
static Boolean		skkinput_now_autosaving ;
static XtWorkProcId	skkinput_autosave_work_id ;

/*
 * メイン関数。
 *-------
 * 引数のチェック等でもう少しにぎやかになるつもり。
 */
int main( int argc, char *argv[] )
{
  Display *display ;
  /* XtTranslations trans ; */
  Arg arg[ 20 ] ;
  Cardinal i ;
  XEvent xevent ;
  volatile XtIntervalId checkautosave_id = 0;
  int (*defaultIOErrorHandler)( Display *disp ) ;

#if defined(DEBUG)
  setbuffer( stdout, NULL, 0 ) ;
#endif
  setlocale( LC_ALL, "C" ) ;
  /* skkinput の動作記述変数を初期化しておく。*/
  initSkkinputDousaketteiVariables() ;
  /* 辞書はまっさらである。*/
  skkinput_jisyo_dirty = skkinput_prev_jisyo_dirty = False ;
  /* 変換の際に用いるハッシュテーブルを初期化しておく。*/
  initVectorHashTable() ;
  /* 確定の際に用いるハッシュテーブルを初期化しておく。*/
  initHenkanKakuteiHashTable() ;

  /* プロトコルに対応するウィジェットを指す変数を初期化しておく。*/
#if defined(SUPPORT_KINPUT)
  kinput_protocol = NULL ;
#endif
#if defined(SUPPORT_XIMP)
  ximp_protocol = NULL ;
#endif
#if defined(SUPPORT_XIM)
  xim_protocol = NULL ;
#endif

  /* Toolkit に先にオプションチェックをさせるとオプションの先頭一致だ
   * けで同じオプションだとか思い込まれてしまい非常に迷惑しているので、
   * 先に自前のオプションチェックをする。*/
  skkinput_config_name = DEFAULT_CONFIGFILE ;
  if( argc > 1 ){
    if( !skkinput_checkOptions( &argc, argv, False ) ){
      return 1 ;
    }
  }
  /* config file を読んでもらう。*/
  skkinput_readConfigFile( skkinput_config_name ) ;

  /* オプションの設定が最優先になるように、この設定を一番最後に読む。*/
  /* 先の prescan によって、ここでの引数チェックでエラーの返ること *
   * はない。*/
  skkinput_checkOptions( &argc, argv, True ) ;

  /* まず、Tool Kit の初期化する。*/
  toplevel = XtAppInitialize
    ( &app_context, "Skkinput", options, XtNumber( options ), &argc, argv,
      NULL, NULL, ( Cardinal ) 0);
  if( argc > 1 ){
    skkinput_usage() ;
    XtDestroyApplicationContext
      ( XtWidgetToApplicationContext( toplevel ) ) ;
    return 1 ;
  }
  initialize_myerrorhandler() ;

  /* application resource を得ておく。*/
  XtGetApplicationResources
    ( toplevel, &toplevel_value,
      toplevel_resources, XtNumber( toplevel_resources ),
      NULL, 0 ) ;

  /* キー入力がとれるようにする。*/
  i = 0 ;
  XtSetArg( arg[ i ], XtNinput,  True ) ;
  i ++ ;  
  XtSetArg( arg[ i ], XtNwidth,  10 ) ;
  i ++ ;
  XtSetArg( arg[ i ], XtNheight, 10 ) ;
  i ++ ;
  XtSetArg( arg[ i ], XtNmappedWhenManaged, False ) ;
  i ++ ;
  XtSetValues( toplevel, arg, i ) ;

  number_of_protocols = 0 ;

#if defined(SUPPORT_KINPUT)
  if( support_kinput_protocol ){
    /* Kinput Protocol を用いるための初期化を行う。*/
    kinput_protocol = XtVaCreateManagedWidget
      ( "kinput2", kinputWidgetClass, toplevel, NULL ) ;
    /* 必要なコールバックを加える。*/
    XtAddCallback
      ( kinput_protocol, XtNsetupInputWindowNotify,
	( XtCallbackProc )setupInputWindow, ( XtPointer )NULL ) ;
    XtAddCallback
      ( kinput_protocol, XtNserverCloseNotify,
	( XtCallbackProc )closeProtocolServer,
	( XtPointer )( &kinput_protocol ) ) ;
    number_of_protocols ++ ;
  }
#endif

#if defined(SUPPORT_XIMP)
  if( support_ximp_protocol ){
    /* Ximp Protocol を用いるための初期化を行う。*/
    ximp_protocol = XtVaCreateManagedWidget
      ( "ximp", ximpWidgetClass, toplevel, XtNwidth, 1, XtNheight, 1, NULL ) ;
    /* 必要なコールバックを加える。*/
    XtAddCallback
      ( ximp_protocol, XtNsetupInputWindowNotify,
	( XtCallbackProc )setupInputWindow, ( XtPointer )NULL ) ;
    XtAddCallback
      ( ximp_protocol, XtNserverCloseNotify,
	( XtCallbackProc )closeProtocolServer,
	( XtPointer )( &ximp_protocol ) ) ;
    number_of_protocols ++ ;
  }
#endif

#if defined(SUPPORT_XIM)
  /* Xim Protocol を用いるための初期化を行う。*/
  if( support_xim_protocol ){
	  xim_protocol = XtVaCreateManagedWidget
		  ( "xim", ximServerWidgetClass, toplevel, XtNwidth, 1, XtNheight, 1, NULL ) ;
	  /* 必要なコールバックを加える。*/
	  XtAddCallback
		  ( xim_protocol, XtNsetupInputWindowNotify,
			( XtCallbackProc )setupInputWindow, ( XtPointer )NULL ) ;
	  XtAddCallback
		  ( xim_protocol, XtNserverCloseNotify,
			( XtCallbackProc )closeProtocolServer,
			( XtPointer )( &xim_protocol ) ) ;
	  number_of_protocols ++ ;
  }
#endif
  if( number_of_protocols > 0 ){
#ifndef MAIKAI_SKKSERV_TO_CONNECT_SURU
    /* socket を作成しておく。*/
    skkinput_StartCommunication( skkserv_host, skkserv_service, skkserv_pf ) ;
#endif
    /* 局所辞書の情報を入れておく。*/
    set_localjisyo
      ( skkinput_local_jisyo_name, skkinput_backup_jisyo_name,
	skkinput_record_name, skk_local_jisyo_name ) ;
    /* 以前のオートセーブファイルが存在した時にそれを読み直す。*/
    skkinput_readAutoSavedLocalJisyo() ;

    /* Widget を作ります。宣言しただけでは実体はないので、ここで実体を 
     * 作るのです。*/
    XtRealizeWidget( toplevel ) ;

    display = XtDisplay( toplevel ) ;
    defaultIOErrorHandler = XSetIOErrorHandler( skkinputIOErrorHandler ) ;
    /* リソースデータベースにあるフォントセットを確保する。Separate と
     * かOverTheSpot とかを直に指されてしまうととてもまともに動作しそ
     * うにないけどね。まあ、そういうことをしたら、それはそれというこ
     * とで。*/ 
    fontMgr_initDefaultFontSet
      ( display, toplevel_value.fontSet, toplevel_value.mfontSet ) ;
    /* オートセーブをすべきか判定する関数を登録する。*/
    skkinput_autosave_key_dirty = False ;
    skkinput_now_autosaving     = False ;
    /* オートセーブのインターバルが正ならばオートセーブを実行する。*/
    if( skkinput_autosave_interval != 0 ){
      checkautosave_id = XtAppAddTimeOut
	( app_context, skkinput_autosave_interval,
	  skkinput_CheckAutoSaveJisyo, NULL ) ;
    }
    /* メインループ。*/
    if( !setjmp( noprotocol_env ) ){
      signal( SIGINT,  ( void (*)() )skkinput_Quit ) ;
      signal( SIGQUIT, ( void (*)() )skkinput_Quit ) ;
      signal( SIGTERM, ( void (*)() )skkinput_Quit ) ;
      signal( SIGPIPE, ( void (*)() )skkinput_Quit ) ;
      for( ; ; ){
	XtSafeAppNextEvent( app_context, &xevent ) ;
	skkinput_CheckEvent( display, &xevent ) ;
	/*XSafeFlush( display ) ;*/
      }
    }
    /* もしオートセーブのチェックが登録されていれば抹消する。*/
    if( skkinput_autosave_interval > 0 )
      XtRemoveTimeOut( checkautosave_id ) ;
    /* もし autosave が登録されていれば抹消する。*/
    if( skkinput_now_autosaving )
      XtRemoveWorkProc( skkinput_autosave_work_id ) ;

    ( void )XSetIOErrorHandler( defaultIOErrorHandler ) ;

    /* シグナルの処理をデフォルトに戻す。*/
    signal( SIGINT,  SIG_DFL ) ;
    signal( SIGQUIT, SIG_DFL ) ;
    signal( SIGTERM, SIG_DFL ) ;
    signal( SIGPIPE, SIG_DFL ) ;

#ifndef MAIKAI_SKKSERV_TO_CONNECT_SURU
    /* SKKSERV とのコネクションを切ります。*/
    skkinput_CloseCommunication() ;
#endif
    destroy_protocolServers() ;
    fontMgr_closeDefaultFontSet( display ) ;
  } else {
    fprintf( stderr, "(skkinput) No protocol is supported.\n" ) ;
  }
  /* 利用していた資源を全て解放する。*/
  XtSafeDestroyApplicationContext
    ( XtWidgetToApplicationContext( toplevel ) ) ;
  /* 辞書が汚なかったら… */
  if( skkinput_jisyo_dirty ){
    fprintf( stderr, "(skkinput) Saving Jisyo..." ) ;
    /* 現在までに確定変換していた候補をセーブする。*/
    skkinput_updateLocalJisyo() ;
    fprintf( stderr, "done\n" ) ;
  }
  /* malloc していたメモリの解放。*/
  close_localjisyo() ;
  /* ハッシュをクリアする。*/
  clearHenkanKakuteiHash() ;
  return 0 ;
}

/*
 * 辞書の状態の変化を全ての日本語入力窓に対してブロードキャストする関
 * 数。
 *------
 * main -> widget への通信には、XtSetValues を使うのが正しいと思うぞ、
 * 私は。
 */
static void skkinput_redraw_allskkinputs( void )
{
  /* 辞書の変化を伝える。*/
#if defined(SUPPORT_KINPUT)
  if( kinput_protocol != NULL ){
    XtVaSetValues
      ( kinput_protocol, XtNjisyoDirty, skkinput_jisyo_dirty, NULL ) ;
  }
#endif
#if defined(SUPPORT_XIM)
  if( xim_protocol != NULL ){
    XtVaSetValues
      ( xim_protocol, XtNjisyoDirty, skkinput_jisyo_dirty, NULL ) ;
  }
#endif
#if defined(SUPPORT_XIMP)
  if( ximp_protocol != NULL ){
    XtVaSetValues
      ( ximp_protocol, XtNjisyoDirty, skkinput_jisyo_dirty, NULL ) ;
  }
#endif
  return ;
}

/*
 * 入って来たイベントをチェックする関数。
 */
static void skkinput_CheckEvent( Display *disp, XEvent *xevent )
{
#ifdef DEBUG
  fprintf( stderr, "[XEvent] Type(%d), Serial(%ld), Window(%ld)\n", 
	   xevent->type, xevent->xany.serial, xevent->xany.window ) ;
#endif

  /* たたり「まさか…自分のかかわっているウィンドウのどれかが破棄され *
   *         ようとしている？！」                                     *
   * 楊雲  「そのようですね…。」                                     *
   * フィリー「そのようですねってね！ あんた何とかならないの！」      */
  if( xevent->type == DestroyNotify ){
    Window dwin = xevent->xany.window ;

    /* 各種プロトコルのサーバに何らかのウィンドウが破棄されたというこ
     * とを伝達する。*/ 
#if defined(SUPPORT_KINPUT)
    if( kinput_protocol != NULL ){
      XtVaSetValues
	( kinput_protocol, XtNdestroyWindowEvent, xevent, NULL ) ;
    }
#endif
#if defined(SUPPORT_XIMP)
    if( ximp_protocol != NULL ){
      XtVaSetValues
	( ximp_protocol, XtNdestroyWindowEvent, xevent, NULL ) ;
    }
#endif
#if defined(SUPPORT_XIM)
    if( xim_protocol != NULL ){
      XtVaSetValues
	( xim_protocol, XtNdestroyWindowEvent, xevent, NULL ) ;
    }
#endif
    /* ヒストリの破棄。*/
    history_destroy( dwin ) ;
    /*
     * 破棄された窓に対し続けてウィンドウの監視を行うことは無意味。
     */
    remove_allmyeventhandler( disp, dwin ) ;
    XSafeSelectInput( disp, dwin, NoEventMask ) ;
    return ;
  }
  mydispatch_event( disp, xevent ) ;

  /* 辞書に変更があった場合には全ての窓に対して書き直し要求が必 *
   * 要である。それが実行できるのは、ここメインだけ。*/
  if( skkinput_jisyo_dirty != skkinput_prev_jisyo_dirty ){
    skkinput_redraw_allskkinputs() ;
    skkinput_prev_jisyo_dirty = skkinput_jisyo_dirty ;
  }
  return ;
}

static void destroy_protocolServers( void )
{
  /* もしまだ実体があったら破棄する。*/
#if defined(SUPPORT_KINPUT)
  if( kinput_protocol != NULL )
    XtDestroyWidget( kinput_protocol ) ;
  kinput_protocol = NULL;
#endif
#if defined(SUPPORT_XIMP)
  if( ximp_protocol != NULL )
    XtDestroyWidget( ximp_protocol ) ;
  ximp_protocol = NULL;
#endif
#if defined(SUPPORT_XIM)
  if( xim_protocol != NULL )
    XtDestroyWidget( xim_protocol ) ;
  xim_protocol = NULL;
#endif
  return ;
}

/*
 * skkinput 終了時の処理を行う関数。
 *-----
 * といっても、main 関数の終了処理部分へ飛んでいるだけですけど。
 * 終了する時には application context を解放しないと駄目なので、作った
 * 関数です。これで Selection Owner も解放できますので、前のがひっかか
 * ることは少ないと思うのですけど。
 */
static void skkinput_Quit( void )
{
  /* signal を解放する。*/
  signal( SIGINT,  SIG_DFL ) ;
  signal( SIGQUIT, SIG_DFL ) ;
  signal( SIGTERM, SIG_DFL ) ;
  signal( SIGPIPE, SIG_DFL ) ;

  destroy_protocolServers() ;
  return ;
}

/*
 * 使用方法を示す関数。
 */
static int skkinput_usage( void )
{
  char **ptr ;
  char *syntaxtable[] = {
    "-\\?           or -help",		  "shows this help",
    "-v            or -version",	  "shows version",
    "-h <hostname> or -host <hostname>",  "specifies skkserv host",
    "-p <port_num> or -port <port_num>",  "specifies port number",
    "-4",				  "specifies to use IPv4",
    "-6",				  "specifies to use IPv6",
    "-uj <jisyo>   or -userjisyo <jisyo>","specifies skkinput-local-jisyo",
    "-bj <jisyo>   or -backjisyo <jisyo>","specifies skkinput-local-jisyo\'s backup",
    "-sj <jisyo>   or -skkjisyo <jisyo>", "specifies skk-local-jisyo",
    "-rc <jisyo>   or -record <record>",  "specifies skkinput-record",
    "-fontset <fontset>",		  "specifies fontset",
    "-mfontset <fontset>",		  "specifies fontset of minibuffer window",
    "-fg <color>",			  "specifies foreground color",
    "-bg <color>",			  "specifies background color",
    "-bd <color>",			  "specifies border color",
    "-/+rv",				  "reverse video mode on/off",
    "-width",				  "specifies width of separate window",
    "-height",				  "specifies height of separate window",
    "-mwidth",				  "specifies width of minibuffer window",
    "-/+mfc",				  "changing minibuffer font on/off",
    "-/+nc",				  "next control on/off",
    "-/+ns",				  "next shift on/off",
#if defined(SUPPORT_KINPUT)
    "-/+kinput",			  "kinput protocol support on/off",
#endif
#if defined(SUPPORT_XIM)
    "-/+xim",				  "xim protocol support on/off",
#endif
#if defined(SUPPORT_XIMP)
    "-/+ximp",				  "ximp protocol support on/off",
#endif
    "-autosave",			  "specifies interval(msec) of auto-save",
    NULL, NULL,
  } ;
  ptr = syntaxtable ;
  while( *ptr != NULL ){
    fprintf( stderr, "%-35s ", *ptr ++ ) ;
    fprintf( stderr, "%s\n", *ptr ++ ) ;
  }
  return 0 ;
}

/*
 * オプションのチェックをする関数。
 */
static int skkinput_checkOptions( int *argc, char *argv[], int flag )
{
  int i, j ;
  struct skkinputOptionDef local_options[] = {
    { "-h",		OPTION_HOST,		True },
    { "-host",		OPTION_HOST,		True },
    { "-p",		OPTION_PORT,		True },
    { "-port",		OPTION_PORT,		True },
    { "-4",		OPTION_IPv4,		False },
    { "-6",		OPTION_IPv6,		False },
    { "-uj",		OPTION_USERDIC,		True },
    { "-userjisyo",	OPTION_USERDIC,		True },
    { "-bj",		OPTION_BAKDIC,		True },
    { "-backjisyo",	OPTION_BAKDIC,		True },
    { "-sj",		OPTION_SKKDIC,		True },
    { "-skkjisyo",	OPTION_SKKDIC,		True },
    { "-rc",		OPTION_SKKREC,		True },
    { "-record",	OPTION_SKKREC,		True },
    { "-config",	OPTION_CONFIG,		True },
    { "-cnf",		OPTION_CONFIG,		True },
    { "-?",		OPTION_HELP,		False },
    { "-help",		OPTION_HELP,		False },
    { "-v",		OPTION_VERSION,		False },
    { "-version",	OPTION_VERSION,		False },
#if defined(SUPPORT_KINPUT)
    { "-kinput",	OPTION_KINPUT_ON,	False },
    { "+kinput",	OPTION_KINPUT_OFF, 	False },
#endif
#if defined(SUPPORT_XIM)
    { "-xim",		OPTION_XIM_ON,		False },
    { "+xim",		OPTION_XIM_OFF, 	False },
#endif
#if defined(SUPPORT_XIMP)
    { "-ximp",		OPTION_XIMP_ON,		False },
    { "+ximp",		OPTION_XIMP_OFF,	False },
#endif
    { "-autosave",	OPTION_AUTOSAVE,	True  },
    { NULL,             0,              	False },
  } ;

  for( i = 1 ; i < *argc ; i ++ ){
    /* どれに一致するか引数を見る。*/
    for( j = 0 ; local_options[ j ].chara != NULL ; j ++ ){
      if( !strcmp( argv[ i ], local_options[ j ].chara ) )
	break ;
    }
    if( local_options[ j ].chara == NULL )
      continue ;
    /* 更に引数を必要としているのか？ */
    if( local_options[ j ].need_argument ){
      if( flag )
	argv[ i ] = NULL ;
      i ++ ;
      if( i >= ( *argc ) ){
	fprintf( stderr, "Lack of argument: \"%s\".\n", argv[ i - 1 ] ) ;
	goto checkoption_errorreturn ;
      }
    }
    /* オプション毎の処理。*/
    switch( local_options[ j ].function_no ){
      /* skkserv の起きているホスト名を設定する。*/
    case OPTION_HOST :
      skkserv_host = argv[ i ] ;
      break ;
      /* skkserv と通信するに利用するサービス名/ポート番号を設定する。*/
    case OPTION_PORT :
      if (skkserv_service) free(skkserv_service) ;
      skkserv_service = strdup(argv[i]);
      break ;
    case OPTION_IPv4:
      skkserv_pf = PF_INET;
      break;
    case OPTION_IPv6:
#if defined(USE_INET6)
      skkserv_pf = PF_INET6;
      break;
#else
      fprintf( stderr, "IPv6 not supported.\n" ) ;
      goto checkoption_errorreturn ;
#endif

      /* 局所辞書の名前を設定する。*/
    case OPTION_USERDIC :
      skkinput_local_jisyo_name = argv[ i ] ;
      break ;
    case OPTION_BAKDIC :
      skkinput_backup_jisyo_name = argv[ i ] ;
      break ;
      /* ヘルプを表示する。*/
    case OPTION_SKKREC :
      skkinput_record_name = argv[ i ] ;
      break ;
      /* 設定ファイルの所在を指定する。*/
    case OPTION_CONFIG :
      skkinput_config_name = argv[ i ] ;
      break ;
    case OPTION_HELP :
      goto checkoption_errorreturn ;
      /* バージョンを表示する。*/
    case OPTION_VERSION :
      fprintf( stderr, "skkinput version %s\n\n", skkinput_version ) ;
      fprintf( stderr, "This is free software. Skkinput can be copied only under the terms of\nthe GNU General Public license. There is NO warranty; not even for \nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ) ;
      return False ;
#if defined(SUPPORT_KINPUT)
    case OPTION_KINPUT_ON :
      support_kinput_protocol = True ;
      break ;
    case OPTION_KINPUT_OFF :
      support_kinput_protocol = False ;
      break ;
#endif
#if defined(SUPPORT_XIMP)
    case OPTION_XIMP_ON :
      support_ximp_protocol = True ;
      break ;
    case OPTION_XIMP_OFF :
      support_ximp_protocol = False ;
      break ;
#endif
#if defined(SUPPORT_XIM)
    case OPTION_XIM_ON :
      support_xim_protocol = True ;
      break ;
    case OPTION_XIM_OFF :
      support_xim_protocol = False ;
      break ;
#endif
    case OPTION_AUTOSAVE :
      /* 辞書の更新チェックをする期間を設定する。*/
      skkinput_autosave_interval = atol( argv[ i ] ) ;
      break ;
    default :
      goto checkoption_errorreturn ;
    }
    if( flag )
      argv[ i ] = NULL ;
  }
  if( flag ){
    for( j = 0, i = 0 ; i < *argc ; i ++ ){
      if( argv[ i ] != NULL ){
	argv[ j ++ ] = argv[ i ] ;
      }
    }
    *argc = j ;
  }
  return True ;

checkoption_errorreturn:
  skkinput_usage() ;
  return False ;
}

/*
 * 親であるメインしか知らない設定を Skkinput Widget に対して行う関数。
 *------
 * 「そりゃグローバル変数だから無視してアクセスしたいけど…」
 * 「いけません！」
 * 「え？ わ、若葉…」
 * 「…さんは、こんなことできっとくじけたりしません！」
 * 「いや…でも…しかし…」
 */
static void setupInputWindow
( Widget gw, caddr_t client, caddr_t caller )
{
  skkinput_setSkkInputValues( ( Widget )caller ) ;
  return ;
}

/*
 * Kinput Procotol Server としての機能を停止させる関数。
 *----------------
 * この関数はコールバックとして下位のウィジェット(つまり、kinputWidget)
 * から呼び出されることになる。
 */
static void closeProtocolServer
( Widget gw, caddr_t client, caddr_t caller )
{
  Widget *protocol_widget = ( Widget * )client ;
  /*
   * 各種 Protocol のために用意していたコールバックを全て破棄しておく。
   */
  XtRemoveAllCallbacks( gw, XtNsetupInputWindowNotify ) ;
  XtRemoveAllCallbacks( gw, XtNserverCloseNotify ) ;
  /*
   * サポートしているプロトコルの数を減らす。全部無くなったら、もはや
   * 変換サーバとしての機能は果たせないので強制終了する。
   */
  number_of_protocols -- ;
  *protocol_widget = NULL ;
  if( number_of_protocols <= 0 )
    longjmp( noprotocol_env, 1 ) ;
  return ;
}

/*
 * 一定時間経過毎に辞書のセーブを行うべきかどうかを判定する関数。
 */
static void skkinput_CheckAutoSaveJisyo
( XtPointer closure, XtIntervalId *id )
{
  /* 現在オートセーブの実行中じゃなく、一定期間内にはキーが押されずに、
   * しかも前回オートセーブされてから辞書が変更されていたら、オートセー
   * ブを実行する。*/ 
  if( !skkinput_autosave_key_dirty && !skkinput_now_autosaving &&
      skkinput_autosave_jisyo_dirty ){
    skkinput_now_autosaving = True ;
    /* ここで辞書セーブを行う関数を登録する。*/
    skkinput_autosave_work_id = XtAppAddWorkProc
      ( app_context, skkinput_AutoSaveJisyo, NULL ) ;
  } else {
    skkinput_autosave_key_dirty = False ;
  }
  *id = XtAppAddTimeOut
    ( app_context, 1000 * 15, skkinput_CheckAutoSaveJisyo, NULL ) ;
  return ;
}

/*
 * オートセーブを実行する関数。
 */
static Boolean skkinput_AutoSaveJisyo( XtPointer closure )
{
  /* さて、どうやってセーブしようかな？ これって上手く行くのかな？ */
  skkinput_autosaveLocalJisyo() ;
  /* セーブが終了したので、ロックを外す。*/
  skkinput_now_autosaving       = False ;
  /* オートセーブされたので、辞書の更新がされるまで再度のオートセーブ
   * は行われないようにする。*/
  skkinput_autosave_jisyo_dirty = False ;
  /* この割込みは自動的に消去されるべきである。*/
  return True ;
}

/*
 * X connection to %s is broken の時の処理を行う関数。
 */
static int skkinputIOErrorHandler( Display *display )
{
#ifndef MAIKAI_SKKSERV_TO_CONNECT_SURU
  /* SKKSERV とのコネクションを切ります。*/
  skkinput_CloseCommunication() ;
#endif
  /* 利用していた資源を全て解放する。*/
  XCloseDisplay( display ) ;
  /* 辞書が汚なかったら… */
  if( skkinput_jisyo_dirty ){
    fprintf( stderr, "(skkinput) Saving Jisyo..." ) ;
    /* 現在までに確定変換していた候補をセーブする。*/
    skkinput_updateLocalJisyo() ;
    fprintf( stderr, "done\n" ) ;
  }
  /* malloc していたメモリの解放。*/
  close_localjisyo() ;
  /* ハッシュをクリアする。*/
  clearHenkanKakuteiHash() ;
  exit( 1 ) ;
}
