\ Copyright (c) 2012 Devin Teske <dteske@FreeBSD.org>
\ All rights reserved.
\ 
\ Redistribution and use in source and binary forms, with or without
\ modification, are permitted provided that the following conditions
\ are met:
\ 1. Redistributions of source code must retain the above copyright
\    notice, this list of conditions and the following disclaimer.
\ 2. Redistributions in binary form must reproduce the above copyright
\    notice, this list of conditions and the following disclaimer in the
\    documentation and/or other materials provided with the distribution.
\ 
\ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
\ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
\ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
\ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
\ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
\ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
\ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
\ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
\ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
\ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
\ SUCH DAMAGE.
\ 

marker task-menusets.4th

vocabulary menusets-infrastructure
only forth also menusets-infrastructure definitions

variable menuset_use_name

create menuset_affixbuf	255 allot
create menuset_x        1   allot
create menuset_y        1   allot

: menuset-loadvar ( -- )

	\ menuset_use_name is true or false
	\ $type should be set to one of:
	\ 	menu toggled ansi
	\ $var should be set to one of:
	\ 	caption command keycode text ...
	\ $affix is either prefix (menuset_use_name is true)
	\               or infix (menuset_use_name is false)

	s" set cmdbuf='set ${type}_${var}=\$'" evaluate
	s" cmdbuf" getenv swap drop ( -- u1 ) \ get string length
	menuset_use_name @ true = if
		s" set cmdbuf=${cmdbuf}${affix}${type}_${var}"
		( u1 -- u1 c-addr2 u2 )
	else
		s" set cmdbuf=${cmdbuf}${type}set${affix}_${var}"
		( u1 -- u1 c-addr2 u2 )
	then
	evaluate ( u1 c-addr2 u2 -- u1 )
	s" cmdbuf" getenv ( u1 -- u1 c-addr2 u2 )
	rot 2 pick 2 pick over + -rot + tuck -
		( u1 c-addr2 u2 -- c-addr2 u2 c-addr1 u1 )
		\ Generate a string representing rvalue inheritance var
	getenv dup -1 = if
		( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 -1 )
		\ NOT set -- clean up the stack
		drop ( c-addr2 u2 -1 -- c-addr2 u2 )
		2drop ( c-addr2 u2 -- )
	else
		( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 c-addr1 u1 )
		\ SET -- execute cmdbuf (c-addr2/u2) to inherit value
		2drop ( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 )
		evaluate ( c-addr2 u2 -- )
	then

	s" cmdbuf" unsetenv
;

: menuset-unloadvar ( -- )

	\ menuset_use_name is true or false
	\ $type should be set to one of:
	\ 	menu toggled ansi
	\ $var should be set to one of:
	\ 	caption command keycode text ...
	\ $affix is either prefix (menuset_use_name is true)
	\               or infix (menuset_use_name is false)

	menuset_use_name @ true = if
		s" set buf=${affix}${type}_${var}"
	else
		s" set buf=${type}set${affix}_${var}"
	then
	evaluate
	s" buf" getenv unsetenv
	s" buf" unsetenv
;

: menuset-loadmenuvar ( -- )
	s" set type=menu" evaluate
	menuset-loadvar
;

: menuset-unloadmenuvar ( -- )
	s" set type=menu" evaluate
	menuset-unloadvar
;

: menuset-loadxvar ( -- )

	\ menuset_use_name is true or false
	\ $type should be set to one of:
	\ 	menu toggled ansi
	\ $var should be set to one of:
	\ 	caption command keycode text ...
	\ $x is "1" through "8"
	\ $affix is either prefix (menuset_use_name is true)
	\               or infix (menuset_use_name is false)

	s" set cmdbuf='set ${type}_${var}[${x}]=\$'" evaluate
	s" cmdbuf" getenv swap drop ( -- u1 ) \ get string length
	menuset_use_name @ true = if
		s" set cmdbuf=${cmdbuf}${affix}${type}_${var}[${x}]"
		( u1 -- u1 c-addr2 u2 )
	else
		s" set cmdbuf=${cmdbuf}${type}set${affix}_${var}[${x}]"
		( u1 -- u1 c-addr2 u2 )
	then
	evaluate ( u1 c-addr2 u2 -- u1 )
	s" cmdbuf" getenv ( u1 -- u1 c-addr2 u2 )
	rot 2 pick 2 pick over + -rot + tuck -
		( u1 c-addr2 u2 -- c-addr2 u2 c-addr1 u1 )
		\ Generate a string representing rvalue inheritance var
	getenv dup -1 = if
		( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 -1 )
		\ NOT set -- clean up the stack
		drop ( c-addr2 u2 -1 -- c-addr2 u2 )
		2drop ( c-addr2 u2 -- )
	else
		( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 c-addr1 u1 )
		\ SET -- execute cmdbuf (c-addr2/u2) to inherit value
		2drop ( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 )
		evaluate ( c-addr2 u2 -- )
	then

	s" cmdbuf" unsetenv
;

: menuset-unloadxvar ( -- )

	\ menuset_use_name is true or false
	\ $type should be set to one of:
	\ 	menu toggled ansi
	\ $var should be set to one of:
	\ 	caption command keycode text ...
	\ $x is "1" through "8"
	\ $affix is either prefix (menuset_use_name is true)
	\               or infix (menuset_use_name is false)

	menuset_use_name @ true = if
		s" set buf=${affix}${type}_${var}[${x}]"
	else
		s" set buf=${type}set${affix}_${var}[${x}]"
	then
	evaluate
	s" buf" getenv unsetenv
	s" buf" unsetenv
;

: menuset-loadansixvar ( -- )
	s" set type=ansi" evaluate
	menuset-loadxvar
;

: menuset-unloadansixvar ( -- )
	s" set type=ansi" evaluate
	menuset-unloadxvar
;

: menuset-loadmenuxvar ( -- )
	s" set type=menu" evaluate
	menuset-loadxvar
;

: menuset-unloadmenuxvar ( -- )
	s" set type=menu" evaluate
	menuset-unloadxvar
;

: menuset-loadtoggledxvar ( -- )
	s" set type=toggled" evaluate
	menuset-loadxvar
;

: menuset-unloadtoggledxvar ( -- )
	s" set type=toggled" evaluate
	menuset-unloadxvar
;

: menuset-loadxyvar ( -- )

	\ menuset_use_name is true or false
	\ $type should be set to one of:
	\ 	menu toggled ansi
	\ $var should be set to one of:
	\ 	caption command keycode text ...
	\ $x is "1" through "8"
	\ $y is "0" through "9"
	\ $affix is either prefix (menuset_use_name is true)
	\               or infix (menuset_use_name is false)

	s" set cmdbuf='set ${type}_${var}[${x}][${y}]=\$'" evaluate
	s" cmdbuf" getenv swap drop ( -- u1 ) \ get string length
	menuset_use_name @ true = if
		s" set cmdbuf=${cmdbuf}${affix}${type}_${var}[${x}][${y}]"
		( u1 -- u1 c-addr2 u2 )
	else
		s" set cmdbuf=${cmdbuf}${type}set${affix}_${var}[${x}][${y}]"
		( u1 -- u1 c-addr2 u2 )
	then
	evaluate ( u1 c-addr2 u2 -- u1 )
	s" cmdbuf" getenv ( u1 -- u1 c-addr2 u2 )
	rot 2 pick 2 pick over + -rot + tuck -
		( u1 c-addr2 u2 -- c-addr2 u2 c-addr1 u1 )
		\ Generate a string representing rvalue inheritance var
	getenv dup -1 = if
		( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 -1 )
		\ NOT set -- clean up the stack
		drop ( c-addr2 u2 -1 -- c-addr2 u2 )
		2drop ( c-addr2 u2 -- )
	else
		( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 c-addr1 u1 )
		\ SET -- execute cmdbuf (c-addr2/u2) to inherit value
		2drop ( c-addr2 u2 c-addr1 u1 -- c-addr2 u2 )
		evaluate ( c-addr2 u2 -- )
	then

	s" cmdbuf" unsetenv
;

: menuset-unloadxyvar ( -- )

	\ menuset_use_name is true or false
	\ $type should be set to one of:
	\ 	menu toggled ansi
	\ $var should be set to one of:
	\ 	caption command keycode text ...
	\ $x is "1" through "8"
	\ $y is "0" through "9"
	\ $affix is either prefix (menuset_use_name is true)
	\               or infix (menuset_use_name is false)

	menuset_use_name @ true = if
		s" set buf=${affix}${type}_${var}[${x}][${y}]"
	else
		s" set buf=${type}set${affix}_${var}[${x}][${y}]"
	then
	evaluate
	s" buf" getenv unsetenv
	s" buf" unsetenv
;

: menuset-loadansixyvar ( -- )
	s" set type=ansi" evaluate
	menuset-loadxyvar
;

: menuset-unloadansixyvar ( -- )
	s" set type=ansi" evaluate
	menuset-unloadxyvar
;

: menuset-loadmenuxyvar ( -- )
	s" set type=menu" evaluate
	menuset-loadxyvar
;

: menuset-unloadmenuxyvar ( -- )
	s" set type=menu" evaluate
	menuset-unloadxyvar
;

: menuset-setnum-namevar ( N -- C-Addr/U )

	s" menuset_nameNNNNN" ( n -- n c-addr1 u1 )	\ variable basename
	drop 12 ( n c-addr1 u1 -- n c-addr1 12 )	\ remove "NNNNN"
	rot     ( n c-addr1 12 -- c-addr1 12 n )	\ move number on top

	\ convert to string
	s>d <# #s #> ( c-addr1 12 n -- c-addr1 12 c-addr2 u2 )

	\ Combine strings
	begin ( using u2 in c-addr2/u2 pair as countdown to zero )
		over	( c-addr1 u1 c-addr2 u2 -- continued below )
			( c-addr1 u1 c-addr2 u2 c-addr2 ) \ copy src-addr
		c@	( c-addr1 u1 c-addr2 u2 c-addr2 -- continued below )
			( c-addr1 u1 c-addr2 u2 c ) \ get next src-addr byte
		4 pick 4 pick
			( c-addr1 u1 c-addr2 u2 c -- continued below )
			( c-addr1 u1 c-addr2 u2 c c-addr1 u1 )
			\ get destination c-addr1/u1 pair
		+	( c-addr1 u1 c-addr2 u2 c c-addr1 u1 -- cont. below )
			( c-addr1 u1 c-addr2 u2 c c-addr3 )
			\ combine dest-c-addr to get dest-addr for byte
		c!	( c-addr1 u1 c-addr2 u2 c c-addr3 -- continued below )
			( c-addr1 u1 c-addr2 u2 )
			\ store the current src-addr byte into dest-addr

		2swap 1+ 2swap	\ increment u1 in destination c-addr1/u1 pair
		swap 1+ swap	\ increment c-addr2 in source c-addr2/u2 pair
		1-		\ decrement u2 in the source c-addr2/u2 pair

		dup 0= \ time to break?
	until

	2drop	( c-addr1 u1 c-addr2 u2 -- c-addr1 u1 )
		\ drop temporary number-format conversion c-addr2/u2
;

: menuset-checksetnum ( N -- )

	\ 
	\ adjust input to be both positive and no-higher than 65535
	\ 
	abs dup 65535 > if drop 65535 then ( n -- n )

	\
	\ The next few blocks will determine if we should use the default
	\ methodology (referencing the original numeric stack-input), or if-
	\ instead $menuset_name{N} has been defined wherein we would then
	\ use the value thereof as the prefix to every menu variable.
	\ 

	false menuset_use_name ! \ assume name is not set

	menuset-setnum-namevar 
	\ 
	\ We now have a string that is the assembled variable name to check
	\ for... $menuset_name{N}. Let's check for it.
	\ 
	2dup ( c-addr1 u1 -- c-addr1 u1 c-addr1 u1 ) \ save a copy
	getenv dup -1 <> if ( c-addr1 u1 c-addr1 u1 -- c-addr1 u1 c-addr2 u2 )
		\ The variable is set. Let's clean up the stack leaving only
		\ its value for later use.

		true menuset_use_name !
		2swap 2drop	( c-addr1 u1 c-addr2 u2 -- c-addr2 u2 )
				\ drop assembled variable name, leave the value
	else ( c-addr1 u1 c-addr1 u1 -- c-addr1 u1 -1 ) \ no such variable
		\ The variable is not set. Let's clean up the stack leaving the
		\ string [portion] representing the original numeric input.

		drop ( c-addr1 u1 -1 -- c-addr1 u1 ) \ drop -1 result
		12 - swap 12 + swap ( c-addr1 u1 -- c-addr2 u2 )
			\ truncate to original numeric stack-input
	then

	\ 
	\ Now, depending on whether $menuset_name{N} has been set, we have
	\ either the value thereof to be used as a prefix to all menu_*
	\ variables or we have a string representing the numeric stack-input
	\ to be used as a "set{N}" infix to the same menu_* variables.
	\ 
	\ For example, if the stack-input is 1 and menuset_name1 is NOT set
	\ the following variables will be referenced:
	\ 	ansiset1_caption[x]		-> ansi_caption[x]
	\ 	ansiset1_caption[x][y]		-> ansi_caption[x][y]
	\ 	menuset1_acpi			-> menu_acpi
	\ 	menuset1_caption[x]		-> menu_caption[x]
	\ 	menuset1_caption[x][y]		-> menu_caption[x][y]
	\ 	menuset1_command[x]		-> menu_command[x]
	\ 	menuset1_init			-> ``evaluated''
	\ 	menuset1_init[x]		-> menu_init[x]
	\ 	menuset1_kernel			-> menu_kernel
	\ 	menuset1_keycode[x]		-> menu_keycode[x]
	\ 	menuset1_options		-> menu_options
	\ 	menuset1_optionstext		-> menu_optionstext
	\ 	menuset1_reboot			-> menu_reboot
	\ 	toggledset1_ansi[x]		-> toggled_ansi[x]
	\ 	toggledset1_text[x]		-> toggled_text[x]
	\ otherwise, the following variables are referenced (where {name}
	\ represents the value of $menuset_name1 (given 1 as stack-input):
	\ 	{name}ansi_caption[x]		-> ansi_caption[x]
	\ 	{name}ansi_caption[x][y]	-> ansi_caption[x][y]
	\ 	{name}menu_acpi			-> menu_acpi
	\ 	{name}menu_caption[x]		-> menu_caption[x]
	\ 	{name}menu_caption[x][y]	-> menu_caption[x][y]
	\ 	{name}menu_command[x]		-> menu_command[x]
	\ 	{name}menu_init			-> ``evaluated''
	\ 	{name}menu_init[x]		-> menu_init[x]
	\ 	{name}menu_kernel		-> menu_kernel
	\ 	{name}menu_keycode[x]		-> menu_keycode[x]
	\ 	{name}menu_options		-> menu_options
	\ 	{name}menu_optionstext		-> menu_optionstext
	\ 	{name}menu_reboot		-> menu_reboot
	\ 	{name}toggled_ansi[x]		-> toggled_ansi[x]
	\ 	{name}toggled_text[x]		-> toggled_text[x]
	\ 
	\ Note that menuset{N}_init and {name}menu_init are the initializers
	\ for the entire menu (for wholly dynamic menus) opposed to the per-
	\ menuitem initializers (with [x] afterward). The whole-menu init
	\ routine is evaluated and not passed down to $menu_init (which
	\ would result in double evaluation). By doing this, the initializer
	\ can initialize the menuset before we transfer it to active-duty.
	\ 

	\ 
	\ Copy our affixation (prefix or infix depending on menuset_use_name)
	\ to our buffer so that we can safely use the s-quote (s") buf again.
	\ 
	menuset_affixbuf 0 2swap ( c-addr2 u2 -- c-addr1 0 c-addr2 u2 )
	begin ( using u2 in c-addr2/u2 pair as countdown to zero )
		over ( c-addr1 u1 c-addr2 u2 -- c-addr1 u1 c-addr2 u2 c-addr2 )
		c@   ( c-addr1 u1 c-addr2 u2 -- c-addr1 u1 c-addr2 u2 c )
		4 pick 4 pick
		     ( c-addr1 u1 c-addr2 u2 c -- continued below )
		     ( c-addr1 u1 c-addr2 u2 c c-addr1 u1 )
		+    ( c-addr1 u1 c-addr2 u2 c c-addr1 u1 -- continued below )
		     ( c-addr1 u1 c-addr2 u2 c c-addr3 )
		c!   ( c-addr1 u1 c-addr2 u2 c c-addr3 -- continued below )
		     ( c-addr1 u1 c-addr2 u2 )
		2swap 1+ 2swap	\ increment affixbuf byte position/count
		swap 1+ swap	\ increment strbuf pointer (source c-addr2)
		1-		\ decrement strbuf byte count (source u2)
		dup 0=          \ time to break?
	until
	2drop ( c-addr1 u1 c-addr2 u2 -- c-addr1 u1 ) \ drop strbuf c-addr2/u2

	\
	\ Create a variable for referencing our affix data (prefix or infix
	\ depending on menuset_use_name as described above). This variable will
	\ be temporary and only used to simplify cmdbuf assembly.
	\ 
	s" affix" setenv ( c-addr1 u1 -- )
;

: menuset-cleanup ( -- )
	s" type"  unsetenv
	s" var"   unsetenv
	s" x"     unsetenv
	s" y"     unsetenv
	s" affix" unsetenv
;

only forth definitions also menusets-infrastructure

: menuset-loadsetnum ( N -- )

	menuset-checksetnum ( n -- )

	\ 
	\ From here out, we use temporary environment variables to make
	\ dealing with variable-length strings easier.
	\ 
	\ menuset_use_name is true or false
	\ $affix should be used appropriately w/respect to menuset_use_name
	\ 

	\ ... menu_init ...
	s" set var=init" evaluate
	menuset-loadmenuvar

	\ If menu_init was set by the above, evaluate it here-and-now
	\ so that the remaining variables are influenced by its actions
	s" menu_init" 2dup getenv dup -1 <> if
		2swap unsetenv \ don't want later menu-create to re-call this
		evaluate
	else
		drop 2drop ( n c-addr u -1 -- n )
	then

	[char] 1 ( -- x ) \ Loop range ASCII '1' (49) to '8' (56)
	begin
		dup menuset_x tuck c! 1 s" x" setenv \ set loop iterator and $x

		s" set var=caption" evaluate

		\ ... menu_caption[x] ...
		menuset-loadmenuxvar

		\ ... ansi_caption[x] ...
		menuset-loadansixvar

		[char] 0 ( x -- x y ) \ Inner Loop ASCII '1' (48) to '9' (57)
		begin
			dup menuset_y tuck c! 1 s" y" setenv
				\ set inner loop iterator and $y

			\ ... menu_caption[x][y] ...
			menuset-loadmenuxyvar

			\ ... ansi_caption[x][y] ...
			menuset-loadansixyvar

			1+ dup 57 > ( x y -- y' 0|-1 ) \ increment and test
		until
		drop ( x y -- x )

		\ ... menu_command[x] ...
		s" set var=command" evaluate
		menuset-loadmenuxvar

		\ ... menu_init[x] ...
		s" set var=init" evaluate
		menuset-loadmenuxvar

		\ ... menu_keycode[x] ...
		s" set var=keycode" evaluate
		menuset-loadmenuxvar

		\ ... toggled_text[x] ...
		s" set var=text" evaluate
		menuset-loadtoggledxvar

		\ ... toggled_ansi[x] ...
		s" set var=ansi" evaluate
		menuset-loadtoggledxvar

		1+ dup 56 > ( x -- x' 0|-1 ) \ increment iterator
		                             \ continue if less than 57
	until
	drop ( x -- ) \ loop iterator

	\ ... menu_reboot ...
	s" set var=reboot" evaluate
	menuset-loadmenuvar

	\ ... menu_acpi ...
	s" set var=acpi" evaluate
	menuset-loadmenuvar

	\ ... menu_kernel ...
	s" set var=kernel" evaluate
	menuset-loadmenuvar

	\ ... menu_options ...
	s" set var=options" evaluate
	menuset-loadmenuvar

	\ ... menu_optionstext ...
	s" set var=optionstext" evaluate
	menuset-loadmenuvar

	menuset-cleanup
;

: menusets-unset ( -- )

	s" menuset_initial" unsetenv

	1 begin
		dup menuset-checksetnum ( n n -- n )

		dup menuset-setnum-namevar ( n n -- n )
		unsetenv

		\ If the current menuset does not populate the first menuitem,
		\ we stop completely.

		menuset_use_name @ true = if
			s" set buf=${affix}menu_caption[1]"
		else
			s" set buf=menuset${affix}_caption[1]"
		then
		evaluate s" buf" getenv getenv -1 = if
			drop ( n -- )
			s" buf" unsetenv
			menuset-cleanup
			exit
		else
			drop ( n c-addr2 -- n ) \ unused
		then

		[char] 1 ( n -- n x ) \ Loop range ASCII '1' (49) to '8' (56)
		begin
			dup menuset_x tuck c! 1 s" x" setenv \ set $x to x

			s" set var=caption" evaluate
			menuset-unloadmenuxvar
			menuset-unloadmenuxvar
			menuset-unloadansixvar
			[char] 0 ( n x -- n x y ) \ Inner loop '0' to '9'
			begin
				dup menuset_y tuck c! 1 s" y" setenv
					\ sets $y to y
				menuset-unloadmenuxyvar
				menuset-unloadansixyvar
				1+ dup 57 > ( n x y -- n x y' 0|-1 )
			until
			drop ( n x y -- n x )
			s" set var=command" evaluate menuset-unloadmenuxvar
			s" set var=init"    evaluate menuset-unloadmenuxvar
			s" set var=keycode" evaluate menuset-unloadmenuxvar
			s" set var=text"    evaluate menuset-unloadtoggledxvar
			s" set var=ansi"    evaluate menuset-unloadtoggledxvar

			1+ dup 56 > ( x -- x' 0|-1 ) \ increment and test
		until
		drop ( n x -- n ) \ loop iterator

		s" set var=acpi"        evaluate menuset-unloadmenuvar
		s" set var=init"        evaluate menuset-unloadmenuvar
		s" set var=kernel"      evaluate menuset-unloadmenuvar
		s" set var=options"     evaluate menuset-unloadmenuvar
		s" set var=optionstext" evaluate menuset-unloadmenuvar
		s" set var=reboot"      evaluate menuset-unloadmenuvar

		1+ dup 65535 > ( n -- n' 0|-1 ) \ increment and test
	until
	drop ( n' -- ) \ loop iterator

	s" buf" unsetenv
	menuset-cleanup
;

only forth definitions

: menuset-loadinitial ( -- )
	s" menuset_initial" getenv dup -1 <> if
		?number 0<> if
			menuset-loadsetnum
		then
	else
		drop \ cruft
	then
;
