#include "glk.h"

/* model.c: Model program for Glk API, version 0.5.
   Designed by Andrew Plotkin <[email protected]>
   http://www.eblong.com/zarf/glk/index.html
   This program is in the public domain.
*/

/* This is a simple model of a text adventure which uses the Glk API.
   It shows how to input a line of text, display results, maintain a
   status window, write to a transcript file, and so on. */

/* This is the cleanest possible form of a Glk program. It includes only
   "glk.h", and doesn't call any functions outside Glk at all. We even
   define our own str_eq() and str_len(), rather than relying on the
   standard libraries. */

/* We also define our own TRUE and FALSE and NULL. */
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef NULL
#define NULL 0
#endif

/* The story, status, and quote windows. */
static winid_t mainwin = NULL;
static winid_t statuswin = NULL;
static winid_t quotewin = NULL;

/* A file reference for the transcript file. */
static frefid_t scriptref = NULL;
/* A stream for the transcript file, when it's open. */
static strid_t scriptstr = NULL;

/* Your location. This determines what appears in the status line. */
static int current_room;

/* A flag indicating whether you should look around. */
static int need_look;

/* Forward declarations */
void glk_main(void);

static void draw_statuswin(void);
static int yes_or_no(void);

static int str_eq(char *s1, char *s2);
static int str_len(char *s1);

static void verb_help(void);
static void verb_jump(void);
static void verb_yada(void);
static void verb_quote(void);
static void verb_move(void);
static void verb_quit(void);
static void verb_script(void);
static void verb_unscript(void);
static void verb_save(void);
static void verb_restore(void);

/* The glk_main() function is called by the Glk system; it's the main entry
   point for your program. */
void glk_main(void)
{
   /* Open the main window. */
   mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
   if (!mainwin) {
       /* It's possible that the main window failed to open. There's
           nothing we can do without it, so exit. */
       return;
   }

   /* Set the current output stream to print to it. */
   glk_set_window(mainwin);

   /* Open a second window: a text grid, above the main window, three lines
       high. It is possible that this will fail also, but we accept that. */
   statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
       3, wintype_TextGrid, 0);

   /* The third window, quotewin, isn't opened immediately. We'll do
       that in verb_quote(). */

   glk_put_string("Model Glk Program\nAn Interactive Model Glk Program\n");
   glk_put_string("By Andrew Plotkin.\nRelease 7.\n");
   glk_put_string("Type \"help\" for a list of commands.\n");

   current_room = 0; /* Set initial location. */
   need_look = TRUE;

   while (1) {
       char commandbuf[256];
       char *cx, *cmd;
       int gotline, len;
       event_t ev;

       draw_statuswin();

       if (need_look) {
           need_look = FALSE;
           glk_put_string("\n");
           glk_set_style(style_Subheader);
           if (current_room == 0)
               glk_put_string("The Room\n");
           else
               glk_put_string("A Different Room\n");
           glk_set_style(style_Normal);
           glk_put_string("You're in a room of some sort.\n");
       }

       glk_put_string("\n>");
       /* We request up to 255 characters. The buffer can hold 256, but we
           are going to stick a null character at the end, so we have to
           leave room for that. Note that the Glk library does *not*
           put on that null character. */
       glk_request_line_event(mainwin, commandbuf, 255, 0);

       gotline = FALSE;
       while (!gotline) {

           /* Grab an event. */
           glk_select(&ev);

           switch (ev.type) {

               case evtype_LineInput:
                   if (ev.win == mainwin) {
                       gotline = TRUE;
                       /* Really the event can *only* be from mainwin,
                           because we never request line input from the
                           status window. But we do a paranoia test,
                           because commandbuf is only filled if the line
                           event comes from the mainwin request. If the
                           line event comes from anywhere else, we ignore
                           it. */
                   }
                   break;

               case evtype_Arrange:
                   /* Windows have changed size, so we have to redraw the
                       status window. */
                   draw_statuswin();
                   break;
           }
       }

       /* commandbuf now contains a line of input from the main window.
           You would now run your parser and do something with it. */

       /* First, if there's a blockquote window open, let's close it.
           This ensures that quotes remain visible for exactly one
           command. */
       if (quotewin) {
           glk_window_close(quotewin, NULL);
           quotewin = 0;
       }

       /* The line we have received in commandbuf is not null-terminated.
           We handle that first. */
       len = ev.val1; /* Will be between 0 and 255, inclusive. */
       commandbuf[len] = '\0';

       /* Then squash to lower-case. */
       for (cx = commandbuf; *cx; cx++) {
           *cx = glk_char_to_lower(*cx);
       }

       /* Then trim whitespace before and after. */

       for (cx = commandbuf; *cx == ' '; cx++) { };

       cmd = cx;

       for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { };
       *(cx+1) = '\0';

       /* cmd now points to a nice null-terminated string. We'll do the
           simplest possible parsing. */
       if (str_eq(cmd, "")) {
           glk_put_string("Excuse me?\n");
       }
       else if (str_eq(cmd, "help")) {
           verb_help();
       }
       else if (str_eq(cmd, "move")) {
           verb_move();
       }
       else if (str_eq(cmd, "jump")) {
           verb_jump();
       }
       else if (str_eq(cmd, "yada")) {
           verb_yada();
       }
       else if (str_eq(cmd, "quote")) {
           verb_quote();
       }
       else if (str_eq(cmd, "quit")) {
           verb_quit();
       }
       else if (str_eq(cmd, "save")) {
           verb_save();
       }
       else if (str_eq(cmd, "restore")) {
           verb_restore();
       }
       else if (str_eq(cmd, "script")) {
           verb_script();
       }
       else if (str_eq(cmd, "unscript")) {
           verb_unscript();
       }
       else {
           glk_put_string("I don't understand the command \"");
           glk_put_string(cmd);
           glk_put_string("\".\n");
       }
   }
}

static void draw_statuswin(void)
{
   char *roomname;
   glui32 width, height;

   if (!statuswin) {
       /* It is possible that the window was not successfully
           created. If that's the case, don't try to draw it. */
       return;
   }

   if (current_room == 0)
       roomname = "The Room";
   else
       roomname = "A Different Room";

   glk_set_window(statuswin);
   glk_window_clear(statuswin);

   glk_window_get_size(statuswin, &width, &height);

   /* Print the room name, centered. */
   glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1);
   glk_put_string(roomname);

   /* Draw a decorative compass rose in the upper right. */
   glk_window_move_cursor(statuswin, width - 3, 0);
   glk_put_string("\\|/");
   glk_window_move_cursor(statuswin, width - 3, 1);
   glk_put_string("-*-");
   glk_window_move_cursor(statuswin, width - 3, 2);
   glk_put_string("/|\\");

   glk_set_window(mainwin);
}

static int yes_or_no(void)
{
   char commandbuf[256];
   char *cx;
   int gotline, len;
   event_t ev;

   draw_statuswin();

   /* This loop is identical to the main command loop in glk_main(). */

   while (1) {
       glk_request_line_event(mainwin, commandbuf, 255, 0);

       gotline = FALSE;
       while (!gotline) {

           glk_select(&ev);

           switch (ev.type) {
               case evtype_LineInput:
                   if (ev.win == mainwin) {
                       gotline = TRUE;
                   }
                   break;

               case evtype_Arrange:
                   draw_statuswin();
                   break;
           }
       }

       len = ev.val1;
       commandbuf[len] = '\0';
       for (cx = commandbuf; *cx == ' '; cx++) { };

       if (*cx == 'y' || *cx == 'Y')
           return TRUE;
       if (*cx == 'n' || *cx == 'N')
           return FALSE;

       glk_put_string("Please enter \"yes\" or \"no\": ");
   }

}

static void verb_help(void)
{
   glk_put_string("This model only understands the following commands:\n");
   glk_put_string("HELP: Display this list.\n");
   glk_put_string("JUMP: A verb which just prints some text.\n");
   glk_put_string("YADA: A verb which prints a very long stream of text.\n");
   glk_put_string("MOVE: A verb which prints some text, and also changes the status line display.\n");
   glk_put_string("QUOTE: A verb which displays a block quote in a temporary third window.\n");
   glk_put_string("SCRIPT: Turn on transcripting, so that output will be echoed to a text file.\n");
   glk_put_string("UNSCRIPT: Turn off transcripting.\n");
   glk_put_string("SAVE: Write fake data to a save file.\n");
   glk_put_string("RESTORE: Read it back in.\n");
   glk_put_string("QUIT: Quit and exit.\n");
}

static void verb_jump(void)
{
   glk_put_string("You jump on the fruit, spotlessly.\n");
}

static void verb_yada(void)
{
   /* This is a goofy (and overly ornate) way to print a long paragraph.
       It just shows off line wrapping in the Glk implementation. */
   #define NUMWORDS (13)
   static char *wordcaplist[NUMWORDS] = {
       "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
           "Ha", "Ni", "Na"
   };
   static char *wordlist[NUMWORDS] = {
       "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
           "gazoon", "ting", "floo", "zonk", "loof", "lob",
   };
   static int wcount1 = 0;
   static int wcount2 = 0;
   static int wstep = 1;
   static int jx = 0;
   int ix;
   int first = TRUE;

   for (ix=0; ix<85; ix++) {
       if (ix > 0) {
           glk_put_string(" ");
       }

       if (first) {
           glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
           first = FALSE;
       }

       glk_put_string(wordlist[jx]);
       jx = (jx + wstep) % NUMWORDS;
       wcount1++;
       if (wcount1 >= NUMWORDS) {
           wcount1 = 0;
           wstep++;
           wcount2++;
           if (wcount2 >= NUMWORDS-2) {
               wcount2 = 0;
               wstep = 1;
           }
       }

       if ((ix % 17) == 16) {
           glk_put_string(".");
           first = TRUE;
       }
   }

   glk_put_char('\n');
}

static void verb_quote(void)
{
   glk_put_string("Someone quotes some poetry.\n");

   /* Open a third window, or clear it if it's already open. Actually,
       since quotewin is closed right after line input, we know it
       can't be open. But better safe, etc. */
   if (!quotewin) {
       /* A five-line window above the main window, fixed size. */
       quotewin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
           5, wintype_TextBuffer, 0);
       if (!quotewin) {
           /* It's possible the quotewin couldn't be opened. In that
               case, just give up. */
           return;
       }
   }
   else {
       glk_window_clear(quotewin);
   }

   /* Print some quote. */
   glk_set_window(quotewin);
   glk_set_style(style_BlockQuote);
   glk_put_string("Tomorrow probably never rose or set\n"
       "Or went out and bought cheese, or anything like that\n"
       "And anyway, what light through yonder quote box breaks\n"
       "Handle to my hand?\n");
   glk_put_string("              -- Fred\n");

   glk_set_window(mainwin);
}

static void verb_move(void)
{
   current_room = (current_room+1) % 2;
   need_look = TRUE;

   glk_put_string("You walk for a while.\n");
}

static void verb_quit(void)
{
   glk_put_string("Are you sure you want to quit? ");
   if (yes_or_no()) {
       glk_put_string("Thanks for playing.\n");
       glk_exit();
       /* glk_exit() actually stops the process; it does not return. */
   }
}

static void verb_script(void)
{
   if (scriptstr) {
       glk_put_string("Scripting is already on.\n");
       return;
   }

   /* If we've turned on scripting before, use the same file reference;
       otherwise, prompt the player for a file. */
   if (!scriptref) {
       scriptref = glk_fileref_create_by_prompt(
           fileusage_Transcript | fileusage_TextMode,
           filemode_WriteAppend, 0);
       if (!scriptref) {
           glk_put_string("Unable to place script file.\n");
           return;
       }
   }

   /* Open the file. */
   scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0);
   if (!scriptstr) {
       glk_put_string("Unable to write to script file.\n");
       return;
   }
   glk_put_string("Scripting on.\n");
   glk_window_set_echo_stream(mainwin, scriptstr);
   glk_put_string_stream(scriptstr,
       "This is the beginning of a transcript.\n");
}

static void verb_unscript(void)
{
   if (!scriptstr) {
       glk_put_string("Scripting is already off.\n");
       return;
   }

   /* Close the file. */
   glk_put_string_stream(scriptstr,
       "This is the end of a transcript.\n\n");
   glk_stream_close(scriptstr, NULL);
   glk_put_string("Scripting off.\n");
   scriptstr = NULL;
}

static void verb_save(void)
{
   int ix;
   frefid_t saveref;
   strid_t savestr;

   saveref = glk_fileref_create_by_prompt(
       fileusage_SavedGame | fileusage_BinaryMode,
       filemode_Write, 0);
   if (!saveref) {
       glk_put_string("Unable to place save file.\n");
       return;
   }

   savestr = glk_stream_open_file(saveref, filemode_Write, 0);
   if (!savestr) {
       glk_put_string("Unable to write to save file.\n");
       glk_fileref_destroy(saveref);
       return;
   }

   glk_fileref_destroy(saveref); /* We're done with the file ref now. */

   /* Write some binary data. */
   for (ix=0; ix<256; ix++) {
       glk_put_char_stream(savestr, (unsigned char)ix);
   }

   glk_stream_close(savestr, NULL);

   glk_put_string("Game saved.\n");
}

static void verb_restore(void)
{
   int ix;
   int err;
   glui32 ch;
   frefid_t saveref;
   strid_t savestr;

   saveref = glk_fileref_create_by_prompt(
       fileusage_SavedGame | fileusage_BinaryMode,
       filemode_Read, 0);
   if (!saveref) {
       glk_put_string("Unable to find save file.\n");
       return;
   }

   savestr = glk_stream_open_file(saveref, filemode_Read, 0);
   if (!savestr) {
       glk_put_string("Unable to read from save file.\n");
       glk_fileref_destroy(saveref);
       return;
   }

   glk_fileref_destroy(saveref); /* We're done with the file ref now. */

   /* Read some binary data. */
   err = FALSE;

   for (ix=0; ix<256; ix++) {
       ch = glk_get_char_stream(savestr);
       if (ch == (glui32)(-1)) {
           glk_put_string("Unexpected end of file.\n");
           err = TRUE;
           break;
       }
       if (ch != (glui32)ix) {
           glk_put_string("This does not appear to be a valid saved game.\n");
           err = TRUE;
           break;
       }
   }

   glk_stream_close(savestr, NULL);

   if (err) {
       glk_put_string("Failed.\n");
       return;
   }

   glk_put_string("Game restored.\n");
}

/* simple string length test */
static int str_len(char *s1)
{
   int len;
   for (len = 0; *s1; s1++)
       len++;
   return len;
}

/* simple string comparison test */
static int str_eq(char *s1, char *s2)
{
   for (; *s1 && *s2; s1++, s2++) {
       if (*s1 != *s2)
           return FALSE;
   }

   if (*s1 || *s2)
       return FALSE;
   else
       return TRUE;
}