/* # skkinput (Simple Kana-Kanji Input)
 * FontMgr.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 <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>

#include "commondef.h"
#include "kanji.h"
#define fontMgr_Static
#include "FontMgr.h"
#include "config.h"

extern unsigned char *getOneFontsetFromFontsetList
( unsigned char **string ) ;

/*
 * skkinput が現在必要としているフォントの名前…。
 */
/* iso8859 の左半分しか利用しないので、どれを使っても大丈夫…だと思う。
 * ただ、jisx0201 を使ってしまうと backslash とかそういうのを使っちゃ
 * うから…。*/
/*
 * フォント管理リスト。
 */
static struct skkinputManagedFont *skkinputManagedFontListTop = NULL ;
static struct skkinputManagedFont *skkinputFreeFontListTop = NULL ;
static int freeFontListLength = 0 ;

/*
 * デフォルトのフォント。
 */
static struct skkinputManagedFont *defaultFontSet[ NUMBER_OF_CHARSET ] ;
static struct skkinputManagedFont *defaultMinibufFontSet[ NUMBER_OF_CHARSET ] ;

#ifdef DEBUG
/*
 *
 */
static void dump_fontlist( void )
{
  struct skkinputManagedFont *node = skkinputManagedFontListTop ;
  int i ;

  printf( "----- Manaed Font List ------\n" ) ;
  for( i = 0 ; node != NULL ; node = node->next, i ++ ){
    printf( "(%2d) ... \"%s\"\n", i, node->name ) ;
  }
  fflush( stdout ) ;
  return ; 
}

static void dump_freefontlist( void )
{
  struct skkinputManagedFont *node = skkinputFreeFontListTop ;
  int i ;

  printf( "----- Free Font List ------\n" ) ;
  for( i = 0 ; node != NULL ; node = node->next, i ++ ){
    printf( "(%2d) ... \"%s\"\n", i, node->name ) ;
  }
  fflush( stdout ) ;
  return ; 
}
#endif

/*
 *
 */
static struct skkinputManagedFont *fontMgr_AllocManagedFontListNode
( void )
{
  struct skkinputManagedFont *node ;

  node = ( struct skkinputManagedFont *)malloc
    ( sizeof( struct skkinputManagedFont ) ) ;
  /* 若葉「たたりさん、どうしましょう。メモリがなくなってしまいました…」*/
  /* たたり「おろおろするなよ、若葉…。って、これはどうしようもないか」 */
  if( node != NULL ){
    /* 一応、きちんと初期化しておく。*/
    node->next = NULL ;
    node->name = NULL ;
    node->font = NULL ;
    node->refer_count = 0 ;
  }
  return node ;
}

/*
 * 既にフォントが登録されているかどうかをチェックする関数。
 */
static struct skkinputManagedFont *fontMgr_FindManagedFontByName
( struct skkinputManagedFont *top, unsigned char *fontName )
{
  struct skkinputManagedFont *node = top ;
  unsigned char *ptr1, *ptr2 ;
  int chara1, chara2 ;

  /* リストの先頭が NULL だったら、リスト自体が空です。*/
  if( node == NULL )
    return NULL ;
  /* 順々にリストをたどりましょうね！ */
  while( node != NULL ){
    ptr1 = node->name ;
    ptr2 = fontName ;
    while( *ptr1 != '\0' ){
      chara1 = ( *ptr1 >= 'A' && *ptr1 <= 'Z' )?
	*ptr1 + ( 'a' - 'A' ) : *ptr1 ;
      chara2 = ( *ptr2 >= 'A' && *ptr2 <= 'Z' )?
	*ptr2 + ( 'a' - 'A' ) : *ptr2 ;
      if( chara1 != chara2 )
	break ;
      ptr1 ++ ;
      ptr2 ++ ;
    }
    if( *ptr2 == '\0' && *ptr1 == '\0' )
      return node ;
    node = node->next ;
  }
  /* 見付かりませんでしたね。*/
  return NULL ;
}

/*
 * 新しくフォントを獲得する関数。
 */
static struct skkinputManagedFont *fontMgr_AllocNewFont
( Display *disp, unsigned char *name )
{
  struct skkinputManagedFont *node, *pNode ;

  /* 既に存在しているかどうかを確認する。*/
  node = fontMgr_FindManagedFontByName
    ( skkinputManagedFontListTop, name ) ;
  if( node == NULL ){
    node = fontMgr_FindManagedFontByName
      ( skkinputFreeFontListTop, name ) ;
    if( node == NULL ){
      /* 存在していなかったら、メモリを確保する。*/
      if( ( node = fontMgr_AllocManagedFontListNode() ) == NULL )
	return NULL ;
      /* フォントを読み込む。*/
      if( ( node->font = XLoadQueryFont( disp, name ) ) == NULL ){
#ifdef DEBUG
	printf( "Find No Font: \"%s\" --> Error.\n", name ) ;
#endif
	free( node ) ;
	return NULL ;
      }
#ifdef DEBUG
      printf( "Find No Font: \"%s\" --> load.\n", name ) ;
#endif
      /* フォントの名前も登録しておく。*/
      if( ( node->name = malloc( strlen( name ) + 1 ) ) == NULL ){
	XFreeFont( disp, node->font ) ;
	free( node ) ;
	return NULL ;
      }
      strcpy( node->name, name ) ;
      /* リストにつなぐ。*/
      node->next   = skkinputManagedFontListTop ;
      skkinputManagedFontListTop = node ;
#ifdef DEBUG
      fprintf
	( stderr, "----- New Font(Name):%s\n", name ) ;
#endif
    } else {
      if( node == skkinputFreeFontListTop ){
	skkinputFreeFontListTop = node->next ;
      } else {
	for( pNode = skkinputFreeFontListTop ;
	     pNode->next != node ; pNode = pNode->next )
	  ;
	pNode->next = node->next ;
      }
#ifdef DEBUG
      printf( "Find Cached Font: \"%s\"\n", node->name ) ;
#endif
      freeFontListLength -- ;
      /* リストにつなぐ。*/
      node->next = skkinputManagedFontListTop ;
      skkinputManagedFontListTop = node ;
    }
    /* 自分しか参照していないから、カウントは 1 である。*/
    node->refer_count = 1 ;
  } else {
#ifdef DEBUG
    printf( "Find Currently Used Font: \"%s\"\n", node->name ) ;
#endif
    /* 既に存在しているのなら、参照カウントを 1 増やしておく。*/
    node->refer_count ++ ;
  }
  return ( node ) ;
}

/*
 * フォントの名前を確認する関数。
 *----
 * skkinput が必要なフォントかどうかをチェックする関数から呼出される
 * ことになる。
 */
static int fontMgr_compareFontName
( unsigned char *fontName, unsigned char *identifier, int a_not_equal_A )
{
  unsigned char *fptr, *iptr ;

  fptr = fontName ;
  iptr = identifier ;

  while( *iptr != '\0' ){
    switch( *iptr ){
    case '*' :
      if( *fptr == '\0' ){
	/* 相手の文字列は最後まで行ってしまっているので、自分も "*" の
	 * 次の文字は "*" か '\0' でなければならない。*/
	iptr ++ ;
      } else {
	/* iptr と fpt + 1 の比較を行う。
	 * "*-hogehoge" と "abc-hogehoge" の比較だった場合に、
	 * まず、"*-hogehoge" と "bc-hogehoge" の比較を行おうとしてい
	 * るのである。*/
	if( fontMgr_compareFontName( fptr + 1, iptr, a_not_equal_A ) ){
	  /* 一致した場合の処理。つまり、これは全部一致したということに
	   * なるのだから、return True */
	  return True ;
	} else {
	  /* 一致しなかった場合の処理。この場合には、"*" の match すると
	   * 予想した長さが間違っていたと判断する。つまり、先の例だと、
	   * "-hogehoge" と "abc-hogehoge" の比較を行うことになる。*/
	  iptr ++ ;
	}
      }
      break ;
    case '?' :
      /* この場合、相手の任意の一文字と match することになる。*/
      if( *fptr == '\0' )
	return False ;
      iptr ++ ;
      fptr ++ ;
      break ;
    case '\\' :
      /* この場合には、この文字を無視して次の文字の比較に移ることにな
       * る。その次の文字が *, ? であっても、ただの文字として扱う。*/
      iptr ++ ;
      if( *iptr == '\0' )
	return False ;
    default :
      if( *iptr != *fptr ){
	int chara1, chara2 ;
	/* もし大文字小文字の区別をしないのなら、絶対に異なる文字列だ
	 * から処理を抜ける。*/
	if( a_not_equal_A )
	  return False ;
	/* 大文字小文字の区別をしないので、アルファベットならば全て小
	 * 文字に直してから比較し直す。*/
	chara1 = ( *iptr >= 'A' && *iptr <= 'Z' )?
	  *iptr + ( 'a' - 'A' ) : *iptr ;
	chara2 = ( *fptr >= 'A' && *fptr <= 'Z' )?
	  *fptr + ( 'a' - 'A' ) : *fptr ;
	/* 小文字同士の比較で同じですか？ 異なれば、絶対に違う文字列で
	 * す。(違う文字列と定義している、と書く方が正しいかな？ )*/
	if( chara1 != chara2 )
	  return False ;
      }
      /* 二つの文字列は今のところ一致しているので、次の文字を比較しに
       * 行きましょうか。*/
      iptr ++ ;
      fptr ++ ;
      break ;
    }
  }
  return True ;
}

/*
 * skkinput が利用するフォントであるかどうかを判別する関数。
 * ---
 * "jisx0208.1983-0", "iso8859-1", "jisx0201.1976-0" 
 */
static int fontMgr_GetCharsetOfFontName
( unsigned char *fontName, unsigned char *charsetlist )
{
  unsigned char ***setptr, **ptr ;
  int charset_num, ret ;

#ifdef MAJIME
  /* こっちは非常に真面目に比較するバージョン。*/
  ret = False ;
  for( setptr = fontset_identifiers, charset_num = 0 ;
       *setptr != NULL && charset_num < NUMBER_OF_CHARSET ;
       setptr ++, charset_num ++ ){
    charsetlist[ charset_num ] = False ;
    for( ptr = *setptr ; *ptr != NULL ; ptr ++ ){
      if( fontMgr_compareFontName( fontName, *ptr, False ) ){
	charsetlist[ charset_num ] = True ;
	ret = True ;
	break ;
      }
    }
  }
#else
  int length = strlen( fontName ) ;
  int flength ;

#ifdef DEBUG
  printf( "(charset check) \"%s\"\n", fontName ) ;
#endif
  /* こっちは手抜きしまくりのバージョン。先頭は全部 * なんだから、後方
   * 一致で良いじゃんとかいうバージョンなわけです。*/ 
  ret = False ;
  for( setptr = fontset_identifiers, charset_num = 0 ;
       *setptr != NULL && charset_num < NUMBER_OF_CHARSET ;
       setptr ++, charset_num ++ ){
    charsetlist[ charset_num ] = False ;
    for( ptr = *setptr ; *ptr != NULL ; ptr ++ ){
      flength = strlen( 1 + *ptr ) ;
      if( length < flength )
	continue ;
      if( !strcasecmp( fontName + length - flength, 1 + *ptr ) ){
#ifdef DEBUG
	printf( "(%d)[Font] %s\n", charset_num, fontName ) ;
#endif
	charsetlist[ charset_num ] = True ;
	ret = True ;
	break ;
      }
    }
  }
#endif
  return ret ;
}

/*
 * skkinput が利用するフォントであるかどうかを判別する関数。
 * ---
 * "jisx0208.1983-0", "iso8859-1", "jisx0201.1976-0" 
 */
static int fontMgr_GetFontName
( unsigned char *fontName, unsigned char **charsetlist )
{
  unsigned char ***setptr, **ptr, *string ;
  int charset_num, count ;
  int length, flength ;

  count       = 0 ;
  setptr      = fontset_identifiers ;
  charset_num = 0 ;
  length      = strlen( fontName ) ;

  /* charset を順番に見て行く。*/
  while( *setptr != NULL && charset_num < NUMBER_OF_CHARSET ){
    /* 既に確保されているのならば問題無し。*/
    if( charsetlist[ charset_num ] == NULL ){
      /* 確保されていないのなら、それが charset のフォントなのかをチェッ
       * クする。*/
      for( ptr = *setptr ; *ptr != NULL ; ptr ++ ){
	flength = strlen( 1 + *ptr ) ;
	if( length < flength )
	  continue ;
	/* もし該当するフォントが存在したのなら。*/
	if( !strcasecmp( fontName + length - flength, 1 + *ptr ) ){
	  if( ( string = malloc( length + 1 ) ) != NULL ){
	    strcpy( string, fontName ) ;
	    charsetlist[ charset_num ] = string ;
	  }
	  count ++ ;
	  break ;
	}
      }
    } else {
      count ++ ;
    }
    setptr      ++ ;
    charset_num ++ ;
  }
  return count ;
}


/*
 * フォントを解放する。
 */
void fontMgr_FreeFont
( Display *disp, struct skkinputManagedFont *font )
{
  struct skkinputManagedFont *node ;

  if( font == NULL )
    return ;
  
  if( font->refer_count > 1 ){
    /* 他にも参照している人がいるので、消去できない。*/
    font->refer_count -- ;
    return ;
  }
  /* もう参照している人は自分だけなので、消去してしまう。*/
  if( font == skkinputManagedFontListTop ){
    skkinputManagedFontListTop = font->next ;
  } else {
    node = skkinputManagedFontListTop ;
    while( node != NULL ){
      if( node->next == font )
	break ;
      node  = node->next ;
    }
    node->next = font->next ;
  }
  if( freeFontListLength < FONT_CACHE_SIZE ){
    /* フリーリストにつなぎかえる。*/
    font->next              = skkinputFreeFontListTop ;
    skkinputFreeFontListTop = font ;
    freeFontListLength ++ ;
  } else {
    /* まずフォントを解放して。*/
    XFreeFont( disp, font->font ) ;
    /* フォントの名前を解放する。*/
    free( font->name ) ;
    /* ノード自体を解放する。*/
    free( font ) ;
  }
  return ;
}

void fontMgr_PrepareFont
( Display *disp, struct skkinputManagedFont **fontset,
  int charset, unsigned char *exactname )
{
  /* フォントを確保する。*/
  struct skkinputManagedFont *font ;
  /* 既にそこには何かフォントが割り当てられているのであれば、解放する。*/
  if( ( font = fontMgr_AllocNewFont( disp, exactname ) ) != NULL ){
    if( fontset[ charset ] != NULL ){
      fontMgr_FreeFont( disp, fontset[ charset ] ) ;
    }
    fontset[ charset ] = font ;
  }
#ifdef DEBUG
  dump_fontlist() ;
  dump_freefontlist() ;
#endif
  return ;
}

/*
 * fontset を指定した文字列から、font を獲得する関数。
 */
void fontMgr_PrepareFontByName
( Display *disp, struct skkinputManagedFont **fontset,
  unsigned char *string )
{
  unsigned char *fontset_string ;
  unsigned char *charsetlist[ NUMBER_OF_CHARSET ] ;
  char **fontslist ;
  int actual_count, i, j ;

  /* もしフォントを指定した筈の文字列が空ならエラーになる。*/
  if( string == NULL )
    return ;

  /* charset の初期化を行う。*/
  for( i = 0 ; i < NUMBER_OF_CHARSET ; i ++ )
    charsetlist[ i ] = NULL ;

  j = 0 ;
  /* 文字列をチェックして、フォント毎に切り出してみる。*/
  while( *string != '\0' && j < NUMBER_OF_CHARSET ){
    fontset_string = getOneFontsetFromFontsetList( &string ) ;
    if( fontset_string == NULL )
      continue ;
#ifdef DEBUG
    printf( "(%d) \"%s\"\n", strlen( fontset_string ), fontset_string ) ;
#endif
    /* フォントの正確な名前を得る。適当な名前では、とてもじゃないが
     * 判断の基準にはならないから。*/
    fontslist = XListFonts
      ( disp, ( char * )fontset_string,
	MAX_AVAILABLE_FONTS, &actual_count ) ;
    /* そのフォント名に一致するフォントが実在するのか？ */
    if( fontslist != NULL ){
      /* 実在するのなら、その正体を記録する。*/
      for( i = 0 ; i < actual_count ; i ++ ){
#ifdef DEBUG
	printf( "(No. %2d) \"%s\"\n", i, fontslist[ i ] ) ;
#endif
	j = fontMgr_GetFontName( fontslist[ i ], charsetlist ) ;
	if( j >= NUMBER_OF_CHARSET )
	  break ;
      }
      XFreeFontNames( fontslist ) ;
    }
    free( fontset_string ) ;
  }
  /* フォントを確保する。*/
  for( i = 0 ; i < NUMBER_OF_CHARSET ; i ++ ){
    if( charsetlist[ i ] != NULL ){
      fontMgr_PrepareFont( disp, fontset, i, charsetlist[ i ] ) ;
      free( charsetlist[ i ] ) ;
    }
  }
  return ;
}


/*
 * この 2 ってのが嫌な数字だけど…やっぱし #define を使うかな。
 */
void fontMgr_PrepareFontByAtom
( Display *disp, struct skkinputManagedFont **fontset,
  Atom *fontatoms, Cardinal num_of_fonts )
{
  Atom *aptr ;
  unsigned char *fontlistName ;
  unsigned char charsetlist[ NUMBER_OF_CHARSET ] ;
  int i, j ;

  /* 送られて来たフォントさんたちを一人ずつチェックする。*/
  for( i = 0, aptr = fontatoms ; i < num_of_fonts ; i ++, aptr ++ ){
    /* まず、フォントの名前を抜き出す。*/
    if( ( fontlistName = XGetAtomName( disp, *aptr ) ) == NULL )
      continue ;
#ifdef DEBUG
    printf( "(ByAtom) \"%s\"\n", fontlistName ) ;
#endif
    if( fontMgr_GetCharsetOfFontName( fontlistName, charsetlist ) ){
      for( j = 0 ; j < NUMBER_OF_CHARSET ; j ++ ){
	if( !charsetlist[ j ] )
	  continue ;
	fontMgr_PrepareFont
	  ( disp, fontset, j, fontlistName ) ;
      }
    }
    /* XGetAtomName さんは malloc しちゃう人なので、XFree しないと駄目。*/
    XFree( fontlistName ) ;
  }
  return ;
}

int fontMgr_GetFontSetInfo
( struct skkinputManagedFont **fontset, int *ret_height, int *ret_ascent )
{
  int i, max_descent, max_ascent, ret ;

  ret = False ;
  max_descent = max_ascent = 0 ;
  for( i = 0 ; i < NUMBER_OF_CHARSET ; i ++ ){
    if( fontset[ i ] == NULL )
      continue ;
    ret  = True ;
    if( max_descent < fontset[ i ]->font->descent )
      max_descent = fontset[ i ]->font->descent ;
    if( max_ascent < fontset[ i ]->font->ascent )
      max_ascent  = fontset[ i ]->font->ascent ;
  }
  if( ret ){
    *ret_height = max_ascent + max_descent ;
    *ret_ascent = max_ascent ;
  }
  return ret ;
}

static struct skkinputManagedFont *fontMgr_CopyFont
( struct skkinputManagedFont *node )
{
  node->refer_count ++ ;
  return node ;
}

void fontMgr_CopyFontSet
( Display *disp, struct skkinputManagedFont **dest, 
  struct skkinputManagedFont **source )
{
  int i ;

  for( i = 0 ; i < NUMBER_OF_CHARSET ; i ++, dest ++, source ++ ){
    if( *source == NULL )
      continue ;
    /* そこにフォントがあるなら〜♪ 解放しないと不味いよ〜。*/
    if( *dest != NULL ){
      fontMgr_FreeFont( disp, *dest ) ;
    }
    /* 参照カウンタを増やすことなく勝手にコピーすると free する時にはまる。*/
    fontMgr_CopyFont( *source ) ;
    *dest = *source ;
  }
  return ;
}

/*
 * フォントセットは共通である…あって欲しいなということから、一度ここ
 * で確保しておけば良いじゃないかという作戦ッス。
 */
void fontMgr_initDefaultFontSet
( Display *disp, 
  unsigned char *defaultFontSetName,
  unsigned char *defaultMinibufFontSetName )
{
  int i ;
  for( i = 0 ; i < NUMBER_OF_CHARSET ; i ++ ){
    defaultFontSet[ i ] = defaultMinibufFontSet[ i ] = NULL ;
  }
  /* デフォルトのフォントを確保する。*/
  fontMgr_PrepareFontByName
    ( disp, defaultFontSet, defaultFontSetName ) ;
  fontMgr_PrepareFontByName
    ( disp, defaultMinibufFontSet, defaultMinibufFontSetName ) ;
  return ;
}

/*
 * デフォルトのフォントセットを解放する関数ッス。
 */
void fontMgr_closeDefaultFontSet( Display *disp )
{
  int i ;
  for( i = 0 ; i < NUMBER_OF_CHARSET ; i ++ ){
    fontMgr_FreeFont( disp, defaultFontSet[ i ] ) ;
    fontMgr_FreeFont( disp, defaultMinibufFontSet[ i ] ) ;
  }
  return ;
}

void fontMgr_copyDefaultFontSet
( Display *disp, struct skkinputManagedFont **dest )
{
  fontMgr_CopyFontSet( disp, dest, defaultFontSet ) ;
  return ;
}

void fontMgr_copyDefaultMinibufFontSet
( Display *disp, struct skkinputManagedFont **dest )
{
  fontMgr_CopyFontSet( disp, dest, defaultMinibufFontSet ) ;
  return ;
}
