/*******************************************************************

  XPUYO   puyo-puyo for X11          (c)1997 CDR/TK  (Tatsuya Kudoh)

  main.c ---- Xt Intrinsics & main routin

  Xt イントリンシックス および メイン処理

*******************************************************************/

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<X11/Intrinsic.h>
#include<X11/StringDefs.h>
#include<X11/Xaw/Command.h>
#include<X11/Xaw/Form.h>
#include<X11/Xaw/Label.h>
#include<X11/Xaw/AsciiText.h>
#include<X11/Shell.h>
#include<X11/xpm.h>
#include"xpuyo.h"
#include"xpuyo_x11.h"
#include"resource.h"

#include"graph/puyo0.xpm"
#include"graph/puyo1.xpm"
#include"graph/puyo2.xpm"
#include"graph/puyo3.xpm"
#include"graph/puyo4.xpm"
#include"graph/puyo5.xpm"
#include"graph/puyo6.xpm"

#include"graph/icon.xbm"
#include"graph/icon_msk.xbm"

#define MD_TITLE    0  /* タイトルモード       */
#define MD_START    1  /* ゲームスタートモード */
#define MD_WAITING  2  /* ぷよ移動待機モード   */
#define MD_MOVING   3  /* ぷよ移動モード       */
#define MD_PAUSE    4  /* ポーズモード         */
#define MD_FALLING  5  /* 全ぷよ落下モード     */
#define MD_KILLING  6  /* ぷよ消去モード       */
#define MD_DEAD     7  /* ゲームオーバーモード */


#define SPEED_FALL  50  /* 全ぷよが落ちる時のインターバル(ミリ秒) */
#define SPEED_BLINK 80  /* 点滅のインターバル                     */
#define SPEED_FAST  15  /* 強制的に落下させる時のインターバル     */
#define SPEED_END 10000 /* ゲームオーバー表示時間                 */

/* ウィジット（クラス) */

static XtAppContext App;   /* アプリケーションコンテクスト */

static Widget
       Top,              /* 最上位                        */
         HiScShell,        /* ハイスコア( トランジェントシェル ) */
           HiScList,         /* ハイスコア・リスト（ラベル） */
         Puyo,             /* 全画面(フォーム)              */
           Title,            /* タイトル（ラベル)             */
           Scr,              /* メイン画面（コア)             */
           PauseLogo,        /* ポーズ表示（ラベル)           */
           GameOver,         /* ゲームオーバー表示(ラベル)    */
           NextBoard,        /* NEXT関係（フォーム)           */
             NextLabel,        /* NEXTの文字（ラベル)           */
             Next,             /* NEXTグラフイック（コア)       */
             ScoreBoard,     /* ユーザー情報（フォーム)       */
               ScoreLabel,     /* スコアの文字(ラベル)          */
               Score,          /* スコア(ラベル)                */
               BlockLabel,     /* 消したぷよの数の文字(ラベル)  */
               Block,          /* 消したぷよの数（ラベル）      */
             Console,        /* 操作パネル(フォーム)          */
               Start,          /* スタートボタン（コマンド）    */
               Pause,          /* ポーズボタン（コマンド)       */
               Quit,           /* 終了ボタン（コマンド)         */
               HiScore,        /* ハイスコアボタン（コマンド）  */
             LvBoard,        /* レベル（フォーム)             */
               LvLabel,        /* 初期レベルの文字(ラベル)       */
               LvDown,         /* 初期レベルダウン(コマンド)     */
               LvUp,           /* 初期レベルアップ               */
               Lv,             /* 初期レベル表示                 */
             Copyright,      /* 著作権表示(ラベル)            */
             Mailto;         /* メール */

static Colormap Cmap;    /* カラーマップ */

static int Interval;     /* タイマー処理のインターバル */

static XtIntervalId IntervalId; /* タイマー処理のＩＤ */
static int IntervalState;       /* タイマー処理中？   */
static int Timi_var;            /* タイマー処理ルーチンで使う。汎用。 */

static int ScoreState = 0;      /* ハイスコアリストが表示されているか？ */

static char Score_list[52*(RECORD_ENTRY+2)+1]="";    /* ハイスコアテキスト */

static char *GraphDir = NULL;  /* グラフィックのあるディレクトリ  */


static void timi( XtPointer c, XtIntervalId id );


/* グローバル変数 */

int Dir_x[4] = {0,1,0,-1};
int Dir_y[4] = {-1,0,1,0};

char *Myname;            /* プログラム名         */
int Sc;                  /* 得点                 */
int Sc_block;            /* 消したぷよの数       */
int Sc_base;             /* 獲得した得点(基本点) */
int Sc_multi;            /* 得点の倍率           */

record_t Record[RECORD_ENTRY];  /* ハイスコアリスト                    */
int Record_entry;               /* ハイスコアリストの数  -1:記録しない */

int Level;               /* レベル                   */
int Level_start = 1;     /* スタート時のレベル       */
int Colors;              /* ぷよの種類の数           */
int Speed;               /* ぷよが落ちるインターバル */

Display *Disp;           /* ディスプレイ構造体 */

Pixmap Graph[7];         /* キャラクタ       */
Pixmap Graph_mask[7];    /* マスク           */
GC Gc_mask[7];           /* マスク処理用ＧＣ */
GC Gc_nomask;            /* マスクなし用ＧＣ */

Pixmap Scr_p;   /* メインの裏画面 */
Pixmap Scr_sp;  /* 点滅用の裏画面 */
Pixmap Next_p;  /* NEXTの裏画面   */
Window Scr_w;   /* メイン画面     */
Window Next_w;  /* NEXT表示画面   */


static int Mode = -1;  /* 現在のモード */



/* 得点表示 */

void put_score( void )
{
  char buf[16];

  sprintf( buf,"%08d",Sc );
  XtVaSetValues( Score,XtNlabel,buf,NULL );

  sprintf( buf,"%08d",Sc_block );
  XtVaSetValues( Block,XtNlabel,buf,NULL );
}



/* 獲得した得点の表示 */

static void put_calc( void )
{
  char buf[16];

  sprintf(buf,"%d*%d",Sc_base,Sc_multi );
  XtVaSetValues( Score,XtNlabel,buf,NULL);
}



/* ハイスコア画面作成 */

static void make_score( void )
{
  int i;
  char *p;

  if( Record_entry == -1 )
    return;

  p = Score_list;

  p += sprintf( p,"RANK  SCORE   BLOCK  NAME\n\
---- -------- ----- -------------------------------\n");

  for( i = 0 ; i < Record_entry ; i++ )
    p += sprintf( p,"%4d %8d %5d %-31.31s\n",i+1,
	    Record[i].score,Record[i].block,Record[i].name);

  p--;
  *p = '\0';

  XtVaSetValues( HiScList,
		XtNstring,Score_list,
		XtNlength,52*(Record_entry+2),
		NULL);
}

/* ハイスコア表示(1)/非表示(0) */


static void list_score( int mode )
{
  if( Record_entry != -1 && mode && !ScoreState ){
    XtPopup( HiScShell,XtGrabNone );
    ScoreState = 1;
  }else if( !mode && ScoreState ){
    XtPopdown( HiScShell );
    ScoreState = 0;
  }
}



/* タイマー中断 */
static void reset_timer( void )
{
  if( IntervalState ){
    XtRemoveTimeOut( IntervalId );
    IntervalState = 0;
  }
}



/* タイマースタート */

static void set_timer( void )
{
  if( IntervalState )
    reset_timer();
  IntervalId = XtAppAddTimeOut( App, Interval,(XtTimerCallbackProc)timi,NULL );
  IntervalState = 1;
}



/* コマンドボタンの状態を変える */

static void set_command( Widget w, Boolean active )
{
  XtVaSetValues( w,XtNsensitive,active,NULL );
}



/* モードチェンジ */

static void change_mode( int mode )
{
  Mode = mode;

 rechange:

  switch( Mode ){

  case MD_TITLE:
    if( Timi_var ){
      XtUnmapWidget( Scr );
      XtMapWidget( GameOver );
      if( save_record() ){
	make_score();
	list_score(1);
      }else if( Record_entry == -1 ){
	list_score(0);
	XtVaSetValues( HiScore,XtNsensitive,FALSE );
      }
      Interval = SPEED_END;
      set_timer();
    }

    set_command( Start,TRUE );
    set_command( Pause,FALSE );
    set_command( LvDown,TRUE );
    set_command( LvUp,TRUE );
    break;

  case MD_START:
    set_command( Start,FALSE );
    set_command( Pause,TRUE );
    set_command( LvDown,FALSE );
    set_command( LvUp,FALSE );
    XtUnmapWidget( GameOver );
    XtMapWidget( Scr );
    Sc = 0;
    Sc_block = 0;
    set_level();
    put_score();
    init_map();
    make_next();
    Mode = MD_WAITING;
    goto rechange;             /* MD_WAITINGへ移行 */

  case MD_WAITING:
    Interval = 200;
    set_timer();
    break;

  case MD_MOVING:
    if( init_move() ){
      Interval = Speed;
      set_timer();
      init_calc();
      Timi_var = 0;
      break;
    }
    Mode = MD_DEAD;
    goto rechange;              /* MD_DEADへ移行 */

  case MD_PAUSE:
    break;                 /* MD_PAUSEは専用ルーチン(pause_game)で処理 */

  case MD_FALLING:
    refresh();
    Interval = SPEED_FALL;
    set_timer();
    break;

  case MD_KILLING:
    save_scr();
    refresh();
    Timi_var = 10;
    Interval = SPEED_BLINK;
    set_timer();
    put_calc();
    break;

  case MD_DEAD:
    Timi_var = 15;
    Interval = SPEED_FALL;
    set_timer();
    break;
  }
}




/* タイマー処理ルーチン */

static void timi( XtPointer c,XtIntervalId id )
{
  IntervalState = 0;

  set_timer();

  switch( Mode ){

  case MD_TITLE:
    reset_timer();
    XtUnmapWidget( GameOver );
    break;

  case MD_WAITING:
    change_mode( MD_MOVING );
    break;

  case MD_MOVING:
    if( !fall_puyo() )
      if( ++Timi_var > 2 ){
	settle_puyo();
	change_mode( MD_FALLING );
      }
    break;

  case MD_FALLING:
    if( fall_all() )
      break;

    if( !kill_puyo() )
      change_mode( MD_WAITING );
    else
      change_mode( MD_KILLING );
    break;

  case MD_KILLING:
    if( --Timi_var )
      blink( Timi_var & 1 );
    else{
      put_score();
      change_mode( MD_FALLING );
    }
    break;

  case MD_DEAD:
    if( --Timi_var )
      dead();
    else{
      Timi_var = 1;
      change_mode( MD_TITLE );
    }
    break;
  }
}



/* Xpmのエラー処理 */

static void xpm_err( int err,char *file )
{
  if( err ){
    if( file == NULL )
      fprintf(stderr,"%s: %s\n",Myname,XpmGetErrorString(err));
    else
      fprintf(stderr,"%s: %s: %s\n",Myname,file,XpmGetErrorString(err));

    exit(1);
  }
}



/* エクスポーズ処理 */

static void expose( Widget w,Pixmap *p,XEvent *e )
{
  XCopyArea(Disp,*p,e->xexpose.window,Gc_nomask,
	    e->xexpose.x,e->xexpose.y,e->xexpose.width,e->xexpose.height,
	    e->xexpose.x,e->xexpose.y);
}



/* ゲーム開始/終了 (アクションハンドラ) */

static void start_game( Widget w,XEvent *e,String *s,Cardinal *num )
{
  if( Mode == MD_TITLE )
    change_mode( MD_START );
}



/* ポーズのトグル (アクションハンドラ)       */
/* PAUSE（ポーズ)/START(解除)/TOGGLE(トグル) */

static void pause_game( Widget w,XEvent *e,String *s,Cardinal *num )
{
  static int mode,timer;

  int state;

  if( Mode < 0 )
    return;

  if( *num == 0 )
    return;

  switch( **s ){
  case 'P':
    if( Mode != MD_PAUSE )
      state = 1;
    else
      return;
    break;

  case 'S':
    if( Mode == MD_PAUSE )
      state = 0;
    else
      return;
    break;

  case 'T':
    if( Mode == MD_PAUSE )
      state = 0;
    else
      state = 1;
    break;

  default:
    return;
  }

  if( state ){
    if( Mode != MD_TITLE ){
      mode = Mode;
      Mode = MD_PAUSE;
      if( IntervalState ){
	reset_timer();
	timer = 1;
      }else
	timer = 0;
      XtUnmapWidget( Scr );
      XtMapWidget( PauseLogo );
    }
  }else{
    XtUnmapWidget( PauseLogo );
    XtMapWidget( Scr );
    Mode = mode;
    if( timer )
      set_timer();
  }
}



/* プログラム終了 (アクションハンドラ) */

static void quit_game( Widget w,XEvent *e,String *s,Cardinal *num )
{
  exit( 0 );
}



/* ハイスコアリスト表示（アクションハンドラ)        */
/* SHOW(表示する)/HIDE(表示しない)/TOGGLE（トグル） */

static void display_score( Widget w,XEvent *e,String *s,Cardinal *num )
{
  if( *num == 0 )
    return;

  switch( **s ){
  case 'S':
    list_score(1);
    break;

  case 'H':
    list_score(0);
    break;

  case 'T':
    list_score(!ScoreState);
    break;

  default:
    return;
  }

}



/* 初期レベル設定(アクションハンドラ) */
/* UP(上げる)/DOWN(下げる)/1-5(設定)  */

static void level( Widget w,XEvent *e,String *s,Cardinal *num )
{
  int lv;
  char buf[2];

  if( Mode != MD_TITLE )
    return;

  if( *num == 0 )
    return;

  switch( **s ){
  case 'U':
    lv = Level_start + 1;
    break;

  case 'D':
    lv = Level_start - 1;
    break;

  default:
    lv = **s - '0';
    break;
  }

  if( lv > 0 && lv < 6 ){
    Level_start = lv;
    sprintf(buf,"%d",Level_start);
    XtVaSetValues( Lv,XtNlabel,buf,NULL);
  }
}



/* ぷよ移動 (アクションハンドラ) */
/* RIGHT(右移動)/LEFT(左移動)    */

static void move( Widget w,XEvent *e,String *s,Cardinal *num )
{
  if( Mode != MD_MOVING )
    return;

  if( *num == 0 )
    return;

  switch( **s ){
  case 'R':
    move_puyo( 1 );
    break;

  case 'L':
    move_puyo( -1 );
    break;

  }
}



/* ぷよ落下 (アクションハンドラ)        */
/* START(開始)/END(終了)/LINE(一段落す) */

static void fall( Widget w,XEvent *e,String *s,Cardinal *num )
{
  if( Mode != MD_MOVING )
    return;

  if( *num == 0 )
    return;

  switch( **s ){
  case 'S':
    Interval = SPEED_FAST;
    set_timer();
    break;

  case 'E':
    Interval = Speed;
    set_timer();
    break;

  case 'L':
    Interval = 0;
    set_timer();
    Interval = Speed;
    break;
  }
}



/* ぷよ回転 (アクションハンドラ) */
/* RIGHT(右回転)/LEFT(左回転)    */

static void rotate( Widget w,XEvent *e,String *s,Cardinal *num )
{
  if( Mode == MD_MOVING ){

    if( *num == 0 )
      return;

    switch( **s ){
    case 'R':
      rotate_puyo( 1 );
      break;

    case 'L':
      rotate_puyo( -1 );
      break;
    }
  }
}



/* アイコンの設定 */

static void set_icon( void )
{
  Pixmap icon,mask;

  icon = XCreateBitmapFromData( Disp,DefaultRootWindow(Disp),icon_bits,
			       icon_width,icon_height );

  mask = XCreateBitmapFromData( Disp,DefaultRootWindow(Disp),icon_msk_bits,
			       icon_msk_width,icon_msk_height );

  XtVaSetValues( Top,XtNiconPixmap,icon,XtNiconMask,mask,NULL);
  XtVaSetValues( HiScShell,XtNiconPixmap,icon,XtNiconMask,mask,NULL);
}



/* ヘルプ */

static void help()
{
  fprintf(stderr,"Usage: %s [options]\n",Myname);
  fprintf(stderr,"\t-toolkitoption ...\n");
  fprintf(stderr,
	  "\t-graphic \"directry\"\tuse your graphic in \"directry\".\n");
  fprintf(stderr,"\t-private\t\tuse private colormap.\n",Myname);
  fprintf(stderr,"\t-help\t\t\tshow this messages.\n");
}



/* コマンドラインオプション解析 */

static void option( int argc, char **argv )
{
  int i;

  for( i = 1 ; i < argc ; i++ ){
    if( strcmp( argv[i],"-graphic" ) == 0 ){
      GraphDir = argv[++i];
      continue;
    }

    if( strcmp( argv[i],"-private" ) == 0 ){
      Cmap =
	XCreateColormap( Disp,DefaultRootWindow(Disp),
			DefaultVisual(Disp,DefaultScreen(Disp)),AllocNone );
      XtVaSetValues( Top,XtNcolormap,Cmap,NULL);
      continue;
    }

    if( strcmp( argv[i],"-help" ) == 0 ){
      help();
      exit(0);
    }
    fprintf(stderr,"%s: Unknown command line option \"%s\".\n",Myname,argv[i]);
    help();
    exit(1);
  }
}  



/* グラフィックの作成／読み込み */

static void make_graphic( void )
{
  int i;
  char fname[256];
  XpmAttributes attr;

  static char **xpmdata[] = {
    puyo0_xpm,puyo1_xpm,puyo2_xpm,puyo3_xpm,puyo4_xpm,puyo5_xpm,puyo6_xpm,
  };

  attr.valuemask = XpmColormap;
  attr.colormap = Cmap;

  if( GraphDir == NULL ){
    for( i = 0 ; i < 7 ; i++ )
      xpm_err
	( XpmCreatePixmapFromData
	 ( Disp,DefaultRootWindow(Disp),xpmdata[i],
	  &Graph[i],&Graph_mask[i],&attr ),NULL);

  }else{
    attr.valuemask |= XpmSize;
    for( i = 0 ; i < 7 ; i++ ){
      sprintf( fname,"%s/puyo%d.xpm",GraphDir,i);
      xpm_err
	( XpmReadFileToPixmap
	 ( Disp,DefaultRootWindow(Disp),fname,
	  &Graph[i],&Graph_mask[i],&attr ),fname);
      if( attr.width < 32 ||
	 ( i == 0 && attr.height < 64 ) || ( i && attr.height < 512 )){
	fprintf(stderr,"%s: %s: xpm size is too small.\n",Myname,fname);
	exit(1);
      }
    }
  }
  for( i = 0 ; i < 7 ; i++ ){
    Gc_mask[i] = XCreateGC( Disp,DefaultRootWindow(Disp),0,NULL );
    XSetClipMask( Disp,Gc_mask[i],Graph_mask[i] );
  }
}



/* Xlib関係の前処理 */

static int init_graph( void )
{
  make_graphic();

  Gc_nomask = XCreateGC( Disp,DefaultRootWindow(Disp),0,NULL );

  Scr_p = XCreatePixmap( Disp,DefaultRootWindow(Disp),256,416,
			DefaultDepth(Disp,DefaultScreen(Disp)) );

  Scr_sp = XCreatePixmap( Disp,DefaultRootWindow(Disp),256,416,
			 DefaultDepth(Disp,DefaultScreen(Disp)) );

  Next_p = XCreatePixmap( Disp,DefaultRootWindow(Disp),96,128,
			 DefaultDepth(Disp,DefaultScreen(Disp)) );

  clear_screen();
} 


int main(int argc,char **argv)
{
  uid_t uid;

  static XtActionsRec a_table[] = {
    {"start",  (XtActionProc)start_game},
    {"pause", (XtActionProc)pause_game},
    {"quit",  (XtActionProc)quit_game},
    {"level", (XtActionProc)level},
    {"move",  (XtActionProc)move},
    {"fall",  (XtActionProc)fall},
    {"rotate",(XtActionProc)rotate},
    {"score", (XtActionProc)display_score},
  };

  if( argv[0] == NULL )
    Myname = "xpuyo";
  else
    Myname = argv[0];

  uid = geteuid();
  seteuid(getuid());

  XtSetLanguageProc(NULL,NULL,NULL);


  srnd();

  Top = XtVaAppInitialize(&App,"XPuyo",NULL,0,
			&argc,argv,DefaultResources,NULL);

  Disp = XtDisplay( Top );

  Cmap = DefaultColormap(Disp,DefaultScreen(Disp));

  option( argc,argv );

  seteuid(uid);

  HiScShell = XtVaCreatePopupShell("hiscShell",
				   transientShellWidgetClass,Top,NULL);

  HiScList = XtVaCreateManagedWidget("hiscList",asciiTextWidgetClass,HiScShell,
				     XtNdisplayCaret,FALSE,
				     XtNwrap,XawtextWrapNever,
				     XtNeditType,XawtextRead,
				     XtNuseStringInPlace,TRUE,
				     XtNstring,Score_list,
				     XtNlength,0,
				     NULL);

  Puyo = XtVaCreateManagedWidget("puyo",formWidgetClass,Top,NULL);


  Title = XtVaCreateManagedWidget("title",labelWidgetClass,Puyo,
				  NULL);

  Scr = XtVaCreateManagedWidget("scr",widgetClass,Puyo,
				XtNwidth,256,
				XtNheight,416,
				XtNmappedWhenManaged,FALSE,
				NULL);

  PauseLogo = XtVaCreateManagedWidget("pauseLogo",labelWidgetClass,Puyo,
				      XtNmappedWhenManaged,FALSE,
				      NULL);

  GameOver = XtVaCreateManagedWidget("gameover",labelWidgetClass,Puyo,
				      XtNmappedWhenManaged,FALSE,
				      NULL);

  NextBoard = XtVaCreateManagedWidget("nextBoard",formWidgetClass,Puyo,
				      NULL);

  NextLabel = XtVaCreateManagedWidget("nextLabel",labelWidgetClass,NextBoard,
				      NULL);

  Next = XtVaCreateManagedWidget("next",widgetClass,NextBoard,
				 XtNwidth,96,
				 XtNheight,128,
				 NULL);

  ScoreBoard = XtVaCreateManagedWidget("scoreBoard",formWidgetClass,Puyo,
				       NULL);

  ScoreLabel =XtVaCreateManagedWidget("scoreLabel",labelWidgetClass,ScoreBoard,
				       NULL);

  Score = XtVaCreateManagedWidget("score",labelWidgetClass,ScoreBoard,
				  XtNlabel,"00000000",
				  NULL);

  BlockLabel =XtVaCreateManagedWidget("blockLabel",labelWidgetClass,ScoreBoard,
				      NULL);

  Block = XtVaCreateManagedWidget("block",labelWidgetClass,ScoreBoard,
				  XtNlabel,"00000000",
				  NULL);

  Console = XtVaCreateManagedWidget("console",formWidgetClass,Puyo,
				    NULL);

  Start = XtVaCreateManagedWidget("start",commandWidgetClass,Console,
				  NULL);

  Pause = XtVaCreateManagedWidget("pause",commandWidgetClass,Console,
				  NULL);

  Quit = XtVaCreateManagedWidget("quit",commandWidgetClass,Console,
				 NULL);

  HiScore = XtVaCreateManagedWidget("hiscore",commandWidgetClass,Console,
				    NULL);

  LvBoard = XtVaCreateManagedWidget("lvBoard",formWidgetClass,Puyo,
				    NULL);

  LvLabel = XtVaCreateManagedWidget("lvLabel",labelWidgetClass,LvBoard,
				  NULL);

  LvDown = XtVaCreateManagedWidget("lvDown",commandWidgetClass,LvBoard,
				   NULL);

  LvUp = XtVaCreateManagedWidget("lvUp",commandWidgetClass,LvBoard,
				 NULL);

  Lv = XtVaCreateManagedWidget("lv",labelWidgetClass,LvBoard,
			       XtNlabel,"1",
			       NULL);
  
  Copyright = XtVaCreateManagedWidget("copyright",labelWidgetClass,Puyo,
				      NULL);
  
  Mailto = XtVaCreateManagedWidget("mailto",labelWidgetClass,Puyo,
				   NULL);

  init_graph();

  if( init_record() )
    XtVaSetValues( HiScore,XtNsensitive,FALSE,NULL );
  else
    make_score();

  XtAddEventHandler( Scr,ExposureMask,FALSE,(XtEventHandler)expose,&Scr_p );
  XtAddEventHandler( Next,ExposureMask,FALSE,(XtEventHandler)expose,&Next_p );

  XtAppAddActions( App,a_table,XtNumber(a_table));

  set_icon();

  XtRealizeWidget(Top);

  Scr_w = XtWindow( Scr );
  Next_w = XtWindow( Next );

  Timi_var = 0;
  change_mode( MD_TITLE );

  XtAppMainLoop(App);

  return 0;
}
