!Various generic handy stuff. Include just after Parser and Verblib

System_file;

Include "newflags";

#IFNDEF WORDSIZE;
#IFNDEF GLULX;
Constant WORDSIZE = 2;
#ENDIF;
#IFDEF GLULX;
Constant WORDSIZE = 4; !TODO - is this right?
#ENDIF;
#ENDIF;

!***Attributes

Attribute examined;

!***Classes

#IFNDEF AP;
Class MyObject
with description [;
if (self provides desc1) 
	{if (self has examined && self provides desc2)
			PrintOrRun(self,desc2);
		else {PrintOrRun(self,desc1); give self examined;} }
else "No description.";],
!add_to_scope [obj; objectloop (obj ofclass Detail) if (obj.detail_of == self) AddToScope(obj);],
has concealed
;
#ENDIF;

#IFDEF AP;
Class MyObject
class ApkeObject,
with description [;
if (self provides desc1) 
	{if (self has examined && self provides desc2)
			PrintOrRun(self,desc2);
		else {PrintOrRun(self,desc1); give self examined;} }
else "No description.";],
!add_to_scope [obj; objectloop (obj ofclass Detail) if (obj.detail_of == self) AddToScope(obj);],
has concealed
;
#ENDIF;

!Give the object a .detail method, which should be an array of the form
!OLD - name1_1 name1_2 0 desc1 name 2_1 0 desc2 name3_1 name3_2 name3_3 0 desc3 etc.
!NEW - name1_1 name1_2 0 action1 desc1_1 action2 desc1_2 0 name 2_1 0 action_1 desc2_1 0 name3_1 name3_2 name3_3 0 action_1 desc3_1 etc.
Class DetailedObject
class MyObject
with parse_name [n length w i which j matched; 
	if (~~self provides detail) return -1;
	self.parsed_detail = 0;
	while (TestInPropArray(self, name, w=NextWord())) n++;
	length = (self.#detail)/WORDSIZE;
	do {
		which++; !goes from 1 up
		while (self.&detail-->(i++)) 
			if (self.&detail-->(i-1) == w) self.parsed_detail = which;
		while (i<=length && self.&detail-->(i++)) {}; !Skip past string
	}
	until (self.parsed_detail || i >= length);
	if (~~self.parsed_detail) return n;
	n++;
	do { w = NextWord();
		i = 0;
		matched = 0;
		for (j=1 : j<which : j++) {while(self.&detail-->(i++)) {} while (i<=length && self.&detail-->(i++)) {}}
		while (self.&detail-->(i++)) 
			if (self.&detail-->(i-1) == w) {
				n++; matched = 1;
			}
	}
	until (matched == 0);
	if (w == 'of' or 'in' or 'on' or 'within' or 'upon') {
		matched=0;
		while (TestInPropArray(self, name, NextWord())) {matched=1; n++;}
		if (matched) n++;
	}
	return n;
],
parsed_detail,
!before [i j it; Examine : if (self.parsed_detail) {
!		for (j=1 : j<=self.parsed_detail : j++) {while(self.&detail-->(i++)) {}}
!		it = self.&detail-->i;
!		!Can be a string to printf or a property of the object to printf or run !TODO - check there's never an overlap in string and property numbers
!		if (it ofclass String) {printf(it); new_line; rtrue;}
!		PrintfOrRun(self, it); rtrue;
!		}
!	}
!], 
before [i j it; 
	if (self.parsed_detail) {
		for (j=1 : j<=self.parsed_detail : j++) {while(self.&detail-->(i++)) {}}
		while (i<=self.#detail/WORDSIZE && self.&detail-->i) {
			if (self.&detail-->i == action) {
				it = self.&detail-->(i+1);
				!Can be a string to printf or a property of the object to printf or run !TODO - check there's never an overlap in string and property numbers
				if (it ofclass String) {printf(it); new_line; rtrue;}
				PrintfOrRun(self, it); rtrue;
			}
			i=i+2;
		}
	}
],
;

Class Place
class MyObject
with before [; LookDir : if (noun==d_obj) <<Examine Floor>>;],
has light
;

MyObject Floor "ground"
with description "The ground here is not particularly note-worthy.",
react_before [; PutOn : if (second==self) <<Drop noun>>;],
react_after [; Drop : if (noun in self) move noun to parent(self);],
name 'floor' 'ground',
found_in [; if (location ofclass Place) rtrue;],
has static supporter enterable
;

Class NoCompassPlace
class MyObject !ApkeObject
with
	n_to [;PrintOrRun(self,nocompass); rtrue;],
	s_to [;PrintOrRun(self,nocompass); rtrue;],
	e_to [;PrintOrRun(self,nocompass); rtrue;],
	w_to [;PrintOrRun(self,nocompass); rtrue;],
	ne_to [;PrintOrRun(self,nocompass); rtrue;],
	sw_to [;PrintOrRun(self,nocompass); rtrue;],
	se_to [;PrintOrRun(self,nocompass); rtrue;],
	nw_to [;PrintOrRun(self,nocompass); rtrue;],
	nocompass "You don't have a compass to hand.",
has light
;

Place DummyPlace
with desc1, desc2,
;

Class Distant
with cantreach [; print (The) self;
			if (self has pluralname) print " are ";
				else print " is ";
			"too far away for that.";
			],
	before [; Examine :
			default: return self.cantreach();
		],
;

!Details can be examined individually, but all other actions will be redirected to the parent object.
!Class Detail
!with before [obj newnoun newsecond; Examine:
		!default : if (noun ~= self && second ~= self) rtrue;
			!obj = self.detail_of; 
			!print "[", (the) obj, "]^";
			!newnoun = noun; newsecond = second;
			!if (newnoun == self) newnoun = obj;
			!if (newsecond == self) newsecond = obj;
		!return InformLibrary.begin_action(action, newnoun, newsecond, 1);
	!],
!;

!***Routines

!For use as a second return value. Or whatever.
Global Misc;

[Char c;
print (char) c-32;
];

[dummytrue;
rtrue;
];

[dummyfalse;
rfalse;
];

!So printf doesn't give errors
#IFNDEF FIXED_POINT;
[ fp;
];
#ENDIF;

Array printed_text --> 1025;
Array printf_vals--> 6;
[ printf format p1 p2 p3 p4 p5 p6   ;
  printed_text-->0 = 1024; OpenBuffer(printed_text); 
  print (string) format; CloseBuffer();
return printf_array(printed_text, p1, p2, p3, p4, p5, p6);
];

[ printf_array the_array p1 p2 p3 p4 p5 p6  pc j k tmp;
  printf_vals-->0 = p1; printf_vals-->1 = p2; printf_vals-->2 = p3;
  printf_vals-->3 = p4; printf_vals-->4 = p5; printf_vals-->5 = p6;
  j=the_array-->0;
  if (j==0) rfalse;

!***Do printf(text, -1) to lop off the last character

  for (k=2:k<j+2 - (p1 == -1):k++)
  {   if (the_array->k == '%' && k<j+2)
      {   switch(the_array->(++k))
	  {	'%': print "%";
		'b': style bold;
		'c': print (char) printf_vals-->pc++;
		'd': print printf_vals-->pc++;
		'e': print (number) printf_vals-->pc++;
		'f': print (fp) printf_vals-->pc++; !Fixed Point
		'i': style underline; !(Because it comes out italic)
		'n': print (name) printf_vals-->pc++;
		'r': style roman;
		's': print printf_vals-->pc++;
		'u': style underline;
		'w': WaitKey();
		'!': @erase_window 0;
		'3': @set_colour 3 0;
		'4': @set_colour 4 0;
		'5': @set_colour 5 0;
		'6': @set_colour 6 0;
		'7': @set_colour 7 0;
		'8': @set_colour 8 0;
		'9': @set_colour 9 0;
		'*': style reverse;
		'+': pc++;
		'-': pc--;
		'{': tmp = the_array->++k - 48;
			if (tmp<1 || tmp>6) { 
				tmp = 1; k--;
			}
			if (~~printf_vals-->(tmp - 1)) {
				while (the_array->++k ~= '|') print (char) the_array->k;
				while (the_array->++k ~= '}') {}
			}
			else {
				while (the_array->++k ~= '|') {}
				while (the_array->++k ~= '}') print (char) the_array->k;
			} !Usage - %{1texta|textb}, prints texta if given 0 as first argument else textb. If don't give a valid number, 1 is assumed.
              default: print "<** Unknown printf escape **>";
          }
      }
      else print (char) the_array->k;
  }
!print "^";
rtrue;
];

[PrintfOrRun o prop p1 p2 p3 p4;
if (o.prop ofclass routine) return o.prop(p1, p2, p3, p4);
printf(o.prop, p1, p2, p3, p4);
rtrue;
];

[Flip o p;
o.p=(1-o.p);
];

[Waitkey text a;
if (text~=0) print "^", (string) text;
@read_char 1 200 dummyfalse a;
return a;
];

[Wait time a;
@read_char 1 time dummytrue a;
return a;
];


[Englishnumbercaps n;
if (n<0 || n>10) return -1000;
switch (n)
	{0: print "Zero";
	 1: print "One";
	 2: print "Two";
	 3: print "Three";
	 4: print "Four";
	 5: print "Five";
	 6: print "Six";
	 7: print "Seven";
	 8: print "Eight";
	 9: print "Nine";
	 10: print "Ten";
	}
rtrue;
];

[ReadNum min max r;
if (min>max) {print "^ERROR - Invalid arguments given to ReadNum^"; rfalse;}
.reread;
print "^>>";
@read_char 1 0 0 r;
r=r-48;
if (r>=min && r<=max) return r;
else {print "^Please type a number between ", min, " and ", max, ".^";
	jump reread;
	}
];

Array ConvText-->9;
Array ConvVal-->9;
[ConvLoad text val;
(ConvText-->0)++;
ConvText-->(ConvText-->0)=text;
ConvVal-->(ConvText-->0)=val;
];

[ConvDo i n;
for (i=1 : i <= ConvText-->0 : i++)
printf("^[%d] %s", i, ConvText-->i);
n = ReadNum(1, ConvText-->0);
ConvText-->0 = 0;
print "^^";
Misc = ConvText-->n;
return ConvVal-->n;
];

[Quote text;
if (~~text) rfalse;
print "~"; printf(text); print"~";
];

[PrintOrRunThis this;
switch(metaclass(this)) 
	{String : print (string) this; rtrue;
	Routine : return this();
	default : print "Error - PrintOrRunThis was passed something it 				shouldn't have been";
	}
];

[PrintfOrRunThis this;
switch(metaclass(this)) 
	{String : printf(this); rtrue;
	Routine : return this();
	default : print "Error - PrintfOrRunThis was passed something it shouldn't have been";
	}
];

[ReturnOrRun obj prop p1 p2 p3 p4;
return (ReturnOrRunThis(obj.prop, p1, p2, p3, p4));
];

[ReturnOrRunThis this p1 p2 p3 p4;
if (this ofclass Routine) return this(p1, p2, p3, p4);
return this;
];

[TestInPropArray obj prop matchto i; !see if matchto appears in property array
for (i=0 : i < (obj.#prop)/2 : i++)
	if (obj.&prop-->i == matchto) rtrue;
rfalse;
];

[FlagWasOff flag temp;
temp = FlagOff(flag);
SetFlag(flag);
return temp;
];

[ Hadnt object attribute temp;
temp = (~~(object has attribute));
give object attribute;
return temp; 
];

!To stop errors
Object StupidObject
with detail,
;

!Buffer handling (yoinked from Platypus)
Default MAXIMUM_OPEN_BUFFERS 5;

Array buffer_addresses  table MAXIMUM_OPEN_BUFFERS;
#ifndef TARGET_GLULX;
Array buffer_offsets    table MAXIMUM_OPEN_BUFFERS;
Array buffer_saved_data table MAXIMUM_OPEN_BUFFERS;
#endif;
#ifdef TARGET_GLULX;
Global current_buffer;
#endif;
Global buffers_open;
#ifndef TARGET_GLULX;

[ OpenBuffer buff;

    if (buffers_open >= MAXIMUM_OPEN_BUFFERS)
    {   #ifdef DEBUG;
            print "***Error: cannot open buffer at ",buff, " because
                there are already ",buffers_open," open,
                and the maximum is ",MAXIMUM_OPEN_BUFFERS,".^";
        #endif;
        rfalse;
    }

    if (buffers_open) Close__Buffer();
    buffers_open++;
    Open__Buffer(buff, 0, 0);
];

[ CloseBuffer     baddr boffset bdata;

    if (buffers_open == 0) rfalse;
    Close__Buffer();
    buffers_open--;

! Was there another open? If so, reopen it:
    if (buffers_open == 0) rfalse;

    baddr = buffer_addresses-->buffers_open;
    boffset = buffer_offsets-->buffers_open;
    bdata = (baddr + boffset)-->0;
    Open__Buffer(baddr, boffset, bdata);
];

[ Open__Buffer baddr boffset bdata     ;

    buffer_addresses-->buffers_open = baddr;
    buffer_offsets -->buffers_open = boffset;
    buffer_saved_data -->buffers_open = bdata;

    baddr = baddr + boffset;
    @output_stream 3 baddr;
];

[ Close__Buffer      baddr boffset bdata;

    @output_stream -3;
    baddr = buffer_addresses --> buffers_open;
    boffset = buffer_offsets --> buffers_open;
    bdata = buffer_saved_data --> buffers_open;

    if (boffset)
    {   baddr-->0 = baddr-->0 + (baddr + boffset)-->0;
        (baddr + boffset)-->0 = bdata;
    }
    buffer_offsets --> buffers_open = baddr-->0;
];

#endif;
#ifdef TARGET_GLULX;

[ OpenBuffer buff;

    if (buffers_open >= MAXIMUM_OPEN_BUFFERS)
    {   #ifdef DEBUG;
            print "***Error: cannot open buffer at ",buff, " because
                there are already ",buffers_open," open,
                and the maximum is ",MAXIMUM_OPEN_BUFFERS,".^";
        #endif;
        rfalse;
    }
    buff-->0 = 0;
    current_buffer = buff;
    buffers_open++;
    buffer_addresses-->buffers_open = current_buffer;
    if (buffers_open == 1) @setiosys 1 FilterOutput;
];

[ CloseBuffer;

    if (buffers_open == 0) rfalse;
    buffer_addresses-->buffers_open = 0;
    buffers_open--;
    if (buffers_open) current_buffer = buffer_addresses-->buffers_open;
    else @setiosys 2 0;
];

[ FilterOutput char     cbi;

    cbi = current_buffer-->0;
    current_buffer->(cbi + WORDSIZE) = char;
    current_buffer-->0 = cbi + 1;
];

#endif;
!***Undo handling

!Define NOUNDO to disable undo, and put
!#IFNDEF DEBUG;
!Replace Keyboard;
!#ENDIF;
!Before including Parser and VerbLib

#IFNDEF DEBUG;
#IFDEF NOUNDO;
[ 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:

   a_buffer->0 = 120;
   a_table->0 = 15;  ! Allow to split input into this many words

!  Print the prompt, and read in the words and dictionary addresses

    L__M(##Prompt);
    AfterPrompt();
    #IFV5; DrawStatusLine(); #ENDIF;
    KeyboardPrimitive(a_buffer, a_table);
    nw=a_table->1;

!  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

    w=a_table-->1;
    if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) jump DoOops;

#IFV5;
!  Undo handling

    if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (parse->1==1))
    { print "If only it were that simple.";
        jump FreshInput;
    }
    @save_undo i;
    just_undone=0;
    undo_flag=2;
    if (i==-1) undo_flag=0;
    if (i==0) undo_flag=1;
    if (i==2)
    {   style bold;
        print (name) location, "^";
        style roman;
        L__M(##Miscellany,13);
        just_undone=1;
        jump FreshInput;
    }
#ENDIF;

    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<=120:i++) buffer2->i = a_buffer->i;
    x1 = a_table->9; ! Start of word following "oops"
    x2 = a_table->8; ! Length of word following "oops"

!  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:

    w = a_table->(4*oops_from + 1); ! Start of word to go
    w2 = a_table->(4*oops_from);    ! Length of word to go

!  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=120:i>=w+x2:i--)
            a_buffer->i = a_buffer->(i-x2+w2);

        ! ...increasing buffer size accordingly.

        a_buffer->1 = (a_buffer->1) + (x2-w2);
    }

!  Write the correction in:

    for (i=0:i<x2:i++) a_buffer->(i+w) = buffer2->(i+x1);

    Tokenise__(a_buffer,a_table);
    nw=a_table->1;

    return nw;
];

#ENDIF;
#ENDIF;
