'OHRRPGCE - EditorKit framework for creating editors
'(C) Copyright 1997-2023 James Paige, Ralph Versteegen, and the OHRRPGCE Developers
'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability.

#ifndef EDITORKIT_BI
#define EDITORKIT_BI

#include "config.bi"
#include "common.bi"
#include "reload.bi"
#include "menus.bi"

' QB relics
#undef defint
#undef defstr

' (Internal) Indicates which of value/valuestr/valuefloat is in use, and how to interpret it.
enum EditorKitDataType
	dtypeNone
	dtypeBool      'Includes boolean
	dtypeInt
	dtypeStr       'Includes string enumerations (*_str_enum)
	dtypeFloat
end enum

' (Internal) How to write value/valuestr/valuefloat back to the data source
enum EditorKitDataWriter
	writerNone
	writerByte
	writerUByte
	writerBoolean
	writerBit
	writerInt      'Includes bool
	writerLongint  'For enums, but 32-bit values only
	writerStr
	writerSingle
	writerDouble
	writerNodeInt
	writerNodeBool
	writerNodeStr          'Includes string enumerations
	writerNodeFloat
	writerNodePathInt
	writerNodePathBool
	writerNodePathStr      'Includes string enumerations
	writerNodePathFloat
	writerNodePathExists
	writerConfigBool
end enum

' (Internal) Encapsulates most of the state of the current menu item, aside from
' the actual data value.  Probably should split up into the part we throw
' away, and the part that could be useful to keep.
type EditorKitItem
	' Data:
	dtype as EditorKitDataType
	writer as EditorKitDataWriter
	union
		byte_ptr as byte ptr
		ubyte_ptr as byte ptr
		int_ptr as integer ptr
		longint_ptr as longint ptr
		str_ptr as string ptr
		single_ptr as single ptr
		double_ptr as double ptr
		node as Reload.Node ptr       'writerNode* only
	end union
	path as string         'writerNodePath* and writerConfig* only: path to node/setting
	whichbit as integer    'writerBit only: a bitmask
	offset as integer      'dtypeInt only: amount to subtract from value before writing
	inverted_bool as bool  'dtypeBool only: whether to invert value before writing
	is_percent as bool     'dtypeFloat only: whether use format_percent (*100 with '%' suffix)

	delete_default as bool 'writerNodePath* only: delete node if equal to default
	' Default value of a missing node
	writer_default_int as integer  'writerNodePathInt only
	writer_default_str as string   'writerNodePathStr only
	writer_default_float as double 'writerNodePathFloat only

	default_value as integer = INT_MIN
	default_eff_value as integer   'What default_value is effectively equivalent to

	' Preserve min/max passed to edit_*
	range_min as integer '= INT_MIN
	range_max as integer '= INT_MAX
	range_min_float as double
	range_max_float as double

	' Menu item:
	id as integer          'Has no purpose yet
	title as string
	caption as string
	helpkey as string
	unselectable as bool
	disabled as bool
	color as integer       '0: default, >0: master() index, <0: -uicol - 1
end type

' An option of a string enumeration (for edit/val_str_enum).
type StringEnumOption
	' FB doesn't allow initialising strings in UDTs (but it allows string arrays!), so we use zstring ptrs
	key as zstring ptr         'The data field value. (Can be "")
	caption as zstring ptr     'Optional, defaults to key
	'description as zstring ptr
end type

'declare sub make_stringenum_array(options() as StringEnumOption, keys() as string)
declare function find_enum_index(key as string, options() as StringEnumOption) as integer
declare function prompt_for_enum(byref key as string, prompt_text as string, options() as StringEnumOption, helpkey as string) as bool

' Saved menu state for a submenu
type SubmenuState
	'state as MenuState
	pt as integer
	top as integer
end type

DECLARE_VECTOR_OF_TYPE(SubmenuState, SubmenuState)


' See editorkit.bas for usage information
type EditorKit extends ModularMenu
	'---- Menu settings
	exit_menu_text as string = "Previous Menu"
	exit_submenu_text as string = "Previous Menu"
	' And many others in ModularMenu, including:
	'helpkey as string         'F1 page if not overridden for an item. Gets copied to default_helpkey
	'floating as bool          'Float on top of current screen contents
	'title as string           'Editor title, displayed at top of screen
	'menuopts as MenuOptions

	'---- State variables which can be accessed inside define_items() while defining an item
	selected as bool           'The current menu item is selected
	hover as bool              'The current menu item has mouse-hover focus
	edited as bool             'The value has been modified and needs to be written back

	' refresh/process/activate tell the context in which the code for a menu
	' item definition is being run.
	' refresh is true for all menu items while refreshing, while activate
	' and process will be true for just one (the currently selected one).
	' No more than one of refresh and process will be true at once.
	refresh as bool
	process as bool            'When called every tick to handle arbitrary input and do editing
	process_text as bool       'Each-tick handling of text editing/input (not select-by-typing)
	activate as bool           'If process, and the item was clicked/activated
	left_click as bool         'If process, and start of left-click on the item
	right_click as bool        'If process, and start of right-click on the item
	declare function delete_action() as bool

	' Holds the value of the datum currently being edited
	value as integer           'Includes bool/boolean data
	valuestr as string
	valuefloat as double

	declare function eff_value() as integer  'Effective val after applying default

	'---- Global menu state variables (for access inside define_items())
	submenu as string          'Name of current submenu; "" by default
	'want_exit as bool         'Pressed ESC/etc or called exit_menu()  (from ModularMenu)
	want_activate as bool      'Cache enter_space_click() result

	enum Phases
		refreshing
		processing
		'activating
	end enum
	phase as Phases            'Whenever define_items() is called, this tells why

	'---- The following is internal state you usually would not access

	want_submenu as string     'Called switch/enter_submenu(), otherwise "NO"
	initialised as bool        'update() has been called at least once
	record_id_grabber_called as bool 'Ensure is only called once a tick

	default_helpkey as string  'If define_items doesn't set helpkey, nor set_helpkey for the selected item
	' Internal state to track the menu item currently being defined, while inside define_items()
	started_item as bool
	cur_item_index as integer
	cur_item as EditorKitItem
	' Set by keycombo(), these are set before defitem and wiped by defitem instead of finish_defitem
	combo_key1 as KBScancode
	combo_key2 as KBScancode

	' For record switching
	record_id_ptr as integer ptr
	min_record_id as integer
	max_record_id_ptr as integer ptr
	max_record_offset as integer  'Added to *max_record_id_ptr
	max_record_max as integer  'The maximum allowed max_record_id

	record_type_name as string 'The type of data being edited, e.g. "Hero"

	submenu_stack as string vector  'Excludes `submenu` (empty on the root menu (by default ""))
	saved_submenus as StrHashTable  'name -> SubmenuState ptr map for all visited submenus

	declare virtual destructor()

        '---- Editor setup routines (call before run())

	declare constructor()

	declare sub setup_record_switching(byref record_id as integer, min_record as integer = 0, byref max_record as integer, max_record_offset_ as integer = 0, record_type_name_ as string = "Record", max_record_max_ as integer = 0)

	' Inherited from ModularMenu
	'declare sub run()

  protected:
	declare sub run_phase(which_phase as Phases)

  private:
	declare sub update()
	' Use before_each_tick() instead
	declare sub each_tick()
	declare sub draw_overlays()
	declare sub write_value()
	declare function get_submenu_state(name as string) as SubmenuState ptr
	declare sub apply_enter_submenu(name as string = "")

  public:

        '---- Overridable methods

	' Subclasses should implement this method, nothing else is necessary.
	declare abstract sub define_items()

	' Cleaner to put code here than in a phase = Phases.processing block in define_items
	declare virtual sub before_each_tick()

	declare virtual sub load()
	declare virtual sub save()
	declare virtual function get_record_name(id as integer) as string
	' If you override try_exit you must call base.try_exit()!
	declare virtual function try_exit() as bool

	' And also ModularMenu methods such as draw_underlays, draw_overlays

	enum EKFlags
		no_flags = 0
		Or_None
		Delete_If_Default
		Preview_Audio
	end enum

	'---- Other non-menu-item methods
	declare sub switch_record(newid as integer)
	declare sub switch_submenu(name as string = "")
	declare sub enter_submenu(name as string = "")
	declare sub exit_menu()
	declare function record_id_grabber() as bool

	'---- Non-data menu item types
	declare sub spacer()
	declare sub section(title as zstring ptr)
	declare sub subsection(title as zstring ptr)
	declare sub def_record_switcher()
	'No method for adding "Previous Menu", it's automatic

	'---- Adding data menu items
	declare sub defitem(title as zstring ptr)
	declare function defitem_act(title as zstring ptr) as bool
	declare sub defunselectable(title as zstring ptr, color as integer = -eduiNote-1)
	declare sub defdisabled(title as zstring ptr)
	declare sub defint overload(title as zstring ptr, byref datum as integer, min as integer = 0, max as integer)
	declare sub defint overload(title as zstring ptr, byref datum as longint, min as integer = 0, max as integer)
	declare sub defint overload(title as zstring ptr, byref datum as ubyte, min as integer = 0, max as integer)
	declare sub defbool overload(title as zstring ptr, byref datum as bool)
	declare sub defbool overload(title as zstring ptr, byref datum as boolean)
	declare sub defbitset(title as zstring ptr, bitwords() as integer, wordnum as integer = 0, bitnum as integer)
	declare sub defstr(title as zstring ptr, byref datum as string, maxlen as integer = 0)

	declare virtual sub finish_defitem()

	'---- Captions
	declare sub set_caption(caption as zstring ptr)
	declare sub caption_default_or_int(default_value as integer = 0, default_caption as zstring ptr = @"Default")
	declare sub caption_default_or_str(default_caption as zstring ptr = @"[default]")
	declare sub captions_yesno(yescapt as zstring ptr, nocapt as zstring ptr)
	declare sub captions(captions_array() as string, invalid_thing as zstring ptr = @"value")
	declare sub captionsz(captions_array() as zstring ptr, invalid_thing as zstring ptr = @"value")
	declare sub captions_or_int(captions_array() as string)
	'declare sub captions_list(...)   'A macro, below
	'declare sub captions_list_or_int(...)   'A macro, below

	#macro captions_list(caption_strings...)
		if refresh then  'Creates a scope
			dim captionarray(...) as string = {caption_strings}
			captions captionarray()
		end if
	#endmacro
	#macro captions_list_or_int(caption_strings...)
		if refresh then  'Creates a scope
			dim captionarray(...) as string = {caption_strings}
			captions_or_int captionarray()
		end if
	#endmacro

	' Mostly internal
	declare sub wrap_caption(caption as string)
	declare function form_default_caption() as string

	'---- Other menu item attributes
	declare sub keycombo(key1 as KBScancode, key2 as KBScancode = scNone)
	declare sub set_unselectable()
	declare sub set_disabled()
	declare sub set_color(color as integer)
	declare sub set_id(id as integer)
	declare sub set_helpkey(key as zstring ptr)
	declare sub set_tooltip(text as zstring ptr)
	declare function multiline_editable() as bool
	declare sub dont_write()

	declare sub default_effective_value(default_value as integer, effective_value as integer)

	'---- Value defining methods (val_*)

	' Value modifiers
	declare sub offset_int overload(offset as integer)
	declare function offset_int overload(offset as integer, byref datum as integer) as integer
	declare sub invert_bool overload()
	declare function invert_bool overload(byref datum as bool) as bool

	' Primitive types
	declare function val_int(byref datum as integer) as integer
	declare function val_int(byref datum as longint) as integer  'Supports 32-bit values only
	declare function val_int(byref datum as ubyte) as ubyte
	declare function val_bool overload(byref datum as bool) as bool
	declare function val_bool overload(byref datum as boolean) as bool
	declare function val_bit(byref bits as integer, whichbit as integer) as bool
	declare function val_bitset(bitwords() as integer, wordnum as integer = 0, bitnum as integer) as bool
	declare function val_str(byref datum as string) as string
	declare function val_float(byref datum as double, is_percent as bool = YES) as double
	declare function val_float(byref datum as single, is_percent as bool = YES) as single

	' Derived types
	declare function val_int_enum(byref datum as integer, options() as string, invalid_thing as zstring ptr = @"value") as integer
	declare function val_str_enum(byref datum as string, options() as StringEnumOption) as string

	' RELOAD Nodes
	declare function val_node_int overload(node as Reload.Node ptr) as integer
	declare function val_node_int overload(root as Reload.Node ptr, path as zstring ptr, default as integer = 0, delete_if_default_flag as EKFlags = 0) as integer
	declare function val_node_bool overload(node as Reload.Node ptr) as bool
	declare function val_node_bool overload(root as Reload.Node ptr, path as zstring ptr, default as bool = NO) as bool
	declare function val_node_str overload(node as Reload.Node ptr) as string
	declare function val_node_str overload(root as Reload.Node ptr, path as zstring ptr, default as zstring ptr = @"", delete_if_default_flag as EKFlags = 0) as string
	declare function val_node_float overload(node as Reload.Node ptr) as double
	declare function val_node_float overload(root as Reload.Node ptr, path as zstring ptr, default as double = 0., delete_if_default_flag as EKFlags = 0) as double
	declare function val_node_exists(root as Reload.Node ptr, path as zstring ptr) as bool

	' .ini config file settings
	declare function val_config_bool(path as zstring ptr, default as bool = NO) as bool

	'---- Basic data editing (edit_*)

	' Primitive types
	declare function edit_int overload(byref datum as integer, min as integer, max as integer) as bool
	declare function edit_int overload(byref datum as longint, min as integer, max as integer) as bool  '32bit values only
	declare function edit_int overload(byref datum as ubyte, min as integer, max as integer) as bool
	declare function edit_bool overload(byref datum as bool) as bool
	declare function edit_bool overload(byref datum as boolean) as bool
	declare function edit_bit(byref bits as integer, whichbit as integer) as bool
	declare function edit_bitset(bitwords() as integer, wordnum as integer = 0, bitnum as integer) as bool
	declare function edit_float(byref datum as double, min as double, max as double, sigfigs as integer = 4, is_percent as bool = YES, cyclic as bool = NO) as bool
	declare function edit_float(byref datum as single, min as double, max as double, sigfigs as integer = 4, is_percent as bool = YES, cyclic as bool = NO) as bool
	declare function edit_str(byref datum as string, maxlen as integer = 0) as bool
	' See also multiline_editable()

	' Derived types
	declare function edit_zint(byref datum as integer, min as integer, max as integer) as bool
	declare function edit_int_enum(byref datum as integer, options() as string, invalid_thing as zstring ptr = @"value") as bool
	declare function edit_str_enum(byref datum as string, options() as StringEnumOption) as bool

	' RELOAD Nodes
	declare function edit_node_int overload(node as Reload.Node ptr, min as integer = 0, max as integer) as bool
	declare function edit_node_int overload(root as Reload.Node ptr, path as zstring ptr, default as integer = 0, min as integer = 0, max as integer, delete_if_default_flag as EKFlags = 0) as bool
	declare function edit_node_bool overload(node as Reload.Node ptr) as bool
	declare function edit_node_bool overload(root as Reload.Node ptr, path as zstring ptr, default as integer = 0) as bool
	declare function edit_node_str overload(node as Reload.Node ptr, maxlen as integer = 0) as bool
	declare function edit_node_str overload(root as Reload.Node ptr, path as zstring ptr, default as zstring ptr = @"", maxlen as integer = 0, delete_if_default_flag as EKFlags = 0) as bool
	declare function edit_node_exists(node as Reload.Node ptr, path as zstring ptr) as bool

	declare sub delete_node()
	declare sub deletable_node(node as Node ptr = NULL, thingname as zstring ptr = NULL)

	' .ini config file settings
	declare function edit_config_bool(path as zstring ptr, default as bool = NO) as bool

	'---- Game data type definitions & editing (as_*, edit_as_*)

	' Tags
	declare sub as_check_tag(byref datum as integer, prefix as zstring ptr = @"Tag", zerocap as zstring ptr = @"None")
	declare function edit_as_check_tag(byref datum as integer, prefix as zstring ptr = @"Tag", zerocap as zstring ptr = @"None", allowneg as bool = YES) as bool
	declare sub as_set_tag(byref datum as integer, prefix as zstring ptr = @"Set tag", allowspecial as bool = NO)
	declare function edit_as_set_tag(byref datum as integer, prefix as zstring ptr = @"Set tag", allowspecial as bool = NO, allowneg as bool = YES) as bool
	declare sub as_tag_id(byref datum as integer, prefix as zstring ptr = @"Tag", allowspecial as bool = NO)
	declare function edit_as_tag_id(byref datum as integer, prefix as zstring ptr = @"Tag", allowspecial as bool = NO) as bool

	' Sprites
	declare sub as_spriteset(byref datum as integer, or_none_flag as EKFlags = 0)
	declare function edit_as_spriteset(byref datum as integer, spr_type as SpriteType, or_none_flag as EKFlags = 0) as bool
	declare sub as_palette(byref datum as integer)
	declare function edit_as_palette(byref datum as integer, spr_type as SpriteType = sprTypeInvalid, spr_set as integer = 0) as bool

	' Audio
	declare sub as_song(byref datum as integer)
	declare function edit_as_song(byref datum as integer, min as integer = -1, preview_audio_flag as EKFlags = 0) as bool
	declare sub as_sfx(byref datum as integer)
	declare function edit_as_sfx(byref datum as integer, min as integer = -1, preview_audio_flag as EKFlags = 0) as bool

	' Enemies
	declare sub as_enemy(byref id as integer, or_none_flag as EKFlags = 0)
	declare function edit_as_enemy(byref id as integer, or_none_flag as EKFlags = 0) as bool

	' Attacks
	declare sub as_attack(byref id as integer, or_none_flag as EKFlags = 0)
	declare function edit_as_attack(byref id as integer, or_none_flag as EKFlags = 0) as bool

	' Text Boxes
	declare sub as_textbox(byref id as integer, or_none_flag as EKFlags = 0)
	declare function edit_as_textbox(byref id as integer, or_none_flag as EKFlags = 0) as bool

	' Extra data vectors
	declare sub edit_extra_data_vector(byref extravec as integer vector)

  private:
	' Disable a few ModularMenu methods so they can't be called directly; they wouldn't work.
	' NOTE: due to FB bug sf#948 (fixed in FB 1.09) if you attempt to call these you'll get
	' the error "error 255: Ambiguous symbol access, explicit scope resolution required"
	' Call section() instead of header()
	declare sub header()
	' Call spacer() instead of add_spacer()
	declare sub add_spacer()
	' Call defitem() or defunselectable() or subsection() instead of add_item() for a do-nothing item.
	declare sub add_item()
end type

' Use the line number as a unique ID to identify the menu item, where needed
'#define defitem defitem_ __LINE__ ,
'#define defitem_act(args...) defitem_act_(__LINE__ ,  args)

#endif
