!===============================================================================
!     FKeys.h                         An Inform 6 library extension
!                                     Copyright 2000-2014 by David Fillmore
!     Release 1.1                
! This work is licensed under the Creative Commons Attribution-NonCommercial    
! 4.0 International License.                                                    
! To view a copy of this license, visit                                         
! http://creativecommons.org/licenses/by-nc/4.0/ 
! or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain 
! View, California, 94041, USA. 
!===============================================================================
! This library file should enable the use of Function Keys as command shortcuts
! in your games.
! To make it work, Replace Keyboard and KeyboardPrimitive before inclusion of
! Parser.h, include this file after Parser.h, and put the line 'SetupFKeys();' 
! somewhere in the initialise routine.
!
! The arrays containing the commands that will be used when various shortcut
! keys are pressed are set up in the SetupFKeys routine. There is a default
! SetupFKeys routine, but you can provide your own if you define it before
! including this file.
! 
! It is highly recommended that you use exclusively lowercase characters
! if you define your own arrays, as some interpreters will break slightly when 
! uppercase characters are used.
!
! If you have any problems, questions, or suggesctions, please e-mail me at
! marvin@frobnitz.co.uk
!
!===============================================================================

#Ifndef TARGET_ZCODE;
Message fatalerror "*** The FKeys library extension only works with Z-Machine games ***";
#Endif; 

#Iffalse (LIBRARY_VERSION == 612);
#Iffalse (LIBRARY_VERSION == 611);
Message fatalerror "*** The FKeys library extension requires version 6/11 or version 6/12 of the standard Inform library ***";
#Endif; 
#Endif; 

Array F1 -> 36;
Array F2 -> 36;
Array F3 -> 36;
Array F4 -> 36;
Array F5 -> 36;
Array F6 -> 36;
Array F7 -> 36;
Array F8 -> 36;
Array F9 -> 36;
Array F10 -> 36;
Array F11 -> 36;
Array F12 -> 36;

Array NP1 -> 36;
Array NP2 -> 36;
Array NP3 -> 36;
Array NP4 -> 36;
Array NP6 -> 36;
Array NP7 -> 36;
Array NP8 -> 36;
Array NP9 -> 36;


#ifndef WORDSIZE;
Constant TARGET_ZCODE;
Constant WORDSIZE 2;
#endif;

ZCharacter terminating 133 134 135 136 137 138 139 140 141 142 143 144 146 147 148 149 151 152 153 154;

Global termchar;

#ifndef SetupFKeys;
[ SetupFKeys;
  F1->0=4;
  F1->1='l';
  F1->2='o';
  F1->3='o';
  F1->4='k';
  F1->5=124;

  F2->0=9;
  F2->1='i';
  F2->2='n';
  F2->3='v';
  F2->4='e';
  F2->5='n';
  F2->6='t';
  F2->7='o';
  F2->8='r';
  F2->9='y';
  F2->10=124;

  F3->0=5;
  F3->1='o';
  F3->2='p';
  F3->3='e';
  F3->4='n';
  F3->5=' ';

  F4->0=8;
  F4->1='e';
  F4->2='x';
  F4->3='a';
  F4->4='m';
  F4->5='i';
  F4->6='n';
  F4->7='e';
  F4->8=' ';

  F5->0=5;
  F5->1='t';
  F5->2='a';
  F5->3='k';
  F5->4='e';
  F5->5=' ';

  F6->0=5;
  F6->1='d';
  F6->2='r';
  F6->3='o';
  F6->4='p';
  F6->5=' ';

  F7->0=7;
  F7->1='a';
  F7->2='t';
  F7->3='t';
  F7->4='a';
  F7->5='c';
  F7->6='k';
  F7->7=' ';

  F8->0=5;
  F8->1='a';
  F8->2='g';
  F8->3='a';
  F8->4='i';
  F8->5='n';
  F8->6=124;

  F9->0=4;
  F9->1='u';
  F9->2='n';
  F9->3='d';
  F9->4='o';
  F9->5=124;

  F10->0=5;
  F10->1='o';
  F10->2='o';
  F10->3='p';
  F10->4='s';
  F10->5=' ';

  F11->0=4;
  F11->1='a';
  F11->2='s';
  F11->3='k';
  F11->4=' ';

  F12->0=5;
  F12->1='t';
  F12->2='e';
  F12->3='l';
  F12->4='l';
  F12->5=' ';

  NP1->0=9;
  NP1->1='s';
  NP1->2='o';
  NP1->3='u';
  NP1->4='t';
  NP1->5='h';
  NP1->6='w';
  NP1->7='e';
  NP1->8='s';
  NP1->9='t';
  NP1->10=124;

  NP2->0=5;
  NP2->1='s';
  NP2->2='o';
  NP2->3='u';
  NP2->4='t';
  NP2->5='h';
  NP2->6=124;

  NP3->0=9;
  NP3->1='s';
  NP3->2='o';
  NP3->3='u';
  NP3->4='t';
  NP3->5='h';
  NP3->6='e';
  NP3->7='a';
  NP3->8='s';
  NP3->9='t';
  NP3->10=124;

  NP4->0=4;
  NP4->1='w';
  NP4->2='e';
  NP4->3='s';
  NP4->4='t';
  NP4->5=124;

  NP6->0=4;
  NP6->1='e';
  NP6->2='a';
  NP6->3='s';
  NP6->4='t';
  NP6->5=124;

  NP7->0=9;
  NP7->1='n';
  NP7->2='o';
  NP7->3='r';
  NP7->4='t';
  NP7->5='h';
  NP7->6='w';
  NP7->7='e';
  NP7->8='s';
  NP7->9='t';
  NP7->10=124;

  NP8->0=5;
  NP8->1='n';
  NP8->2='o';
  NP8->3='r';
  NP8->4='t';
  NP8->5='h';
  NP8->6=124;

  NP9->0=9;
  NP9->1='n';
  NP9->2='o';
  NP9->3='r';
  NP9->4='t';
  NP9->5='h';
  NP9->6='e';
  NP9->7='a';
  NP9->8='s';
  NP9->9='t';
  NP9->10=124;

];
#endif;

[ PressedKey a_buffer charnum x y z fkey max_chars current_chars total_chars;
  switch(termchar)
  { 133 to 154: switch(termchar)
                { 133: fkey=F1;
                  134: fkey=F2;
                  135: fkey=F3;
                  136: fkey=F4;
                  137: fkey=F5;
                  138: fkey=F6;
                  139: fkey=F7;
                  140: fkey=F8;
                  141: fkey=F9;
                  142: fkey=F10;
                  143: fkey=F11;
                  144: fkey=F12;
                  146: fkey=NP1;
                  147: fkey=NP2;
                  148: fkey=NP3;
                  149: fkey=NP4;
                  151: fkey=NP6;
                  152: fkey=NP7;
                  153: fkey=NP8;
                  154: fkey=NP9;
                }
   
                max_chars = a_buffer->0;
                current_chars = a_buffer->1;
                total_chars = (fkey->0) + current_chars;
                if (total_chars > max_chars-1) ! the new characters will not fit on the line
                { @sound_effect 2;
                  rtrue;
                }
                a_buffer->1=total_chars;

                x=(fkey->0)+1;
                @buffer_mode false;
                for (y=1:y<x:y++)
                { a_buffer->(y+1+current_chars)=(fkey->y);
                  print (char) a_buffer->(y+1+current_chars);
                  if (a_buffer->(y+1+current_chars) > $40 && a_buffer->(y+1+current_chars) < $5b)
                  { a_buffer->(y+1+current_chars)=(a_buffer->(y+1+current_chars)+$20);
                  }
                }


                if (fkey->y==124)
                { print "^";
                  z=0;
                }
                else
                z=1;
                @buffer_mode true;
  }
  charnum=a_buffer->1;
  return z;
];


[ KeyboardPrimitive  a_buffer a_table;
    a_buffer->0 = 120;
    @aread a_buffer a_table -> termchar;

    #Iftrue (#version_number == 6);
    @output_stream -1;
    @loadb a_buffer 1 -> sp;
    @add a_buffer 2 -> sp;
    @print_table sp sp;
    new_line;
    @output_stream 1;
    #Endif;
];

#Iftrue (LIBRARY_VERSION == 612);
[ Keyboard  a_buffer a_table  nw i w w2 x1 x2 a;
  DisplayStatus();
  .FreshInput;

  ! Save the start of the buffer, in case "oops" needs to restore it
  ! to the previous time's buffer

  for (i=0 : i<OOPS_WORKSPACE_LEN : i++) oops_workspace->i = a_buffer->i;

  ! In case of an array entry corruption that shouldn't happen, but would be
  ! disastrous if it did:

  #Ifdef TARGET_ZCODE;
  a_buffer->0 = INPUT_BUFFER_LEN - WORDSIZE;
  a_table->0  = MAX_BUFFER_WORDS; ! Allow to split input into this many words
  #Endif; ! TARGET_

  ! Print the prompt, and read in the words and dictionary addresses

  L__M(##Prompt);
  if (AfterPrompt() == 0) LibraryExtensions.RunAll(ext_afterprompt);
  a_buffer->1=0;
  .xyzzy;
  #IFV5; DrawStatusLine(); #ENDIF;
  KeyboardPrimitive(a_buffer, a_table);
  if (termchar~=13 or 10)
  { if (PressedKey(a_buffer)==1)
    { jump xyzzy;
    }
    
    @tokenise a_buffer a_table;
  }
    
   nw = NumberWords(a_table);

  ! If the line was blank, get a fresh line
  if (nw == 0) {
      L__M(##Miscellany, 10);
      jump FreshInput;
  }

  ! Unless the opening word was "oops", return
  ! Conveniently, a_table-->1 is the first word in both ZCODE and GLULX.

  w = a_table-->1;
  if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) jump DoOops;

  if (a_buffer->WORDSIZE == COMMENT_CHARACTER) {
      #Ifdef TARGET_ZCODE;
      if ((HDR_GAMEFLAGS-->0) & $0001 || xcommsdir)
                                         L__M(##Miscellany, 54);
      else                               L__M(##Miscellany, 55);
      #Ifnot; ! TARGET_GLULX
      if (gg_scriptstr || gg_commandstr) L__M(##Miscellany, 54);
      else                               L__M(##Miscellany, 55);
      #Endif; ! TARGET_

      jump FreshInput;
  }

  #IfV5;
  ! Undo handling

  if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
      if (turns == START_MOVE) {
          L__M(##Miscellany, 11);
          jump FreshInput;
      }
      if (undo_flag == 0) {
          L__M(##Miscellany, 6);
          jump FreshInput;
      }
      if (undo_flag == 1) jump UndoFailed;
      #Ifdef TARGET_ZCODE;
      @restore_undo i;
      #Ifnot; ! TARGET_GLULX
      @restoreundo i;
      i = (~~i);
      #Endif; ! TARGET_
      if (i == 0) {
        .UndoFailed;
          L__M(##Miscellany, 7);
      }
      jump FreshInput;
   }
    #Ifdef TARGET_ZCODE;
    @save_undo i;
    #Ifnot; ! TARGET_GLULX
    @saveundo i;
    if (i == -1) {
        GGRecoverObjects();
        i = 2;
    }
    else  i = (~~i);
    #Endif; ! TARGET_
    just_undone = 0;
    undo_flag = 2;
    if (i == -1) undo_flag = 0;
    if (i == 0) undo_flag = 1;
    if (i == 2) {
        RestoreColours();
        #Ifdef TARGET_ZCODE;
        style bold;
        #Ifnot; ! TARGET_GLULX
        glk($0086, 4); ! set subheader style
        #Endif; ! TARGET_
        print (name) location, "^";
        #Ifdef TARGET_ZCODE;
        style roman;
        #Ifnot; ! TARGET_GLULX
        glk($0086, 0); ! set normal style
        #Endif; ! TARGET_
        L__M(##Miscellany, 13);
        just_undone = 1;
        jump FreshInput;
    }
    #Endif; ! V5

    return nw;

  .DoOops;
    if (oops_from == 0) {
        L__M(##Miscellany, 14);
        jump FreshInput;
    }
    if (nw == 1) {
        L__M(##Miscellany, 15);
        jump FreshInput;
    }
    if (nw > 2) {
        L__M(##Miscellany, 16);
        jump FreshInput;
    }

    ! So now we know: there was a previous mistake, and the player has
    ! attempted to correct a single word of it.

    for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
    #Ifdef TARGET_ZCODE;
    x1 = a_table->9;  ! Start of word following "oops"
    x2 = a_table->8;  ! Length of word following "oops"
    #Ifnot; ! TARGET_GLULX
    x1 = a_table-->6; ! Start of word following "oops"
    x2 = a_table-->5; ! Length of word following "oops"
    #Endif; ! TARGET_

    ! Repair the buffer to the text that was in it before the "oops"
    ! was typed:

    for (i=0 : i < OOPS_WORKSPACE_LEN : i++) a_buffer->i = oops_workspace->i;
    Tokenise__(a_buffer, a_table);

    ! Work out the position in the buffer of the word to be corrected:

    #Ifdef TARGET_ZCODE;
    w = a_table->(4*oops_from + 1); ! Start of word to go
    w2 = a_table->(4*oops_from);    ! Length of word to go
    #Ifnot; ! TARGET_GLULX
    w = a_table-->(3*oops_from);      ! Start of word to go
    w2 = a_table-->(3*oops_from - 1); ! Length of word to go
    #Endif; ! TARGET_

    ! Write spaces over the word to be corrected:

    for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';

    if (w2 < x2) {
        ! If the replacement is longer than the original, move up...
        for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
            a_buffer->i = a_buffer->(i-x2+w2);

        ! ...increasing buffer size accordingly.
        SetKeyBufLength(GetKeyBufLength(a_buffer) + (x2-w2), a_buffer);
    }

    ! Write the correction in:

    for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);

    Tokenise__(a_buffer, a_table);
    nw=NumberWords(a_table);

    return nw;
]; ! end of Keyboard
#Endif; 

#Iftrue (LIBRARY_VERSION == 611);
[ Keyboard  a_buffer a_table  nw i w w2 x1 x2;
    DisplayStatus();

  .FreshInput;

    ! Save the start of the buffer, in case "oops" needs to restore it
    ! to the previous time's buffer

    for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;

    ! In case of an array entry corruption that shouldn't happen, but would be
    ! disastrous if it did:

    #Ifdef TARGET_ZCODE;
    a_buffer->0 = INPUT_BUFFER_LEN;
    a_table->0 = 15;  ! Allow to split input into this many words
    #Endif; ! TARGET_

    ! Print the prompt, and read in the words and dictionary addresses

    L__M(##Prompt);
    AfterPrompt();
    a_buffer->1=0;
    .xyzzy;
    #IFV5; DrawStatusLine(); #ENDIF;
    KeyboardPrimitive(a_buffer, a_table);
    if (termchar~=13 or 10)
    { if (PressedKey(a_buffer)==1)
      { jump xyzzy;
      }
    
      @tokenise a_buffer a_table;
    }

    #Ifdef TARGET_ZCODE;
    nw = a_table->1;
    #Ifnot; ! TARGET_GLULX
    nw = a_table-->0;
    #Endif; ! TARGET_

    ! If the line was blank, get a fresh line
    if (nw == 0) {
        L__M(##Miscellany, 10);
        jump FreshInput;
    }

    ! Unless the opening word was "oops", return
    ! Conveniently, a_table-->1 is the first word in both ZCODE and GLULX.

    w = a_table-->1;
    if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) jump DoOops;

    if (a_buffer->WORDSIZE == COMMENT_CHARACTER) {
        #Ifdef TARGET_ZCODE;
        if ((HDR_GAMEFLAGS-->0) & 1 || xcommsdir)
                                           L__M(##Miscellany, 54);
        else                               L__M(##Miscellany, 55);
        #Ifnot; ! TARGET_GLULX
        if (gg_scriptstr || gg_commandstr) L__M(##Miscellany, 54);
        else                               L__M(##Miscellany, 55);
        #Endif; ! TARGET_

        jump FreshInput;
    }

    #IfV5;
    ! Undo handling

    if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
        if (turns == START_MOVE) {
            L__M(##Miscellany, 11);
            jump FreshInput;
        }
        if (undo_flag == 0) {
            L__M(##Miscellany, 6);
            jump FreshInput;
        }
        if (undo_flag == 1) jump UndoFailed;
        #Ifdef TARGET_ZCODE;
        @restore_undo i;
        #Ifnot; ! TARGET_GLULX
        @restoreundo i;
        i = (~~i);
        #Endif; ! TARGET_
        if (i == 0) {
          .UndoFailed;
            L__M(##Miscellany, 7);
        }
        jump FreshInput;
    }
    #Ifdef TARGET_ZCODE;
    @save_undo i;
    #Ifnot; ! TARGET_GLULX
    @saveundo i;
    if (i == -1) {
        GGRecoverObjects();
        i = 2;
    }
    else  i = (~~i);
    #Endif; ! TARGET_
    just_undone = 0;
    undo_flag = 2;
    if (i == -1) undo_flag = 0;
    if (i == 0) undo_flag = 1;
    if (i == 2) {
        RestoreColours();
        #Ifdef TARGET_ZCODE;
        style bold;
        #Ifnot; ! TARGET_GLULX
        glk($0086, 4); ! set subheader style
        #Endif; ! TARGET_
        print (name) location, "^";
        #Ifdef TARGET_ZCODE;
        style roman;
        #Ifnot; ! TARGET_GLULX
        glk($0086, 0); ! set normal style
        #Endif; ! TARGET_
        L__M(##Miscellany, 13);
        just_undone = 1;
        jump FreshInput;
    }
    #Endif; ! V5

    return nw;

  .DoOops;
    if (oops_from == 0) {
        L__M(##Miscellany, 14);
        jump FreshInput;
    }
    if (nw == 1) {
        L__M(##Miscellany, 15);
        jump FreshInput;
    }
    if (nw > 2) {
        L__M(##Miscellany, 16);
        jump FreshInput;
    }

    ! So now we know: there was a previous mistake, and the player has
    ! attempted to correct a single word of it.

    for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
    #Ifdef TARGET_ZCODE;
    x1 = a_table->9;  ! Start of word following "oops"
    x2 = a_table->8;  ! Length of word following "oops"
    #Ifnot; ! TARGET_GLULX
    x1 = a_table-->6; ! Start of word following "oops"
    x2 = a_table-->5; ! Length of word following "oops"
    #Endif; ! TARGET_

    ! Repair the buffer to the text that was in it before the "oops"
    ! was typed:

    for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i;
    Tokenise__(a_buffer,a_table);

    ! Work out the position in the buffer of the word to be corrected:

    #Ifdef TARGET_ZCODE;
    w = a_table->(4*oops_from + 1); ! Start of word to go
    w2 = a_table->(4*oops_from);    ! Length of word to go
    #Ifnot; ! TARGET_GLULX
    w = a_table-->(3*oops_from);      ! Start of word to go
    w2 = a_table-->(3*oops_from - 1); ! Length of word to go
    #Endif; ! TARGET_

    ! Write spaces over the word to be corrected:

    for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';

    if (w2 < x2) {
        ! If the replacement is longer than the original, move up...
        for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
            a_buffer->i = a_buffer->(i-x2+w2);

        ! ...increasing buffer size accordingly.
        #Ifdef TARGET_ZCODE;
        a_buffer->1 = (a_buffer->1) + (x2-w2);
        #Ifnot; ! TARGET_GLULX
        a_buffer-->0 = (a_buffer-->0) + (x2-w2);
        #Endif; ! TARGET_
    }

    ! Write the correction in:

    for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);

    Tokenise__(a_buffer, a_table);
    #Ifdef TARGET_ZCODE;
    nw = a_table->1;
    #Ifnot; ! TARGET_GLULX
    nw = a_table-->0;
    #Endif; ! TARGET_

    return nw;
]; ! end of Keyboard
#Endif; 