'OHRRPGCE CUSTOM - RELOAD Editor
'(C) Copyright 1997-2020 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.

#include "config.bi"
#include "allmodex.bi"
#include "common.bi"
#include "slices.bi"
#include "customsubs.bi"
#include "loading.bi"
#include "reload.bi"

#include "reloadedit.bi"

'-----------------------------------------------------------------------

TYPE ReloadEditorState
 indent as integer
 doc as Reload.Docptr
 root as Reload.Nodeptr
 mode as integer
 mode_name(1) as string
 menu as MenuDef
 state as MenuState
 seeknode as Reload.Nodeptr
 filename as string
 clipboard as Reload.NodePtr
 clipboard_is as Reload.NodePtr 'only used for visual display of what you copied last
 changed as bool 'track whether or not changes that you might want to save have been made
END TYPE

'-----------------------------------------------------------------------

DECLARE SUB reload_editor_refresh (byref st as ReloadEditorState, byval node as Reload.Nodeptr)
DECLARE FUNCTION reload_editor_node_string(byref st as ReloadEditorState, byval node as Reload.Nodeptr) as string
DECLARE FUNCTION reload_editor_browse(byref st as ReloadEditorState) as bool
DECLARE SUB reload_editor_export(byref st as ReloadEditorState)
DECLARE SUB reload_editor_load(filename as string, byref st as ReloadEditorState)
DECLARE SUB reload_editor_save(filename as string, byref st as ReloadEditorState)
DECLARE FUNCTION reload_editor_load_special_file(byref st as ReloadEditorState) as bool
DECLARE SUB reload_editor_edit_node(byref st as ReloadEditorState, mi as MenuDefItem Ptr)
DECLARE FUNCTION reload_editor_edit_node_name(byval node as Reload.Nodeptr) as bool
DECLARE FUNCTION reload_editor_edit_node_value(byref st as ReloadEditorState, byval node as Reload.Nodeptr) as bool
DECLARE FUNCTION reload_editor_edit_node_type(byval node as Reload.Nodeptr) as bool
DECLARE SUB reload_editor_rearrange(byref st as ReloadEditorState, mi as MenuDefItem Ptr)
DECLARE SUB reload_editor_swap_node_left(byval node as Reload.Nodeptr)
DECLARE SUB reload_editor_swap_node_right(byval node as Reload.Nodeptr)
DECLARE SUB reload_editor_focus_node(byref st as ReloadEditorState, byval node as Reload.Nodeptr)
DECLARE FUNCTION reload_editor_numeric_input_check() as bool
DECLARE FUNCTION reload_editor_okay_to_unload(byref st as ReloadEditorState) as bool

'-----------------------------------------------------------------------

SUB reload_editor()
 DIM st as ReloadEditorState
 
 st.changed = NO
 st.doc = Reload.CreateDocument()
 st.root = Reload.CreateNode(st.doc, "")
 Reload.SetRootNode(st.doc, st.root)

 st.mode_name(0) = "node values"
 st.mode_name(1) = "node names"

 st.state.pt = 0
 st.state.need_update = YES
 st.state.active = YES
 st.state.autosize = YES
 st.state.autosize_ignore_pixels = 16

 InitLikeStandardMenu st.menu
 
 setkeys YES
 DO
  setwait 55
  setkeys YES
  IF keyval(ccCancel) > 1 THEN
   IF reload_editor_okay_to_unload(st) THEN EXIT DO 
  END IF
  IF keyval(scF1) > 1 THEN show_help("reload_editor")
  IF keyval(scTAB) > 1 THEN st.mode = st.mode XOR 1
  IF keyval(scF2) > 1 THEN
   reload_editor_export st
  END IF
  IF keyval(scF3) > 1 THEN
   IF reload_editor_okay_to_unload(st) THEN
    IF reload_editor_browse(st) THEN
     st.state.need_update = YES
    END IF
    setkeys YES
   END IF
  END IF
  IF keyval(scF4) > 1 THEN
   IF reload_editor_okay_to_unload(st) THEN
    IF reload_editor_load_special_file(st) THEN
     st.state.need_update = YES
    END IF
    setkeys YES
   END IF
  END IF

  IF st.state.need_update THEN
   DeleteMenuItems st.menu
   st.indent = 0
   reload_editor_refresh st, st.root
   init_menu_state st.state, st.menu
   IF st.seeknode THEN
    reload_editor_focus_node st, st.seeknode
    st.seeknode = 0
   END IF
   st.state.need_update = NO
  END IF

  IF st.state.pt >= 0 AND st.state.pt <= st.menu.numitems - 1 THEN
   reload_editor_edit_node st, st.menu.items[st.state.pt]
   reload_editor_rearrange st, st.menu.items[st.state.pt]
  END IF
  
  IF keyval(scShift) = 0 THEN
   usemenu st.state
  END IF

  clearpage dpage
  draw_menu st.menu, st.state, dpage
  IF st.doc = NULL THEN
   edgeprint "(No Document loaded)", pMenuX, pMenuY, uilook(uiText), dpage
  END IF
  edgeprint "F1=Help TAB=Mode (" & st.mode_name(st.mode) & ") ", pInfoX, pInfoY, uilook(uiText), dpage

  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP

 IF st.clipboard <> 0 THEN Reload.FreeNode(st.clipboard)
 Reload.FreeDocument(st.doc)
 
END SUB

SUB reload_editor_rearrange(byref st as ReloadEditorState, mi as MenuDefItem Ptr)
 DIM node as Reload.Nodeptr
 node = mi->dataptr
 
 DIM changed as bool = NO
 
 IF keyval(scInsert) > 1 THEN
  DIM s as string
  IF prompt_for_string(s, "name of new node") THEN
   DIM newnode as Reload.Nodeptr
   newnode = Reload.CreateNode(st.doc, s)
   IF node = Reload.DocumentRoot(st.doc) THEN
    'root node can't have siblings!
    Reload.AddChild node, newnode
   ELSE
    Reload.AddSiblingAfter node, newnode
   END IF
   st.seeknode = newnode
   changed = YES
  END IF
 END IF
 
 IF keyval(scShift) > 0 THEN
  IF copy_keychord() THEN
   '--copy this node
   IF st.clipboard <> 0 THEN Reload.FreeNode(st.clipboard)
   st.clipboard = Reload.CloneNodeTree(node)
   st.clipboard_is = node
   changed = YES
  END IF
  IF paste_keychord() THEN
   '--paste this node
   IF st.clipboard <> 0 THEN
    Reload.AddSiblingAfter(node, Reload.CloneNodeTree(st.clipboard))
    IF Reload.NodeHasAncestor(NodeParent(node), st.clipboard_is) THEN st.clipboard_is = 0 'cosmetic importance only
    changed = YES
   END IF
  END IF
 END IF
 
 IF keyval(scShift) > 0 THEN
  IF keyval(ccUP) > 1 THEN
   Reload.SwapNodePrev node
   st.seeknode = node
   changed = YES
  END IF
  IF keyval(ccDOWN) > 1 THEN
   Reload.SwapNodeNext node
   st.seeknode = node
   changed = YES
  END IF
  IF keyval(ccLEFT) > 1 THEN
   reload_editor_swap_node_left node
   st.seeknode = node
   changed = YES
  END IF
  IF keyval(ccRIGHT) > 1 THEN
   reload_editor_swap_node_right node
   st.seeknode = node
   changed = YES
  END IF
 END IF
 
 IF keyval(scDelete) > 1 THEN
  IF node <> Reload.DocumentRoot(st.doc) THEN
   IF yesno("Delete this node?" & CHR(10) & reload_editor_node_string(st, node)) THEN
    Reload.FreeNode(node)
    changed = YES
   END IF
  END IF
 END IF
 
 IF changed THEN
  st.state.need_update = YES
  st.changed = YES
 END IF
END SUB

SUB reload_editor_swap_node_left(byval node as Reload.Nodeptr)
 IF node = 0 THEN EXIT SUB
 DIM parent as Reload.NodePtr
 parent = Reload.NodeParent(node)
 IF parent = 0 THEN EXIT SUB
 Reload.AddSiblingAfter(parent, node)
END SUB

SUB reload_editor_swap_node_right(byval node as Reload.Nodeptr)
 IF node = 0 THEN EXIT SUB
 DIM sib as Reload.NodePtr
 sib = Reload.PrevSibling(node)
 IF sib = 0 THEN EXIT SUB
 Reload.AddChild(sib, node)
END SUB

SUB reload_editor_edit_node(byref st as ReloadEditorState, mi as MenuDefItem Ptr)
 BUG_IF(mi = 0, "null mi")
 DIM node as Reload.NodePtr
 node = mi->dataptr
 BUG_IF(node = 0, "mi has null node")

 DIM changed as bool = NO

 IF keyval(scCTRL) > 0 AND keyval(scShift) > 0 THEN EXIT SUB 'no typing while holding ctrl+shift!
  
 SELECT CASE st.mode
  CASE 0:
   IF reload_editor_edit_node_value(st, node) THEN changed = YES
  CASE 1:
   IF reload_editor_edit_node_name(node) THEN changed = YES
 END SELECT
 IF reload_editor_edit_node_type(node) THEN changed = YES

 IF changed THEN
  mi->caption = STRING(mi->extra(0), " ") & reload_editor_node_string(st, node)
  st.changed = YES
 END IF

END SUB

FUNCTION reload_editor_edit_node_name(byval node as Reload.Nodeptr) as bool
 BUG_IF(node = 0, "null node", NO)
 DIM s as string
 s = Reload.NodeName(node)
 IF strgrabber(s) THEN
  Reload.RenameNode(node, s)
  RETURN YES
 END IF
 RETURN NO
END FUNCTION

FUNCTION reload_editor_edit_node_value(byref st as ReloadEditorState, byval node as Reload.Nodeptr) as bool
 BUG_IF(node = 0, "null node", NO)

 DIM nt as Reload.NodeTypes = Reload.NodeType(node)
  
 IF nt = Reload.rltFloat THEN
  'debug "no floatgrabber exists yet"
 ELSEIF nt = Reload.rltInt ORELSE (nt = Reload.rltNull ANDALSO reload_editor_numeric_input_check()) THEN
  IF keyval(scShift) = 0 THEN
   DIM n as integer
   n = Reload.GetInteger(node)
   IF intgrabber(n, -2147483648, 2147483647) THEN
    Reload.SetContent(node, n)
    RETURN YES
   END IF
  END IF
  IF keyval(scBackspace) > 1 THEN
   IF Reload.GetInteger(node) = 0 THEN
    Reload.SetContent(node)
    RETURN YES
   END IF
  END IF
 ELSEIF nt = Reload.rltString ORELSE nt = Reload.rltInternString ORELSE nt = Reload.rltNull THEN
  DIM s as string
  s = Reload.GetString(node)
  IF nt <> Reload.rltNull AND keyval(scENTER) > 1 THEN
   s = multiline_string_editor(s, "reload_editor_multiline")
   Reload.SetContent(node, s)
   RETURN YES
  ELSE
   IF strgrabber(s) THEN
    Reload.SetContent(node, s)
    RETURN YES
   END IF
  END IF
  IF keyval(scBackspace) > 1 THEN
   IF Reload.GetString(node) = "" THEN
    Reload.SetContent(node)
    RETURN YES
   END IF
  END IF
 ELSEIF nt <> Reload.rltNull THEN
  visible_debug "invalid reload node type " & nt
 END IF
 RETURN NO
END FUNCTION

FUNCTION reload_editor_edit_node_type(byval node as Reload.Nodeptr) as bool
 BUG_IF(node = 0, "null node", NO)
 IF keyval(scCTRL) > 0 THEN
  IF keyval(scI) > 1 THEN Reload.SetContent(node, Reload.GetInteger(node)) : RETURN YES
  IF keyval(scS) > 1 THEN Reload.SetContent(node, Reload.GetString(node)) : RETURN YES
  IF keyval(scF) > 1 THEN Reload.SetContent(node, Reload.GetFloat(node)) : RETURN YES
  IF keyval(scN) > 1 THEN Reload.SetContent(node) : RETURN YES
 END IF
 RETURN NO
END FUNCTION

SUB reload_editor_refresh (byref st as ReloadEditorState, byval node as Reload.Nodeptr)
 IF st.root = 0 THEN EXIT SUB  'No doc loaded

 DIM s as string
 s = STRING(st.indent, " ") & reload_editor_node_string(st, node)
 
 DIM index as integer
 index = append_menu_item(st.menu, s)
 
 DIM mi as MenuDefItem Ptr
 mi = st.menu.items[index]

 mi->dataptr = node
 mi->extra(0) = st.indent

 st.indent += 1 
 DIM chnode as Reload.Nodeptr
 chnode = Reload.FirstChild(node)
 DO WHILE chnode
  reload_editor_refresh st, chnode
  chnode = Reload.NextSibling(chnode)
 LOOP
 st.indent -= 1
END SUB

FUNCTION reload_editor_node_string(byref st as ReloadEditorState ,byval node as Reload.Nodeptr) as string
 BUG_IF(node = 0, "null node", "<null ptr>")
 DIM s as string = ""
 if Reload.NodeHasAncestor(node, st.clipboard_is) then s &= "*"
 s &= Reload.NodeName(node)
 SELECT CASE Reload.NodeType(node)
  CASE Reload.rltNull:   s &= "()"
  CASE Reload.rltInt:    s &= "(int) "    & Reload.GetInteger(node)
  CASE Reload.rltFloat:  s &= "(float) "  & Reload.GetFloat(node)
  CASE Reload.rltString, Reload.rltInternString
                         s &= "(str) "    & Reload.GetString(node)
 END SELECT
 RETURN s
END FUNCTION

SUB reload_editor_focus_node(byref st as ReloadEditorState, byval node as Reload.Nodeptr)
 DIM mi as MenuDefItem Ptr
 DIM n as Reload.Nodeptr
 FOR i as integer = 0 TO st.menu.numitems - 1
  mi = st.menu.items[i]
  n = mi->dataptr
  IF n = node THEN
   st.state.pt = i
   EXIT FOR
  END IF
 NEXT i
 correct_menu_state st.state
END SUB

SUB reload_editor_export(byref st as ReloadEditorState)
 IF st.doc = NULL THEN EXIT SUB
 DIM outfile as string
 outfile = inputfilename("Export RELOAD document", "", "", "input_file_export_reload", st.filename)
 IF outfile <> "" THEN
  IF INSTR(outfile, ".") = 0 THEN outfile &= ".reld"
  reload_editor_save outfile, st
 END IF
END SUB

'Returns true if the document changed
FUNCTION reload_editor_browse(byref st as ReloadEditorState) as bool
 DIM filename as string
 filename = browse(browseRELOAD, "", "", "browse_import_reload")
 IF filename = "" THEN RETURN NO
 reload_editor_load(filename, st)
 RETURN YES
END FUNCTION

SUB reload_editor_load(filename as string, byref st as ReloadEditorState)
 st.filename = ""
 st.changed = NO
 Reload.FreeDocument st.doc
 st.doc = Reload.LoadDocument(filename, optNoDelay)
 ERROR_IF(st.doc = 0, "load '" & filename & "' failed: null doc")
 st.root = Reload.DocumentRoot(st.doc)
 ERROR_IF(st.root = 0, "load '" & filename & "' failed: null root node")
 st.filename = trimpath(filename)
END SUB

SUB reload_editor_save(filename as string, byref st as ReloadEditorState)
 Reload.SerializeBin(filename, st.doc)
 st.filename = trimpath(filename)
 st.changed = NO
END SUB

FUNCTION reload_editor_numeric_input_check() as bool
 IF keyval(scShift) > 0 THEN RETURN NO
 FOR i as KBScancode = sc1 TO sc0
  IF keyval(i) > 1 THEN RETURN YES
 NEXT i
 IF keyval(scMinus) > 1 OR keyval(scNumpadMinus) > 1 THEN RETURN YES
 RETURN NO
END FUNCTION

FUNCTION reload_editor_okay_to_unload(byref st as ReloadEditorState) as bool
 IF st.changed = NO THEN RETURN YES
 DIM choice as integer
 'Prevent attempt to quit the program, stop and wait for response first
 DIM quitting as bool = getquitflag()
 setquitflag NO
 choice = twochoice("Save your changes before exiting?", "Yes, save", "No, discard")
 IF getquitflag() THEN choice = 1  'Second attempt to close the program: discard
 SELECT CASE choice
  CASE -1: 'cancelled
   RETURN NO
  CASE 0: 'yes, save!
   reload_editor_export st
   'but only actually allow unload if the save was confirmed
   IF st.changed = NO THEN
    IF quitting THEN setquitflag
    RETURN YES
   END IF
   RETURN NO
  CASE 1: 'no discard!
   IF quitting THEN setquitflag
   RETURN YES
 END SELECT
 RETURN NO
END FUNCTION

'Returns true if the document changed (including if doesn't exist)
FUNCTION reload_editor_load_special_file(byref st as ReloadEditorState) as bool
 'Could add slices and RSAV docs
 DIM menu(3) as string
 menu(0) = "general.reld"
 menu(1) = "distrib.reld"
 menu(2) = "heroes.reld"
 menu(3) = "heroform.reld"
 DIM choice as integer = multichoice(!"Which RELOAD document to view?\n(Changes have no effect)", menu())
 IF choice = -1 THEN RETURN NO

 st.filename = ""
 Reload.FreeDocument st.doc
 st.changed = NO
 SELECT CASE choice
  CASE 0
   st.doc = Reload.CreateDocument()
   st.root = Reload.CloneNodeTree(get_general_reld(), st.doc)
   Reload.SetRootNode(st.doc, st.root)
  CASE 1, 2, 3
   DIM filename as string = workingdir & SLASH & menu(choice)
   st.doc = Reload.LoadDocument(filename, optNoDelay or optIgnoreMissing)
   IF st.doc = 0 THEN visible_debug menu(choice) & " doesn't exist in this game"

 END SELECT
 IF st.doc = 0 THEN st.root = 0 : RETURN YES
 st.root = Reload.DocumentRoot(st.doc)
 RETURN YES
END FUNCTION