/* # skkinput (Simple Kana-Kanji Input)
 * skksoc.c --- communicate with skkserv.
 * 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.
 */
/* skkserv とお話する部分をまとめたソース。*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

#include "config.h"
#include "commondef.h"
#include "buffers.h"

/*
 * プロトタイプ宣言。
 */
/* skksvect.c */
extern void free_SkkinpSearchVector( SkkinpSearchVector **top ) ;
/* skksoc.c */
int skkinput_CloseCommunication( void ) ;

/* skkserv との通信に利用できるコマンドの一覧。*/
enum {
  SKKSERV_BYE = 0, SKKSERV_SEARCH, SKKSERV_VERSION, SKKSERV_HOSTNAME,
} ;

extern char *skkserv_host ;
extern char *skkserv_service ;
extern int skkserv_pf;

/* skkserv とお話する際のバッファ。*/
static struct MessageBuffer mesbuf ;
#if !USE_INET6
/* SKK server の情報。*/
struct sockaddr_in server ;
/* SKK server の Host の情報。*/
static struct hostent *host ;
#endif
/* skkserv をお話するのに利用する socket */
static int skkserv_soc ;
/* skkserv とのお話に使えるファイルポインタ。*/
static FILE *read_skkserv_fp = NULL, *write_skkserv_fp = NULL ;
static int skkserv_connection_ok = False ;

/*
 * プロトタイプ宣言。
 */
/* skksvect.c */
SkkinpSearchVector *add_SkkinpSearchVector
( SkkinpSearchVector *topp,   struct myChar *str,
  SkkinpSearchVector **rnode, int *rpos ) ;
/* local */
static void clear_ServerSend( void ) ;
static SkkinpSearchVector *messagebuffer_to_searchVector
( SkkinpSearchVector *top, struct MessageBuffer *mesbuf ) ;
static void myCharStringToNormalString
( struct MessageBuffer *buf, struct myChar *string ) ;

#if !USE_INET6
/*
 * ソケットを作成する関数。
 *------------
 * server の名前が与えられると、そこに対しての socket を作成しよう
 * と試みる。
 */
static int makeSocket( char *server_name, int port_num )
{
  struct protoent *proto;
  /* Protocol の設定。*/
  if( ( proto = getprotobyname( "tcp" ) ) == NULL ){
    return 1 ;
  }
  /* ソケットの生成 */
  if( ( skkserv_soc = socket( AF_INET, SOCK_STREAM, proto->p_proto ) ) < 0 ){
    fprintf( stderr, "Cannot make socket.\n" ) ;
    return 1 ;
  }
  /* サーバの情報を取り出す。*/
#ifdef HAVE_BZERO
  bzero( ( char *)&server, sizeof( server ) ) ;
#else
  memset( ( char *)&server, 0, sizeof( server ) ) ;
#endif
  server.sin_family = AF_INET ;
  if( ( host = gethostbyname( server_name ) ) == NULL ){
    shutdown( skkserv_soc, 2 ) ;
    close( skkserv_soc ) ;
    fprintf( stderr, "%s: Unknown host.\n", server_name ) ;
    return 1 ;
  }
#ifdef HAVE_BCOPY
  bcopy( host->h_addr, &server.sin_addr, host->h_length ) ;
#else
  memcpy( &server.sin_addr, host->h_addr, host->h_length ) ;
#endif
  server.sin_port = htons( port_num ) ;

  read_skkserv_fp  = fdopen( skkserv_soc, "r" ) ;
  write_skkserv_fp = fdopen( skkserv_soc, "w" ) ;

  /* fdopen に失敗したら… */
  if( read_skkserv_fp == NULL || write_skkserv_fp == NULL ){
    if( read_skkserv_fp != NULL )
      fclose( read_skkserv_fp ) ;
    if( write_skkserv_fp != NULL )
      fclose( write_skkserv_fp ) ;
    read_skkserv_fp = write_skkserv_fp = NULL ;
    return 1 ; 
  }
  return 0 ;
}
#endif

/*
 * skkserv に message を送信する関数。
 */
static int send_to_server( int funcno, struct myChar *string )
{
  /* 制御コードに間違いは存在しないか？ */
  switch( funcno ){
    /* 制御コードだけのコマンド。*/
  case SKKSERV_BYE :
  case SKKSERV_VERSION :
  case SKKSERV_HOSTNAME :
    /* サーバーに行って欲しい機能を指定。*/
    fprintf( write_skkserv_fp, "%d\n", funcno ) ;
    fflush( write_skkserv_fp ) ;
    break ;

    /* 加えて、文字列が必要なコマンド。*/
  case SKKSERV_SEARCH :
    /* サーバーに行って欲しい機能 = 「検索」を指定。*/
    init_messagebuffer( &mesbuf ) ;
    /* 内部コードを通常の文字列に変換する。*/
    myCharStringToNormalString( &mesbuf, string ) ;
#ifdef DEBUG
    printf( "To skkserv: \"%s\"\n", mesbuf.buffer ) ;
#endif
    fprintf( write_skkserv_fp, "1%s \n", mesbuf.buffer ) ;
    fflush( write_skkserv_fp ) ;
    close_messagebuffer( &mesbuf ) ;
    break ;

  default :
#ifdef DEBUG
    fprintf( stderr, "WARNING : Unknown function code.\n" ) ;
#endif
    return 1 ;
  }
  return 0 ;
}

/*
 * skkserv から文字列を受信する関数。
 * ----
 * どの位の長さの文字列が帰って来るのかが判明しないので、リストで受ける
 * ことになる。
 */
static SkkinpSearchVector *skkinput_ReadRequestsResult
( SkkinpSearchVector *top )
{
  /* サーバーの返した答えを受けるバッファ。*/
  int i, ret, top_flag ;
  unsigned char pbuf ;

#ifdef DEBUG
  printf( "Wait for an answer....\n" ) ;
#endif

  /* メッセージバッファを初期化する。*/
  init_messagebuffer( &mesbuf ) ;

  i = 0 ;
  top_flag = False ;

  for( ; ; ){
    /* server から文字列を受信する。*/
    ret = read( skkserv_soc, &pbuf, 1 ) ;
    /* エラーが発生した場合の処理。*/
    if( ret <= 0 ){
      /* 受信、失敗。*/
      perror( "read" ) ;
      close_messagebuffer( &mesbuf ) ;
      /* コネクションを切断して強制的に処理を抜ける。*/
      skkinput_CloseCommunication() ;
      return top ;
    }
    /* skkserv はたまに先頭に空白を入れて返すということをしてくれるの
       で、それを捨てる。この空白はただのバグだから。*/
    if( !top_flag && pbuf != '/' ){
      continue ;
    } else {
#ifdef DEBUG
      printf( "%c", pbuf ) ;
#endif
      top_flag = True ;
      add_messagebuffer( &mesbuf, pbuf ) ;
    }
    /* 終端文字を読み込んだのなら、終了する。skkserv の終端文字は *
     * EOL(0x0a) である。*/
    if( pbuf == 0x0a )
      break ;
  }
#ifdef DEBUG
  printf( "\n" ) ;
#endif
  top = messagebuffer_to_searchVector( top, &mesbuf ) ;
  /* メッセージバッファを閉じる。*/
  close_messagebuffer( &mesbuf ) ;
  return top ;
}

#if 0
/*
 * クライアントのバージョンを得るためにサーバと通信する関数。
 */
char *skkinput_GetClientVersion( void )
{
  int i ;

  /* サーバーにクライアントのバージョンを返すよう指示する。*/
  send_to_server( SKKSERV_VERSION, NULL ) ;

  /* server が返して来るバージョン番号を受ける。*/
  i = 0 ;
  if( read( skkserv_soc, message_buffer, TEXTMAXLEN ) <= 0 ){
    perror( "read" ) ;
    return NULL ;
  }
  message_buffer[ TEXTMAXLEN ] = '\0' ;
  return message_buffer ;
}

/*
 * クライアントのバージョンを得るためにサーバと通信する関数。
 */
char *skkinput_GetClientHost( void )
{
  /* サーバーにクライアントのバージョンを返すよう指示する。*/
  send_to_server( SKKSERV_HOSTNAME, NULL ) ;

  /* server が返して来る hostname を受ける。*/
  if( read( skkserv_soc, message_buffer, TEXTMAXLEN ) <= 0 ){
    perror( "read" ) ;
    return NULL ;
  }
  message_buffer[ TEXTMAXLEN ] = '\0' ;
  return message_buffer ;
}
#endif

/*
 * サーバからの受信をクリアする関数。
 */
static void clear_ServerSend( void )
{
  unsigned char pbuf ;

  for( ; ; ){
    if( read( skkserv_soc, &pbuf, 1 ) <= 0 ){
      /* 受信、失敗。*/
      perror( "read" ) ;
      skkinput_CloseCommunication() ;
      return ;
    }
    if( pbuf == 0x0a )
      break ;
  }
  return ;
}

/*
 * socket を用意し、skkerv に接続する関数。
 *----
 */
int skkinput_StartCommunication( char *server_name, char *service_name, int pf )
{
#if USE_INET6
  struct addrinfo hints, *res, *res0;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = pf;
  hints.ai_socktype = SOCK_STREAM;

  if (getaddrinfo(server_name, service_name, &hints, &res0)) {
    fprintf(stderr, "Warning: cannot make a socket to %s:%s\n", 
	    server_name, service_name);
    skkserv_connection_ok = False;
    return 1;
  }

  skkserv_connection_ok = False;
  for (res = res0; res != NULL; res = res->ai_next) {
    skkserv_soc = socket(res->ai_family, res->ai_socktype, 0);
    if (skkserv_soc < 0) continue;

    if (connect(skkserv_soc, 
		(struct sockaddr *)res->ai_addr, res->ai_addrlen) < 0) {
      perror("connect");
      fprintf(stderr, "Warning: cannot connect SKK server.\n");
      shutdown(skkserv_soc, 2);
      close(skkserv_soc);
      skkserv_connection_ok = False;
      skkserv_soc = -1;
      continue;
    } else {
      skkserv_connection_ok = True;
      break;
    }
  }

  /* free addrinfo */
  freeaddrinfo (res0);

  if (skkserv_soc < 0 || skkserv_connection_ok == False) {
    fprintf(stderr, "Warning: cannot connect to SKK server, give up.\n");
    skkserv_connection_ok = False;
    return 1;
  }

  read_skkserv_fp = fdopen(skkserv_soc, "r");
  write_skkserv_fp = fdopen(skkserv_soc, "w");
  if (read_skkserv_fp == NULL || write_skkserv_fp == NULL) {
    if (read_skkserv_fp != NULL) fclose(read_skkserv_fp);
    if (write_skkserv_fp != NULL) fclose(write_skkserv_fp);
    read_skkserv_fp = write_skkserv_fp = NULL;
    shutdown(skkserv_soc, 2);
    close(skkserv_soc);
    skkserv_connection_ok = False;
    fprintf(stderr, "Warning: cannot make socket fd of SKK server connection.\n");
    return 1;
  }
#else
  /* IPv4 only */
  struct servent *se = getservbyname(service_name, SKKSERV_SERVICE_PROTO);
  short port_num = atoi(DEFAULT_SKKPORT);
  if (se)
      port_num = ntohs(se->s_port);
  /* skkerv と通信するためのソケットを作成致します。*/
  if( makeSocket( server_name, port_num ) ){
    fprintf( stderr, "Warning : cannot make a socket." ) ;
    skkserv_connection_ok = False ;
    return 1 ;
  }
  /* 接続要求。*/
  if( connect( skkserv_soc, 
	       ( struct sockaddr *)&server, sizeof( server ) ) < 0 ){
    perror( "connect" ) ;
    fprintf( stderr, "Warning : cannot connect SKK server." ) ;
    shutdown( skkserv_soc, 2 ) ;
    close( skkserv_soc ) ;
    skkserv_connection_ok = False ;
    return 1 ;
  }
  skkserv_connection_ok = True ;
#endif
  return 0 ;
}

/*
 * SKK server との接続を終了する関数。
 *----
 * 終了前には必ず一度呼ばれた方が良いと思われる関数である。
 */
int skkinput_CloseCommunication( void )
{
  /* 接続されていなければ、切断できない。*/
  if( !skkserv_connection_ok )
    return 0 ;
  /* fp を閉じる。*/
  if( write_skkserv_fp != NULL ){
    /* SKK server に「さようなら」と伝える。*/
    send_to_server( SKKSERV_BYE, NULL ) ;
    fclose( write_skkserv_fp ) ;
    write_skkserv_fp = NULL ;
  }
  if( read_skkserv_fp != NULL ){
    fclose( read_skkserv_fp ) ;
    read_skkserv_fp = NULL ;
  }
  /* もうその socket は利用されないことを宣言する。*/
  shutdown( skkserv_soc, 2 ) ;
  /* Socket を閉じる。*/
  close( skkserv_soc ) ;
  skkserv_connection_ok = False ;
  return 0 ;
}

/*
 * skkserv に検索の REQUEST を送る関数。
 *----
 * 入力は検索キーである。
 */
SkkinpSearchVector *skkinput_Request
( struct myChar *str, SkkinpSearchVector *top )
{
  unsigned char pbuf ;

#ifndef MAIKAI_SKKSERV_TO_CONNECT_SURU
  if( !skkserv_connection_ok ){
    if( skkinput_StartCommunication( skkserv_host, skkserv_service, skkserv_pf ) )
      return top ;
  }
#else
  /* skkserv が生きていなければ、メッセージの送りようが無いので終了
     する。*/
  if( skkinput_StartCommunication( skkserv_host, skkserv_portnum ) )
    return top ;
#endif

  /* サーバーに検索を行うよう指示する。*/
  send_to_server( SKKSERV_SEARCH, str ) ;

  /* skkserv が connect に成功したかどうかを返すので、それを受ける。*/
  if( read( skkserv_soc, &pbuf, 1 ) <= 0 ){
    /* 受信、失敗。*/
    perror( "read" ) ;
    skkinput_CloseCommunication() ;
    return top ;
  }
  /* サーバーの返答をチェックする。*/
  switch( pbuf ){
    /* サーバーがエラーを返した場合の処理。*/
  case '0' :
#ifdef DEBUG
    fprintf( stderr, "SKK server error." ) ;
#endif
    break ;

    /* サーバーがきちんと見付かった場合の処理。*/
  case '1' :
    top = skkinput_ReadRequestsResult( top ) ;
    break ;

    /* サーバーがリクエストしたキーで見付けられなかった場合の処理。*/
  case '4' :
    /* 検索に用いた文字列がそのまま返って来る。*/
    /* それを読み込んでおく。*/
    clear_ServerSend() ;
    break ;

    /* サーバーが複数の client を処理しないといけなくて、困っている場合。*/
  case '9' :
    fprintf( stderr, "SKK server has too many clients." ) ;
    break ;

  default :
    clear_ServerSend() ;
    break ;
  }
#ifdef MAIKAI_SKKSERV_TO_CONNECT_SURU
  /* コネクションを切るようにする。*/
  skkinput_CloseCommunication() ;
#endif
  return top ;
}

/*
 * skkserv から送られてきた文字列を内部コードに変換して、それを変換結
 * 果として記憶するよう指示する関数。
 */
static SkkinpSearchVector *messagebuffer_to_searchVector
( SkkinpSearchVector *top, struct MessageBuffer *mesbuf )
{
  struct myChar *string ;
#ifdef DEBUG
  printf( "From skkserv:\"%s\"\n", mesbuf->buffer ) ;
#endif
  /* skkserv から送られて来た文字列を内部コードに変換する。*/
  extendedUnixCode_stringToMycharset( mesbuf->buffer, &string ) ;
  /* search vector として登録する。*/
  top = add_SkkinpSearchVector( top, string, NULL, NULL ) ;
  /* 内部コード文字列を解放する。*/
  free( string ) ;
  return top ;
}

/*
 * 内部文字コードからなる文字列を日本語Extended Unix Code に変換する関数。
 *---
 * この時に ISO8859_? とか KBC とか GBD とか全部捨てられることになる。
 * (合掌)
 */
static void myCharStringToNormalString
( struct MessageBuffer *buf, struct myChar *string )
{
  for( ; !IS_END_OF_STRING( *string ) ; string ++ ){
    switch( string->charset ){
    case CHARSET_ISO8859_1 :
    case CHARSET_ISO8859_2 :
    case CHARSET_ISO8859_3 :
    case CHARSET_ISO8859_4 :
    case CHARSET_ISO8859_5 :
    case CHARSET_ISO8859_6 :
    case CHARSET_ISO8859_7 :
    case CHARSET_ISO8859_8 :
    case CHARSET_ISO8859_9 :
      /* ISO8859 文字集合の左半面なら受理する。*/
      if( !( string->chara & 0x80 ) )
	add_messagebuffer( buf, string->chara ) ;
      break ;
    case CHARSET_JISX0201_1976 :
      if( string->chara & 0x80 ){
	add_messagebuffer( buf, 0x8E ) ;
	add_messagebuffer( buf, string->chara ) ;
      } else {
	add_messagebuffer( buf, string->chara & 0x7F ) ;
      }
      break ;
      /* 旧JIS も新JIS も無視して全部 JISX0208_1983 とする。*/
    case CHARSET_JISX0208_1978 :
    case CHARSET_JISX0208_1983 :
      add_messagebuffer( buf, ( string->chara >> 8     ) | 0x80 ) ;
      add_messagebuffer( buf, ( string->chara & 0x00FF ) | 0x80 ) ;
      break ;
    case CHARSET_JISX0212_1990 :
      add_messagebuffer( buf, 0x8F ) ;
      add_messagebuffer( buf, ( string->chara >> 8     ) | 0x80 ) ;
      add_messagebuffer( buf, ( string->chara & 0x00FF ) | 0x80 ) ;
      break ;
    default :
      break ;
    }
  }
  return ;
}
