#include "app.h"

#define THREAD_WIN32

/*************************************************************/
/**************** Shared data  *******************************/
/*************************************************************/

typedef struct {
  int wnum;
  int t;
  TRELLIS_ATOM *tlist;
  LOGPROB cm_alpha;
  WORD_INFO *winfo;
} RECOGINFO;

static RECOGINFO rinfo;
static FILE *fp; 
/**
 * TRUE if the string was determined already
 * 
 */
static boolean determined_flag = FALSE;

static float *cm = NULL;
static int cmlen;
static char *buf;


/*************************************************************/
/**************** SDL GUI part *******************************/
/*************************************************************/

#ifdef GUID_DEFINE_HEADER
#include <initguid.h>
#include <dinput.h>
#endif

#include <SDL.h>
#include <SDL_ttf.h>

#define SCREEN_WIDTH  800
//#define SCREEN_HEIGHT 110
#define SCREEN_HEIGHT 480
#define SCREEN_BPP    32
#define FONT_PATH "./data/font.ttf"
#define FONT_SIZE 100
#define WINTITLE "Word Flow"
#define FONT_X 25
#define FONT_Y 25

#define MAXBUFLEN 4096 ///< Maximum line length of a message sent from a client

#include <process.h>
#include <windows.h>

static SDL_Surface *gScreenSurface;
static TTF_Font *Font = NULL;
static int screen_width = SCREEN_WIDTH;
static int screen_height = SCREEN_HEIGHT;
static char *font_path = NULL;
static int font_size = FONT_SIZE;

/** 
 * Free some global variables for SDL.
 * 
 */
static void
GFree()
{
  if (Font) TTF_CloseFont(Font);
}

/** 
 * Application quit function.
 * 
 * @param recog [in] recognition instance.
 */
static void
EndProc(Recog *recog)
{
  /* close thread */
  close_recog_thread(); 
  /* free the whole recognition instance */
  j_recog_free(recog);
  /* free some global variables for SDL */
  GFree();
  /* end procedure for SDL */
  SDL_Quit();
}

/** 
 * Initialize SDL.
 * 
 * 
 * @return 0 on success, or -1 on failure.
 */
static int
GInit(int maxwordlen)
{
  int i;
  int width;

  /* initialize library */
  if (SDL_Init(SDL_INIT_VIDEO) != 0) {
    fprintf(stderr, "Error: failed to initialize SDL library: %s.\n", SDL_GetError());
    return -1;
  }

  /* call clean up at exit */
  atexit(SDL_Quit);

  /* set window title */
  SDL_WM_SetCaption(WINTITLE, NULL);


  printf("width=%d, fontsize=%d\n", screen_width, font_size);

  /* request global software surface */
  gScreenSurface = SDL_SetVideoMode(screen_width, screen_height, SCREEN_BPP, SDL_SWSURFACE /* | SDL_ANYFORMAT | SD_FULLSCREEN */);
  if (gScreenSurface == NULL) {
    fprintf(stderr, "Erorr: failed to set video mode to %dx%dx%d: %s.\n", 
	    screen_width, screen_height, SCREEN_BPP, SDL_GetError());
    exit(1);
    return -1;
  }
  printf("set video mode to %d %d %d bpp\n", screen_width, screen_height, gScreenSurface->format->BitsPerPixel);

  /* initialize ttf font functions */
  if (TTF_Init()) return;

  /* load font */
  Font = TTF_OpenFont(font_path, font_size);

  return 0;
}

/** 
 * Draw function to fill in the total application window.
 * 
 * @param color [in] color code to fill.
 */
static void
GFillScreen(Uint32 color)
{
  SDL_Rect dest;

  dest.x = 0;dest.y = 0;
  dest.w = screen_width;
  dest.h = screen_height;
  SDL_FillRect( gScreenSurface, &dest, color );
  //SDL_FillRect( gScreenSurface, &dest, SDL_MapRGB(gScreenSurface->format, r, g,b));
  
}

/** 
 * Output a word string as a final candidate.
 * 
 * @param recog [in] recognition instance
 * @param word [in] word to be displayed
 */
static void
GProcessOutput(WORD_ID word)
{
  static char buf[MAXBUFLEN];
  SDL_Color col = {70, 255, 120, 0}; /* color of the final candidate string */
  SDL_Rect dest;
  int i;
  SDL_Surface *TextSurface2;
  int t;

  /* get semaphore */

#ifdef CHARACTER_CONVERSION
  /* do character conversion to utf8 */
  charconv(rinfo.winfo->woutput[word], buf, MAXBUFLEN);
#else
  buf[0] = '\0';
  strcpy(buf, rinfo.winfo->woutput[word]);
#endif
 
  fflush(stdout);

  /* write the word string to a text surface */
  //TextSurface2 = TTF_RenderUTF8_Solid( Font, buf, col );
  //TextSurface2 = TTF_RenderUTF8_Shaded( Font, buf, col );
  TextSurface2 = TTF_RenderUTF8_Blended( Font, buf, col );

  /* fill in the whole screen with black */
  GFillScreen(0);

  /* scroll from the bottom to the top */
  for(t=rinfo.t; t >= 0; t -= 30) {
    dest.x = FONT_X;
    dest.y = FONT_Y + t;
    SDL_BlitSurface(TextSurface2, NULL, gScreenSurface, &dest );
    SDL_Flip(gScreenSurface);
    dest.w = screen_width;
    dest.h = font_size + 10;
    SDL_Delay(40);
    SDL_FillRect( gScreenSurface, &dest, SDL_MapRGB(gScreenSurface->format, 0, 0, 0));
  }
  dest.x = FONT_X;
  dest.y = FONT_Y;
  SDL_BlitSurface(TextSurface2, NULL, gScreenSurface, &dest );
  SDL_Flip(gScreenSurface);

  /* free the text surface */
  SDL_FreeSurface(TextSurface2);

  /* release semaphore */
}

/* work area for keeping trail data in GProcessFlow */
static short *dens = NULL;
static WORD_ID didxnum[9];
static short *didx;
static int denslen;

static void
GProcessFlow()
{
  int t;
  int i;

  if (dens) {
    if (denslen != rinfo.wnum) {
      free(dens);
      free(didx);
      dens = NULL;
    }
  }
  if (!dens) {
    denslen = rinfo.wnum;
    dens = (short *)mymalloc(sizeof(short) * denslen);
    didx = (short *)mymalloc(sizeof(short) * denslen * 9);
  }

  /* current time */
  t = rinfo.t - 1;

  if (t == 0) {
    /* first time: clear trail data and fill screen with background color */
    memset(dens, 0, sizeof(short) * denslen);
    GFillScreen(SDL_MapRGB(gScreenSurface->format, 0, 0, 70));
  }

  if (! determined_flag)
  {
    /* if the final word candidate is not determined yet, output word
       trail according to the current (and previous) survived words
       and its confidence score */
    int d, j, k;
    static char inbuf[MAXBUFLEN];
    static char buf[MAXBUFLEN];
    SDL_Color col = {0, 0, 0, 0};
    SDL_Rect dest;
    SDL_Surface *TextSurface;

    /* When a new word appears, its intencity (cm score) will be stored
       to dens[].  The intencity will be aged at the following frames:
       the existing dens[] from the last frame will be decreased frame 
       by frame. */
    /* then, the words with intencity > 0 will be listed according
       to the intencity to didx[0..didxnum[]-1] */
    for(i=0;i<9;i++) didxnum[i] = 0;
    for(i=0;i<denslen;i++) {
      d = cm[i] * 10.0;
      if (d > 9) d = 9;
      if (dens[i] < d) dens[i] = d;
      else if (dens[i] > 0) dens[i]--;
      if (dens[i] > 0) {
	didx[(dens[i]-1) * denslen + didxnum[dens[i]-1]] = i;
	didxnum[dens[i]-1]++;
      }
    }

    /* rwrite words with intencity > 0 with corresponding color intencity
       to a single textsurface, which shows the word trail
       */
    printf("%3d:", t);
    //for(j=0;j<9;j++) {
    for(j=1;j<9;j++) {
      d = j + 1;
      for(k=0;k<didxnum[j];k++) {
	i = didx[j * denslen + k];
#ifdef CHARACTER_CONVERSION
	charconv(rinfo.winfo->woutput[i], buf, MAXBUFLEN);
#else
	strcpy(buf, rinfo.winfo->woutput[i]);
#endif

	col.r = col.g = col.b = (d * 220.0) / 9.0;
	TextSurface = TTF_RenderUTF8_Solid( Font, buf, col );
	dest.x = FONT_X;
	/* the displaying position will be shifted by the time */
	dest.y = FONT_Y + t;
	SDL_BlitSurface(TextSurface, NULL, gScreenSurface, &dest);
	SDL_FreeSurface(TextSurface);
	printf(" %s(%d)", rinfo.winfo->woutput[i], d);
      }
    }

    SDL_Flip(gScreenSurface);

    printf("\n");
  }
  /* release semaphore */
}  

/*************************************************************/
/**************** Start recognition thread *******************/
/*************************************************************/

#ifdef THREAD_WIN32

static HANDLE hThread;
static unsigned dwThreadId;
static Recog *recog_gvar;

unsigned __stdcall recog_thread(void *arg)
{
  Recog *recog;
  
  recog = (Recog *)arg;

  j_recognize_stream(recog);

  return(0);
}

void
start_recog_thread(Recog *recog)
{
  recog_gvar = recog;
  hThread = (HANDLE)_beginthreadex(NULL, 0, recog_thread, (LPVOID)recog_gvar, 0, &dwThreadId);
}

void
close_recog_thread()
{
  CloseHandle(hThread);
}

#endif /* THREAD_WIN32 */


/**********************************************************************/
/************* speech recognition callbacks ***************************/
/**********************************************************************/

/** 
 * Callback function to process final result output, to output the final
 * result if not determined yet.  This will also be called when recognition
 * was failed.
 * 
 * @param recog [in] recognition instance
 * @param dummy [in] dummy argument
 */
static void
process_result(Recog *recog, void *dummy)
{
  int i, n, num;
  WORD_INFO *winfo;
  WORD_ID *seq;
  int seqnum;
  Sentence *s;

  if (recog->result.status < 0) {
    /* recognition result cannot be obtained for the input */
    switch(recog->result.status) {
    case -3:                    /* input rejected by GMM */
      //server_send("%s=gmm\n", MODSTR);
      printf("Rejected by GMM\n");
      break;
    case -2:
      //server_send("%s=shortinput\n", MODSTR);
      printf("Rejected by short input\n");
      break;
    case -1:
      /* ??????? RECOGFAIL */
      ////module_send(module_sd, "<RECOGFAIL/>\n.\n");
      printf("Recognition failed\n");
      break;
    }
    return;
  }

  winfo = recog->model->winfo;

  /* use only 1-best */
  s = &(recog->result.sent[0]);

  /* output log to stdout */
  printf("recognized: %d: %s\n", recog->result.num_frame, winfo->woutput[s->word[0]]);

  /* do nothing if already determined */
  if (determined_flag == TRUE) {
    /* reset the flag for next input */
    determined_flag = FALSE;
    return;
  }

  /* send event */
  /* get semaphore */
  rinfo.wnum = recog->model->winfo->num;
  rinfo.t = recog->pass1.current_frame_num;
  rinfo.tlist = recog->backtrellis->list;
  rinfo.cm_alpha = recog->jconf->annotate.cm_alpha;
  rinfo.winfo = recog->model->winfo;
  /* release semaphore */
  //GProcessOutput(recog, s->word[0]);
  {
    SDL_Event event;
    event.type = SDL_USEREVENT;
    event.user.code = 0;
    event.user.data1 = s->word[0];
    if (SDL_PushEvent(&event) != 0) {
      fprintf(stderr, "Error: failed to push event\n");
    }
  }
}

/** 
 * Callback function called when a word is determinized in the cource of
 * recognition.  This will output the word and set determined_flag;
 * 
 * @param recog [in] recognition instance
 * @param dummy [in] dummy argument
 */
static void
output_determined(Recog *recog, void *dummy)
{
  printf("determined: %d: %s\n", recog->result.num_frame,
	 recog->model->winfo->woutput[recog->result.pass1.word[0]]);
  /* set flag */
  determined_flag = TRUE;
  /* send event */
  /* get semaphore */
  rinfo.wnum = recog->model->winfo->num;
  rinfo.t = recog->pass1.current_frame_num;
  rinfo.tlist = recog->backtrellis->list;
  rinfo.cm_alpha = recog->jconf->annotate.cm_alpha;
  rinfo.winfo = recog->model->winfo;
  /* release semaphore */
  //GProcessOutput(recog, s->word[0]);
  //GProcessOutput(recog, recog->result.pass1.word[0]);
  {
    SDL_Event event;
    event.type = SDL_USEREVENT;
    event.user.code = 0;
    event.user.data1 = recog->result.pass1.word[0];
    if (SDL_PushEvent(&event) != 0) {
      fprintf(stderr, "Error: failed to push event\n");
    }
  }
}

/** 
 * Callback function that will be called periodically while recognizing
 * at a certain frame interval.  This will list up for the word ends
 * survived at the time, compute confidence scores, and output the
 * "word trail" on the window.
 * 
 * @param recog [in] recognition instance
 * @param dummy [in] dummy argument
 */
static void
output_status(Recog *recog, void *dummy)
{
  TRELLIS_ATOM *tre;
  TRELLIS_ATOM *tremax;
  int t;
  int i;
  SDL_Event event;

  /* get semaphore */
  rinfo.wnum = recog->model->winfo->num;
  rinfo.t = recog->pass1.current_frame_num;
  rinfo.tlist = recog->backtrellis->list;
  rinfo.cm_alpha = recog->jconf->annotate.cm_alpha;
  rinfo.winfo = recog->model->winfo;
  if (cm) {			/* memory already assigned */
    if (cmlen != rinfo.wnum) {
      /* clear */
      free(cm);
      free(buf);
      cm = NULL;
    }
  }
  if (!cm) {
    /* assign memory */
    cmlen = rinfo.wnum;
    cm = (float *)mymalloc(sizeof(float) * cmlen);
    buf = (char *)mymalloc(cmlen+1);
  }

  /* current time */
  t = rinfo.t - 1;

  /* compute confidence score for all survived words */
  {
    float sum = 0.0;
    float maxscore = LOG_ZERO;
    int i;

    for(i=0;i<cmlen;i++) cm[i] = 0.0;

    tremax = NULL;
    for (tre = rinfo.tlist; tre && tre->endtime == t; tre = tre->next) {
      if (maxscore < tre->backscore) {
	maxscore = tre->backscore;
	tremax = tre;
      }
    }
    for (tre = rinfo.tlist; tre && tre->endtime == t; tre = tre->next) {
      sum += pow(10, rinfo.cm_alpha * (tre->backscore - maxscore));
    }
    for (tre = rinfo.tlist; tre && tre->endtime == t; tre = tre->next) {
      cm[tre->wid] = pow(10, rinfo.cm_alpha * (tre->backscore - maxscore)) / sum;
    }
  }

#if 0
  {
    int d;
    printf("%3d: ", t);
    buf[0] = buf[cmlen] = '\0';
    for(i=0;i<cmlen;i++) {
      d = cm[i] * 10.0;
      if (d > 9) d = 9;
      if (d > 0) buf[i] = '0' + d;
      else buf[i] = ' ';
    }
    if (tremax) printf("%s| %s\n", buf, rinfo.winfo->woutput[tremax->wid]);
    else printf("%s|\n", buf);
  }
#endif

  /* release semaphore */
  event.type = SDL_USEREVENT;
  event.user.code = 1;
  if (SDL_PushEvent(&event) != 0) {
    fprintf(stderr, "Error: failed to push event\n");
  }
}

/************************************************************************/
/************************************************************************/
/************************************************************************/
int
main(int argc, char *argv[])
{
  char *jconffile, *wordlistfile;
  Recog *recog;
  Jconf *jconf;
  Model *model;
  int frame_interval = 0;

  /* when linked with SDL library, print will not go to tty, instead
     saved to stdout.txt and stderr.txt. */
  /* output system log to a file */
  fp = fopen("log.txt", "w"); jlog_set_output(fp);
  /* inihibit system log output (default: stdout) */
  //jlog_set_output(NULL);

  /* if no option argument, output julius usage and exit */
  if (argc == 1) {
    fprintf(stderr, "Wordflow %s - based on %s rev.%s (%s)\n", WORDFLOW_VERSION, JULIUS_PRODUCTNAME, JULIUS_VERSION, JULIUS_SETUP);
    fprintf(stderr, "Usage: %s [options] wordlistfile\n", argv[0]);
    fprintf(stderr, "Options:\n\t-C jconffile\n\t[-w width]\n\t[-h height]\n\t[-f ttffile]\n\t[-s fontsize]\n\t[-i update_interval_msec]\n");
    return -1;
  }

  /* option parsing */
  {
    int i;
    jconffile = NULL;
    wordlistfile = NULL;
    for(i=1;i<argc;i++) {
      if (argv[i][0] == '-') {
	switch(argv[i][1]) {
	case 'C':
	  if (++i >= argc) {fprintf(stderr, "Error in option\n"); return -1;}
	  jconffile = argv[i];
	  break;
	case 'w':
	  if (++i >= argc) {fprintf(stderr, "Error in option\n"); return -1;}
	  screen_width = atoi(argv[i]);
	  break;
	case 'h':
	  if (++i >= argc) {fprintf(stderr, "Error in option\n"); return -1;}
	  screen_height = atoi(argv[i]);
	  break;
	case 'f':
	  if (++i >= argc) {fprintf(stderr, "Error in option\n"); return -1;}
	  font_path = argv[i];
	  break;
	case 's':
	  if (++i >= argc) {fprintf(stderr, "Error in option\n"); return -1;}
	  font_size = atoi(argv[i]);
	  break;
	case 'i':
	  if (++i >= argc) {fprintf(stderr, "Error in option\n"); return -1;}
	  frame_interval = atoi(argv[i]);
	  break;
	}
      } else {
	wordlistfile = argv[i];
      }
    }
    if (jconffile == NULL) {
      fprintf(stderr, "Error in option\n"); return -1;
    }
    if (wordlistfile == NULL) {
      fprintf(stderr, "Error in option\n"); return -1;
    }
    if (font_path == NULL) font_path = FONT_PATH;
  }

  /* initialize charconv */
  if (charconv_setup_real(
#ifdef USE_WIN32_MULTIBYTE
			  "euc", "utf-8"
#else
			  "euc-jp", "utf8"
#endif
			  ) == FALSE) {
    fprintf(stderr, "Error: character set conversion setup failed\n");
    return -1;
  }

  /* load configurations from arguments or jconf file */
  //   jconf = j_jconf_new(); j_config_load_file(jconf, jconffile);
  // or
  //   jconf = j_config_load_file_new(jconffile);
  if ((jconf = j_config_load_file_new(jconffile)) == NULL) {
    fprintf(stderr, "Try `-help' for more information.\n");
    return -1;
  }

  /* set wordlist file */
  if (multigram_add_prefix_list(wordlistfile, NULL, jconf, LM_DFA_WORD) == FALSE) {
    fprintf(stderr, "Error: failed to set word list\n");
    return -1;
  }

  /* set (force) frame interval if specified */
  if (frame_interval != 0) {
    jconf->output.progout_interval = frame_interval;
  }

  /* create recognition instance from a jconf */
  if ((recog = j_create_instance_from_jconf(jconf)) == NULL) {
    fprintf(stderr, "Error in startup\n");
    return -1;
  }

  /* register result output callback functions */
  callback_add(recog, CALLBACK_RESULT_PASS1_INTERIM, output_status, NULL);
  callback_add(recog, CALLBACK_RESULT_PASS1_DETERMINED, output_determined, NULL);
  callback_add(recog, CALLBACK_RESULT, process_result, NULL);

  /* initialize SDL */
  {
    int l, i, k;
    l = 0;
    for(i=0;i<recog->model->winfo->num;i++) {
      k = strlen(recog->model->winfo->woutput[i]);
      if (l < k) l = k;
    }
    if (GInit(l) != 0) return -1;
  }

  /* initialize and standby audio input device */
  /* for microphone or other threaded input, ad-in thread starts at this time */
  if (j_adin_init(recog) == FALSE) {
    /* error */
    return -1;
  }

  /* output system information */
  j_recog_info(recog);

  /* enter main recognition loop here */
  /* begin A/D input */
  switch(j_open_stream(recog, NULL)) {
  case 0:			/* succeeded */
    break;
  case -1:      		/* error */
    fprintf(stderr, "error in input stream\n");
    return;
  case -2:			/* end of recognition process */
    fprintf(stderr, "failed to begin input stream\n");
    return;
  }

  /* start main recognition loop for the input stream */
  start_recog_thread(recog);

  /* event loop */
  {
    SDL_Event ev;
    SDLKey *key;

    while(SDL_WaitEvent(&ev)) {
      switch(ev.type){
      case SDL_USEREVENT:
	fflush(fp);
	switch(ev.user.code) {
	case 0:			/* result output */
	  GProcessOutput(ev.user.data1);
	  break;
	case 1:
	  GProcessFlow();
	  break;
	}
	break;
      case SDL_QUIT:// ウィンドウの×ボタンが押された時など
	return -1;
	break;
      case SDL_KEYDOWN:// キーボードからの入力があった時
	key=&(ev.key.keysym.sym); // どのキーが押されたかを取得
	if(*key==27){// ESCキー
	  return -1;
	}
      break;
      }
    }
  }

  /* release all */
  EndProc(recog);

  return(0);
}
