/* main.c: Glulxe top-level code.
    Designed by Andrew Plotkin <erkyrath@eblong.com>
    http://eblong.com/zarf/glulx/index.html
*/

#include "glk.h"
#include "glulxe.h"

int vm_exited_cleanly = TRUE;
strid_t gamefile = NULL; /* The stream containing the Glulx file. */
glui32 gamefile_start = 0; /* The position within the stream. (This will not 
    be zero if the Glulx file is a chunk inside a Blorb archive.) */
glui32 gamefile_len = 0; /* The length within the stream. */
char *init_err = NULL;
char *init_err2 = NULL;

glui32 init_rng_seed = 0;

/* The library_start_hook is called at the beginning of glk_main. This
   is not normally necessary -- the library can do all its setup work
   before calling glk_main -- but iosglk has some weird cases which
   require it. */
static void (*library_start_hook)(void) = NULL;
/* The library_autorestore_hook is called right after the VM's initial
   setup. This is an appropriate time to autorestore an initial game
   state, if the library has that capability. (Currently, only iosglk
   and remglk do.) */
static void (*library_autorestore_hook)(void) = NULL;

static winid_t get_error_win(void);
static void stream_hexnum(glsi32 val);

/* glk_main():
   The top-level routine. This does everything, and consequently is
   very simple. 
*/
void glk_main()
{
  vm_exited_cleanly = FALSE;
  
  if (library_start_hook)
    library_start_hook();
  
  if (init_err) {
    fatal_error_2(init_err, init_err2);
    return;
  }

  if (!is_gamefile_valid()) {
    /* The fatal error has already been displayed. */
    return;
  }

  glulx_setrandom(init_rng_seed);
#ifdef FLOAT_SUPPORT
  if (!init_float()) {
    return;
  }
#endif /* FLOAT_SUPPORT */
  if (!init_dispatch()) {
    return;
  }
  if (!init_profile()) {
    return;
  }

  setup_vm();
  if (library_autorestore_hook)
    library_autorestore_hook();
  execute_loop();
  finalize_vm();

  gamefile = NULL;
  gamefile_start = 0;
  gamefile_len = 0;
  init_err = NULL;
  vm_exited_cleanly = TRUE;
  
  profile_quit();
  glk_exit();
}

void set_library_start_hook(void (*func)(void))
{
  library_start_hook = func;
}

void set_library_autorestore_hook(void (*func)(void))
{
  library_autorestore_hook = func;
}

/* get_error_win():
   Return a window in which to display errors. The first time this is called,
   it creates a new window; after that it returns the window it first
   created.
*/
static winid_t get_error_win()
{
  static winid_t errorwin = NULL;

  if (!errorwin) {
    winid_t rootwin = glk_window_get_root();
    if (!rootwin) {
      errorwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
    }
    else {
      errorwin = glk_window_open(rootwin, winmethod_Below | winmethod_Fixed, 
        3, wintype_TextBuffer, 0);
    }
    if (!errorwin)
      errorwin = rootwin;
  }

  return errorwin;
}

/* fatal_error_handler():
   Display an error in the error window, and then exit.
*/
void fatal_error_handler(char *str, char *arg, int useval, glsi32 val)
{
  winid_t win;

  /* If the debugger is compiled in, send the error message to the debug
     console. This may also block for debug commands, depending on 
     preferences. */
  debugger_handle_crash(str);

  win = get_error_win();
  if (win) {
    glk_set_window(win);
    glk_put_string("Glulxe fatal error: ");
    glk_put_string(str);
    if (arg || useval) {
      glk_put_string(" (");
      if (arg)
        glk_put_string(arg);
      if (arg && useval)
        glk_put_string(" ");
      if (useval)
        stream_hexnum(val);
      glk_put_string(")");
    }
    glk_put_string("\n");
  }
  glk_exit();
}

/* nonfatal_warning_handler():
   Display a warning in the error window, and then continue.
*/
void nonfatal_warning_handler(char *str, char *arg, int useval, glsi32 val)
{
  winid_t win = get_error_win();
  if (win) {
    strid_t oldstr = glk_stream_get_current();
    glk_set_window(win);
    glk_put_string("Glulxe warning: ");
    glk_put_string(str);
    if (arg || useval) {
      glk_put_string(" (");
      if (arg)
        glk_put_string(arg);
      if (arg && useval)
        glk_put_string(" ");
      if (useval)
        stream_hexnum(val);
      glk_put_string(")");
    }
    glk_put_string("\n");
    glk_stream_set_current(oldstr);
  }
}

/* stream_hexnum():
   Write a signed integer to the current Glk output stream.
*/
static void stream_hexnum(glsi32 val)
{
  char buf[16];
  glui32 ival;
  int ix;

  if (val == 0) {
    glk_put_char('0');
    return;
  }

  if (val < 0) {
    glk_put_char('-');
    ival = -val;
  }
  else {
    ival = val;
  }

  ix = 0;
  while (ival != 0) {
    buf[ix] = (ival % 16) + '0';
    if (buf[ix] > '9')
      buf[ix] += ('A' - ('9' + 1));
    ix++;
    ival /= 16;
  }

  while (ix) {
    ix--;
    glk_put_char(buf[ix]);
  }
}

