/* # skkinput (Simple Kana-Kanji Input)
 * parseStr.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 <string.h>
#include <X11/X.h>
#include <X11/Xlib.h>

#include "commondef.h"
#include "skkkey.h"
#include "kanji.h"

/*
 * parseStr.c 内での局所的な定義。
 */
struct keyToMask {
  unsigned char *string ;
  long mask ;
} ;

/*
 * マクロの定義。
 */
#define forceLowerChr(chara)	\
(( (chara) >= 'A' && (chara) <= 'Z' )? ((chara) - 'A' + 'a') : (chara))

/*
 * プロトタイプ宣言。
 */
#if 0
static void dump_buffer( unsigned short *buffer, int size ) ;
#endif

/*
 * 空白文字を読み飛ばす関数。
 */
struct myChar *skip_empty_symbols( struct myChar *ptr )
{
  while( !IS_END_OF_STRING( *ptr ) ){
    if( !IS_ASCII_EQUAL( *ptr, 0x20 ) && 
	!IS_ASCII_EQUAL( *ptr, '\t' ) &&
	!IS_ASCII_EQUAL( *ptr, '\n' ) &&
	!IS_ASCII_EQUAL( *ptr, '\r' ) )
      break ;
    ptr ++ ;
  }
  return ptr ;
}

/*
 * 与えられた文字列の先頭部分に一致する文字列であるかどうかを判定する
 * 関数。ただし、大文字小文字の区別はしない。
 */
static int isPrefixString
( unsigned char *masterstring, unsigned char *prefixstring )
{
  while( *prefixstring != '\0' ){
    /* 大文字、小文字の区別なしに比較して異なる場合。*/
    if( forceLowerChr( *masterstring ) != *prefixstring )
      return False ;
    /* 次の文字を確認。*/
    masterstring ++ ;
    prefixstring ++ ;
  }
  return True ;
}

/*
 * X Resource 風のキー指定を解釈する関数。
 *---
 * ただし、この関数が解釈するのは一文字だけである。文字列なんかが入っ
 * ても意味はない。
 */
int parseXrLikeKeyString
( unsigned char *string, KeySym *r_keysym, 
  long *r_modifiers, long *r_checkModifiers )
{
  static struct keyToMask key_masks[] = {
    { "shift",   ShiftMask   }, { "lock", LockMask },
    { "cntrl", ControlMask }, { "mod1", Mod1Mask },
    { NULL,      0L          },
  } ;
  struct keyToMask *kptr ;

  /* いきなりのキー指定でなく、何らかの mask が存在する場合。*/
  if( *string != '<' ){
    for( kptr = key_masks ; kptr->string != NULL ; kptr ++ )
      if( isPrefixString( string, kptr->string ) ){
	*r_modifiers = *r_checkModifiers = kptr->mask ;
	string += strlen( kptr->string ) ;
	break ;
      }
    /* もし、mask が間違っていたら。*/
    if( kptr->string == NULL )
      return False ;
    /* ここで "<" が来るまで読み飛ばす。*/
    while( *string != '<' ){
      if( *string == '\0' || ( *string != ' ' && *string != '\t' ) )
	return False ;
      string ++ ;
    }
  }
  /* ここは <key> と書かれることになっているけど、どうしようかな。きち
     んと見るべきかな。*/
  if( !isPrefixString( string, "<key>" ) )
    return False ;
  /* 残りの文字列は keysym だと思う。*/
  *r_keysym = XStringToKeysym( string + 5 ) ;
  return True ;
}

int parseXrLikeKeyStrings
( unsigned char *string, struct XrLikeKey *keytbl )
{
  unsigned char *buffer, *ptr, *tmppoint ;
  int length = strlen( string ), num ;

  buffer = malloc( sizeof( unsigned char ) * ( length + 1 ) ) ;
  if( buffer == NULL )
    return 0 ;
  strcpy( buffer, string ) ;

  tmppoint = buffer ;
  num = 0 ;
  for( ptr = buffer ; ; ptr ++ ){
    switch( *ptr ){
    case '\\' :
      ptr ++ ;
      if( *ptr == '\0' )
	goto exit_loop ;
      break ;
    case ',' :
      /* 開始点と終了位置が同じだったら、空文字列なので無視する。*/
      if( tmppoint == ptr ){
	tmppoint ++ ;
	break ;
      }
      /* 登録する先があるのなら、実際に解釈する。*/
      if( keytbl != NULL ){
	/* ここで一旦変更したりするので、buffer に copy をとっていたり
	   するわけである。もしかすると、writable でない文字列が来るか
	   もしれないから。そう頻繁に呼ばれることはないので、malloc と
	   free を繰り返しても大丈夫な筈である。*/
	*ptr = '\0' ;
	if( parseXrLikeKeyString
	    ( tmppoint, &keytbl->keysym, &keytbl->modifiers,
	      &keytbl->checkModifiers ) ){
	  /* 無事に解釈出来たら、それを登録して次の文字へと向かう。*/
	  keytbl ++ ;
	  num ++ ;
	}
	/* separator はコンマなわけである。*/
	*ptr = ',' ;
      } else {
	num ++ ;
      }
      tmppoint = ptr + 1 ;
      break ;
    case '\0' :
      /* 開始点と終了位置が同じだったら、空文字列なので無視する。*/
      if( tmppoint != ptr ){
	if( keytbl != NULL ){
	  if( parseXrLikeKeyString
	      ( tmppoint, &keytbl->keysym, &keytbl->modifiers,
		&keytbl->checkModifiers ) ){
	    /* 無事に解釈出来たら、それを登録して次の文字へと向かう。*/
	    keytbl ++ ;
	    num ++ ;
	  }
	} else {
	  num ++ ;
	}
      }
      goto exit_loop ;
    default :
      break ;
    }
  }
exit_loop:
  free( buffer ) ;
  return num ;
}

/*
 * ポインタで指し示されたアドレスから続く文字列を 8 進数の数値とみて解
 * 釈する関数。 
 *----
 * ただし、127 を超えないように見る。
 */
int parseOctNumber
( struct myChar *string, struct myChar *dest )
{
  int counter = 0, num = 0 ;
  while( IS_ASCII_CHARA( *string ) && 
	 string->chara >= '0' && string->chara < '8' ){
    num = ( num << 3 ) | ( string->chara - '0' ) ;
    /* 巨大な数値は入らないようになっている。設定できるのは、char の正
       数だけ。 */
    if( num > 127 ){
      num = num >> 3 ;
      break ;
    }
    counter ++ ;
    string ++ ;
  }
  if( dest != NULL ){
    dest->charset = CHARSET_ASCII ;
    dest->chara   = num ;
  }
  return counter ;
}

/*
 * ポインタで指し示されたアドレスから続く文字列を 16 進数の数値とみて解
 * 釈する関数。 
 *----
 * ただし、127 を超えないように見る。
 */
int parseHexNumber
( struct myChar *string, struct myChar *dest )
{
  int counter = 0, num = 0 ;
  while( 1 ){
    if( !IS_ASCII_CHARA( *string ) )
      break ;
    /* 16進の指定として可能なのは、0 から 9 と a から f まで。*/
    if( string->chara >= '0' && string->chara <= '9' ){
      num = ( num << 4 ) | ( string->chara - '0' ) ;
    } else if( string->chara >= 'a' && string->chara <= 'f' ){
      num = ( num << 4 ) | ( string->chara - 'a' + 10 ) ;
    } else if( string->chara >= 'A' && string->chara <= 'F' ){
      num = ( num << 4 ) | ( string->chara - 'A' + 10 ) ;
    } else {
      break ;
    }
    /* 巨大な数値は入らないようになっている。設定できるのは、char の正
       数だけ。 */
    if( num > 127 ){
      num = num >> 4 ;
      break ;
    }
    counter ++ ;
    string ++ ;
  }
  if( dest != NULL ){
    dest->charset = CHARSET_ASCII ;
    dest->chara   = num ;
  }
  return counter ;
}

/*
 * backslash の後に続く特殊記号を見切るのに利用される関数。
 */
int parseBackslashControl
( struct myChar *string, struct myChar *dest )
{
  static unsigned char *cntrlKeys = "@abcdefghijklmnopqrstuvwxyz[\\]^_" ;
  unsigned char *ptr ;
  static unsigned char *miscKeys[] = {
    "Home",	"Left",		"Up",		"Right",
    "Down",	"PageUp",	"PageDown",	"End",
    "Begin",	"Insert",	"Clear",	"Help",
    "Break",	"Henkan_Mode",	"Muhenkan",	"F1",
    "F2",	"F3",		"F4",		"F5",
    "F6",	"F7",		"F8",		"F9",
    "F10",	"F11",		"F12",		NULL,
  } ;
  struct myChar lchara ;
  int i, len ;

  /* 続く文字が ASCII でなければ駄目。*/
  if( !IS_ASCII_CHARA( *string ) || IS_END_OF_STRING( *string ) )
    return ERR ;

  if( string->chara >= '0' && string->chara < '8' ){
    /* 続く文字列は 8進数による直接コード指定。*/
    return parseOctNumber( string, dest ) ;
  } else if( string->chara == 'x' ){
    /* 続く文字列は 16進数による直接コード指定。*/
    string ++ ;
    return parseHexNumber( string, dest ) ;
  }
  /* そうでない場合には…。*/
  if( IS_ASCII_EQUAL( string[ 1 ], '-' ) ){
    if( IS_END_OF_STRING( string[ 2 ] ) ){
      return ERR ;
    }
    switch( string->chara ){
      /* コントロールキーの指定であった場合。*/
    case 'C' :
      if( IS_ASCII_EQUAL( string[ 2 ], '\\' ) ){
	if( IS_ASCII_EQUAL( string[ 3 ], '\\' ) ){
	  /* backslash の場合は二個続かないと駄目だよね。*/
	  if( dest != NULL ){
	    dest->charset = CHARSET_ASCII ;
	    dest->chara   = 28 ;
	  }
	  return 4 ;
	} else {
	  /* もう一度＼を解析する。*/
	  len = parseBackslashControl( string + 3, &lchara ) ;
	  if( len < 0 )
	    return ERR ;
	  /* これらのキーの場合には特別にコントロール付きを解釈する。
	   * 本当は全てのキーを見たいけどね…。でもそこまですると重く
	   * なるだけのような気がするからしません。そもそもこれらの
	   * キーを見るのも、Insert を使いたかったその延長なんです。
	   */ 
	  if( lchara.chara >= CHARA_HOME &&
	      lchara.chara <	NUMBER_OF_KEYS ){
	    lchara.chara = lchara.chara | 1 ;
	  }
	  if( dest != NULL )
	    *dest = lchara ;
	  return ( len + 3 ) ;
	}
      } else {
	/* Control の指定として正しい文字が来ているかどうかを調べる。*/
	for( ptr = cntrlKeys ; *ptr != '\0' ; ptr ++ ){
	  if( IS_ASCII_EQUAL( string[ 2 ], *ptr ) ){
	    /* 正しい文字が来た！ */
	    if( dest != NULL ){
	      dest->charset = CHARSET_ASCII ;
	      dest->chara   = ptr - cntrlKeys ;
	    }
	    return 3 ;
	  }
	}
      }
      /* 一応、Control+Space は C-@ と同じものとして扱う。*/
      if( IS_ASCII_EQUAL( string[ 2 ], ' ' ) ){
	if( dest != NULL ){
	  dest->charset = CHARSET_ASCII ;
	  dest->chara   = '\0' ;
	}
	return 3 ;
      }
      return ERR ;
      
      /* ESC キーの指定であった場合。*/
    case 'M' :
      if( dest != NULL ){
	dest->charset = CHARSET_ASCII ;
	dest->chara   = 0x1b ;
      }
      return 2 ;
    }
  }
  /* それ以外の制御文字列だったかどうかを判断する。*/
  for( i = 0 ; miscKeys[ i ] != NULL ; i ++ ){
    int len = strlen( miscKeys[ i ] ) ;
    if( !myCharCharStrncmp( string, miscKeys[ i ], len ) ){
      if( dest != NULL ){
	dest->charset = CHARSET_ASCII ;
	dest->chara   = CHARA_HOME + ( i << 1 ) ;
      }
      return len ;
    }
  }
  return ERR ;
}

/*
 * emacs-like なキー指定を解釈するのに利用される関数。
 * buffer に NULL を入れた場合には必要となる short の配列の大きさを返
 * すようになっている。
 */
int parseKeyString
( struct myChar *string, struct myChar *buffer, int bufsize )
{
  int i, ret, uselen ;

  uselen = 0 ;
  for( i = 0 ; !IS_END_OF_STRING( *string ) ; i ++ ){
    /* バッファを使い切ってないかどうかチェックする。*/
    if( buffer != NULL && uselen >= bufsize )
      break ;
    /* バックスラッシュが来た場合？ */
    if( IS_ASCII_EQUAL( *string, '\\' ) ){
      string ++ ;
      /* ここでいきなり文字列が切れたりするとそれは変である。*/
      if( IS_END_OF_STRING( *string ) )
	return 0 ;
      if( IS_ASCII_EQUAL( *string, '\\' ) ){
	if( buffer != NULL )
	  *buffer ++ = *string ;
	uselen ++ ;
	string ++ ;
      } else {
	/* バック／に続く文字の解釈をする。*/
	if( ( ret = parseBackslashControl( string, buffer ) ) <= 0 ){
	  if( buffer != NULL )
	    *buffer ++ = *string ;
	  uselen ++ ;
	  /* おおっと失敗したら何事も無かったのかのように次へと移動し
	   * ている…。*/
	  string ++ ;
	} else {
	  /* buffer + 0 には parseBackslashControl の結果が入っている
	   * ので、次へと送ることにする。*/
	  buffer ++ ;
	  uselen ++ ;
	  /* 解釈した分だけ送る。*/
	  string += ret ;
	}
      }
    } else {
      /* それ以外の文字が来た場合の処理。*/
      if( buffer != NULL )
	*buffer ++ = *string ;
      uselen ++ ;
      string ++ ;
    }
  }
  return uselen ;
}

/*
 * 続く double quote で囲まれた文字列を emacs-lisp 風の文字列の宣言で
 * あると解釈して、その結果を short の配列として返す関数。result には、
 * その配列のサイズが入る。負だったらエラーになる。
 */
struct myChar *parseString
( struct myChar *string, struct myChar **dest, int *result )
{
  struct myChar *start, *ptr ;
  int len ;

  /* まず結果は何も出ていないと設定しておく。*/
  *dest   = NULL ;
  *result = -1 ;
  /* 最初に空白が存在しても大丈夫なように読み飛ばす。*/
  string = skip_empty_symbols( string ) ;
  /* 文字列の開始記号である double quote が無ければ、エラーである。*/
  if( !IS_ASCII_EQUAL( *string, 0x22 ) )
    return string ;
  string ++ ;
  start = string ;
  /* 文字列の中身を調べる。*/
  while( !IS_END_OF_STRING( *string ) &&
	 !IS_ASCII_EQUAL( *string, 0x22 ) ){
    /* もし仮に double quote を入れたいとかいう要求があった時に…。*/
    if( IS_ASCII_EQUAL( *string, '\\' ) ){
      string ++ ;
      /* そこで文字列が終わってしまったら、変。*/
      if( IS_END_OF_STRING( *string ) )
	return string ;
    }
    string ++ ;
  }
  /* さて、どこまで行ったかな？ */
  if( !IS_ASCII_EQUAL( *string, 0x22 ) ){
    return string ;
  }
  /* 空文字列も許す…。*/
  if( string == start ){
    /* 全然文字列が入ってないか、きちんとダブルクウォートで終端され *
     * てない。*/
    *dest   = NULL ;
    *result = 0 ;
    string ++ ;
  } else {
    MYCHAR_SET_END_OF_STRING( *string ) ;
    /* あらかじめ必要な文字列の長さを計算しておく。*/
    if( ( len = parseKeyString( start, NULL, 0 ) ) <= 0 ){
      /* 一応もとに戻す。*/
      MYCHAR_SET_CHARA( *string, 0x22 ) ;
      string ++ ;
      return string ;
    }
    /* 元の文字列の長さだけmallocします。*/
    ptr = malloc( ( len + 1 ) * sizeof( struct myChar ) ) ;
    if( ptr != NULL ){
      /* 解釈した結果を dest に代入する。*/
      parseKeyString( start, ptr, len ) ;
      MYCHAR_SET_END_OF_STRING( ptr[ len ] ) ;
      *dest   = ptr ;
      *result = len ;
    }
    /* 改変してしまった文字列をもとに戻す。*/
    MYCHAR_SET_CHARA( *string, 0x22 ) ;
    string ++ ;
  }
  return string ;
}

/*
 * emacs-lisp にある expand-file-name に相当する関数。
 *-----
 * 一番最初に "~" があった場合に home directory に展開する機能を所持し
 * ている。ただし、wild-card なんとかには対応していない。
 * この関数を呼んだ結果は skk-local-jisyo-path に入ることになる。
 * skk-local-jisyo-path が NULL でなかったら、この関数を呼ばない方が良
 * いと思うが呼ぶ側では多分それをチェックできないので、ここでチェック
 * する。
 */
unsigned char *expand_file_name( unsigned char *path )
{
  unsigned char *ptr ;
  unsigned char *home ;
  int len = strlen( path ) ;

  /* "~" という home directory を表現する記号が入っていた場合の処理。*/
  if( *path == '~' ){
    /* HOME Directory が何処かを得る。*/
    home = getenv( "HOME" ) ;
    /* Home directory + 残りのパスの長さを malloc する。*/
    ptr = malloc( strlen( home ) + len + 1 ) ;
    if( ptr == NULL ){
      /* malloc 失敗…これは致命的なエラーなので継続動作はしない。*/
      fprintf( stderr, "Memory fault.\n" ) ;
      exit( 1 ) ;
    }
    /* "~" を展開して、書き直す。*/
    strcpy( ptr, home ) ;
    strcat( ptr, path + 1 ) ;
  } else {
    /* その必要が無い場合。*/
    ptr = ( unsigned char *)malloc( len + 1 ) ;
    strcpy( ptr, path ) ;
  }
  return ptr ;
}

#if 0
int main( int argc, char *argv[] )
{
  unsigned short buffer[ 256 ] ;
  int len ;
  len = parseKeyString( "\\C-u\\M-xabc", buffer, 256 ) ;
  dump_buffer( buffer, len ) ;
  return 0 ;
}

#endif

void dump_sstring( unsigned short *buffer, int size )
{
  int i ;
  printf( "(dump_sstring)" ) ;
  if( size == 0 ){
    printf( "empty string" ) ;
  }
  for( i = 0 ; i < size; i ++ ){
    printf( "%04x, ", buffer[ i ] ) ;
  }
  printf( "\n" ) ;
  return ;
}

static void skip_charSpace( unsigned char **string )
{
  unsigned char *ptr = *string ;

  while( *ptr != '\0'  ){
    if( *ptr != 0x20 && *ptr != '\t' && *ptr != '\n' &&
	*ptr != '\r' )
      break ;
    ptr ++ ;
  }
  *string = ptr ;
  return ;
}

unsigned char *getOneFontsetFromFontsetList( unsigned char **string )
{
  unsigned char *ptr, *retstring = NULL ;
  int length, next_chara_ignore ;

  /* 先頭の空白文字列を飛ばす。*/
  skip_charSpace( string ) ;

  next_chara_ignore = False ;
  for( ptr = *string, length = 0  ; *ptr != '\0' ; ptr ++, length ++ ){
    if( next_chara_ignore ){
      next_chara_ignore = False ;
      continue ;
    }
    /* "," が見付かるとそこまでが一つのフォントセット名になる。*/
    if( *ptr == ',' ){
      /* "," は飛ばして欲しいが、"," も含んで文字列コピーされるのは嫌
       * だから…length は増やさない。*/ 
      ptr ++ ;
      break ;
    }
    /* あるのかどうかは分からないけど、＼は次の文字を無視するものとし
     * て予約しておく。*/
    if( *ptr == '\\' ){
      next_chara_ignore = True ;
    }
  }
  /* 見付かりましたか？ */
  if( length > 0 ){
    retstring = malloc( sizeof( unsigned char ) * ( length + 1 ) ) ;
    if( retstring == NULL )
      return NULL ;
    strncpy( retstring, *string, length ) ;
    retstring[ length ] = '\0' ;
  }
  *string = ptr ;
  return retstring ;
}
