local DEBUG_INACTIVE = 1 -- debug is inactive, hooks are not set
local DEBUG_ACTIVE = 2 -- debug should be active and running, until we found a breakpoint or the program ends
local DEBUG_STOPPED = 3 -- debug should be active and running, we are waiting to stop in the next opportunity, we have to abort
local DEBUG_STEP_INTO = 4 -- debug should be active and running, we are waiting until its steps into the function to pause, or we found a breakpoint
local DEBUG_STEP_OVER = 5 -- debug should be active and running, we are waiting until its steps over the function to pause, or we found a breakpoint
local DEBUG_STEP_OUT = 6 -- debug should be active and running, we are waiting until its steps out of the function to pause, or we found a breakpoint
local DEBUG_PAUSED = 7 -- debug should be active, but paused
local DEBUG_ERRORPAUSED = 8 -- debug should be active, but paused in an error

local FUNC_STATE_INSIDE = 1
local FUNC_STATE_OUTSIDE = 2

local debugger = {
  debugState = DEBUG_INACTIVE,

  currentFuncLevel = 0,
  stepFuncLevel = 0,
  stepFuncState = 0,

  startFile = nil,
  currentLine = nil,
  currentFile = nil,

  breakpoints = {},

  main_dialog = nil,
  panel_tabs = nil,
  multitext_tabs = nil,
  console_pos = 0,
  debug_pos = 0,
}

function iup.DebuggerInit(panelTabs, multitextTabs)
  debugger.main_dialog = iup.GetDialog(panelTabs)
  debugger.multitext_tabs = multitextTabs
  debugger.panel_tabs = panelTabs
  debugger.console_pos = panelTabs.count - 4
  debugger.debug_pos = panelTabs.count - 2

  debugger.panel_tabs.valuepos = debugger.console_pos -- show console tab
end

function iup.DebuggerShowConsoleTab()
  debugger.panel_tabs.valuepos = debugger.console_pos -- show console tab
end

function iup.DebuggerShowDebugTab()
  debugger.panel_tabs.valuepos = debugger.debug_pos -- show debug tab
end

function iup.DebuggerGetCurrentMultitext()
  return debugger.multitext_tabs.value_handle
end

-----------  IupScintillaDlg Usage  ---------------------

function iup.DebuggerNewFile()
  debugger.main_dialog.newfile = nil
end

function iup.DebuggerOpenFile(filename)
  debugger.main_dialog.openfile = filename
end

function iup.DebuggerForceCloseFile(pos)
  debugger.main_dialog.forceclosefile = pos
end

function iup.DebuggerUpdateTitle()
  debugger.main_dialog.updatetitle = "Yes"
end

function iup.DebuggerSaveFile()
  debugger.main_dialog.savefile = nil
end

function iup.DebuggerToggleMarker(lin)
  debugger.main_dialog["togglemarker"..lin] = 2 -- margin=2
end

-----------------------------------------__________----

function iup.DebuggerFindMultitext(filename)
  local multitext = iup.GetChild(debugger.multitext_tabs, 0)
  while multitext do
    if multitext.filename == filename then
      return multitext
    end

    multitext = iup.GetBrother(multitext)
  end
end

function iup.DebuggerRestoreBreakpoints(multitext, m_filename)
  local list_break = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_BREAK")

  local index = 1
  local filename = list_break["FILENAME" .. index]
  while filename do
    if filename == m_filename then
      local line = tonumber(list_break["LINE" .. index])
      multitext["MARKERADD".. (line - 1)] = 1 -- (margin=1)
    end

    index = index + 1
    filename = list_break["FILENAME" .. index]
  end
end

function iup.DebuggerOpenMultitext(filename, source)
  local old_count = tonumber(debugger.multitext_tabs.count)

  local s = string.sub(filename, 1, 6)
  if s == "string" then
    iup.DebuggerNewFile()
  else
    iup.DebuggerOpenFile(filename)
  end

  local count = tonumber(debugger.multitext_tabs.count)
  if count > old_count then
    local multitext = iup.GetChild(debugger.multitext_tabs, old_count)

    if s == "string" then
      multitext.filename = filename
      multitext.value = source
      multitext.temporary = "Yes"

      iup.DebuggerRestoreBreakpoints(multitext, filename)
    end

    if iup.DebuggerIsActive() then
      multitext.readonly = "Yes"
    end

    return multitext
  end
end

function iup.DebuggerCloseTemporary()
  local multitext = iup.GetChild(debugger.multitext_tabs, 0)
  local pos = 0
  while multitext do
    if multitext.temporary == "Yes" then
      local temp = multitext
      multitext = iup.GetBrother(multitext)

      iup.DebuggerForceCloseFile(pos)
    else
      multitext = iup.GetBrother(multitext)
      pos = pos + 1
    end
  end
end

function iup.DebuggerSetAttribAllMultitext(name, value)
  local multitext = iup.GetChild(debugger.multitext_tabs, 0)
  while multitext do
    multitext[name] = value 
    multitext = iup.GetBrother(multitext)
  end
end

function iup.DebuggerSetDialogChildAttrib(child_name, name, value)
  iup.SetAttribute(iup.GetDialogChild(debugger.main_dialog, child_name), name, value)
end

local function setparent_param_cb(param_dialog, param_index)
  if param_index == iup.GETPARAM_MAP then
    param_dialog.parentdialog = debugger.main_dialog
  end

  return 1
end


------------------------------------- User Interface State -------------------------------------


function iup.DebuggerSetStateString(state)
  local map_state = {  -- only the ones used in C
    DEBUG_ACTIVE = DEBUG_ACTIVE,
    DEBUG_STOPPED = DEBUG_STOPPED,
    DEBUG_STEP_INTO = DEBUG_STEP_INTO,
    DEBUG_STEP_OVER = DEBUG_STEP_OVER,
    DEBUG_STEP_OUT = DEBUG_STEP_OUT,
    DEBUG_PAUSED = DEBUG_PAUSED,
  }

  iup.DebuggerSetState(map_state[state])
end

function iup.DebuggerIsActive()
  if debugger.debugState ~= DEBUG_INACTIVE then
    return true
  else
    return false
  end
end

function iup.DebuggerSetState(st)
  local stop, step, pause, run, dbg, curline

  if debugger.debugState == st then
    return
  end

  iup.DebuggerSetAttribAllMultitext("READONLY", "Yes")

  if st == DEBUG_STOPPED then

    stop = "NO"
    step = "NO"
    run = "NO"
    pause = "NO"
    dbg = "NO"
    curline = "NO"
  elseif st == DEBUG_ACTIVE or st == DEBUG_STEP_INTO or st == DEBUG_STEP_OVER or st == DEBUG_STEP_OUT then
    stop = "YES"
    step = "NO"
    run = "NO"
    pause = "YES"
    dbg = "NO"
    curline = "NO"

    if st == DEBUG_STEP_OUT then
      debugger.stepFuncLevel = debugger.currentFuncLevel
      debugger.stepFuncState = FUNC_STATE_INSIDE
    else
      debugger.stepFuncLevel = 0
      debugger.stepFuncState = FUNC_STATE_OUTSIDE
    end
  elseif st == DEBUG_PAUSED then
    stop = "YES"
    step = "YES"
    run = "NO"
    pause = "NO"
    dbg = "YES"
    curline = "Yes"
  elseif st == DEBUG_ERRORPAUSED then
    stop = "YES"
    step = "NO"
    run = "NO"
    pause = "NO"
    dbg = "NO"
    curline = "Yes"
  else -- st == DEBUG_INACTIVE
    stop = "NO"
    step = "NO"
    run = "YES"
    pause = "NO"
    dbg = "YES"
    curline = "NO"

    iup.DebuggerSetAttribAllMultitext("READONLY", "No")

    iup.DebuggerClearLocalVariablesTree()
    iup.DebuggerClearStackList()
    iup.DebuggerInitGlobalsTree()
  end
    
  debugger.debugState = st

  iup.DebuggerSetAttribAllMultitext("MARKERDELETEALL", "2") -- clear all line highlight (margin=2)
  iup.DebuggerSetAttribAllMultitext("MARKERDELETEALL", "3") -- clear all line arrow (margin=3)

  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_RUN", "ACTIVE", run)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_STOP", "ACTIVE", stop)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_PAUSE", "ACTIVE", pause)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_DEBUG", "ACTIVE", dbg)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_STEPINTO", "ACTIVE", step)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_STEPOVER", "ACTIVE", step)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_STEPOUT", "ACTIVE", step)
  iup.DebuggerSetDialogChildAttrib("DEBUG_ITM_CURRENTLINE", "ACTIVE", curline)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_RUN", "ACTIVE", run)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_STOP", "ACTIVE", stop)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_PAUSE", "ACTIVE", pause)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_DEBUG", "ACTIVE", dbg)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_STEPINTO", "ACTIVE", step)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_STEPOVER", "ACTIVE", step)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_STEPOUT", "ACTIVE", step)
  iup.DebuggerSetDialogChildAttrib("DEBUG_BTN_CURRENTLINE", "ACTIVE", curline)
end                   

function iup.DebuggerHighlightLine(filename, line, source)
  if line == nil or line <= 0 then
    return
  end

  debugger.currentLine = line
  debugger.currentFile = filename

  local multitext = iup.DebuggerGetMultitext(filename, true, true, source) -- find and open if necessary
  if not multitext then
    return
  end

  local pos = iup.TextConvertLinColToPos(multitext, line-1, 0) -- line here starts at 0
  multitext.caretpos = pos

  -- highlight
  multitext["MARKERADD"..(line-1)] = 2 -- (margin=2)
  -- arrow
  multitext["MARKERADD"..(line-1)] = 3 -- (margin=3)
end

function iup.DebuggerShowCurrentLine()
  if debugger.currentLine and debugger.currentFile then
    iup.DebuggerSelectLine(debugger.currentFile, debugger.currentLine, true) -- find
  end
end

function iup.DebuggerGetMultitext(filename, find, open, source)
  local multitext = iup.DebuggerGetCurrentMultitext()

  if filename ~= multitext.filename then
    if find then
      multitext = iup.DebuggerFindMultitext(filename)
      if open then
        debugger.multitext_tabs.value = multitext
        iup.DebuggerUpdateTitle()

        if not multitext then
          multitext = iup.DebuggerOpenMultitext(filename, source)
          if not multitext then
            return
          end
        end
      end
    else
      return
    end
  end

  return multitext
end

function iup.DebuggerSelectLine(filename, line, find, source)
  if line == nil or line <= 0 then
    return
  end

  local multitext = iup.DebuggerGetMultitext(filename, find, true, source) -- find and always open if necessary
  if not multitext then
    return
  end

  local pos = iup.TextConvertLinColToPos(multitext, line-1, 0) -- line here starts at 0
  multitext.caretpos = pos

  -- select
  multitext.selection = line-1 .. ",0:" .. line-1 .. ",9999"
end


------------------------------------- Breakpoints -------------------------------------

function iup.DebuggerHasBreakpoint(filename, line)
  local file_breaks = debugger.breakpoints[filename]
  if file_breaks and file_breaks[line] then
    return true
  end
  return false
end

function iup.DebuggerRestoreLastListValue(list, last_value)
  -- maintain visual position of the selection on the list, not necessarily the same item
  last_value = tonumber(last_value)
  local count = tonumber(list.count)
  if last_value > count then
    list.value = count
  else
    list.value = last_value
  end
end

function table.count(t)
  local count = 0
  for i, v in pairs(t) do
    count = count + 1
  end
  return count
end

function iup.DebuggerUpdateBreakpointsListLines(list_break, m_filename, m_line, new_line)
  local count = tonumber(list_break.count)
  for i = 1, count do
    local filename = list_break["FILENAME" .. i]
    local line = tonumber(list_break["LINE" .. i])

    if m_filename == filename and m_line == line then
      -- update list (just title, index remains the same)
      list_break[i] = "Line " .. new_line .. " of \"" .. filename .. "\""
        
      -- update FILENAME#LINE
      list_break["LINE" .. i] = new_line
    end
  end
end

function iup.DebuggerRemoveBreakpointsListItem(list_break, m_filename, m_line)
  local count = tonumber(list_break.count)
  for i = 1, count do
    local filename = list_break["FILENAME" .. i]
    local line = tonumber(list_break["LINE" .. i])

    if m_filename == filename and m_line == line then
      iup.DebuggerRemoveBreakpointsListIndex(list_break, i)
    end
  end
end

function iup.DebuggerRemoveBreakpointsListIndex(list_break, index)
  -- update list
  list_break.removeitem = index
  local count = tonumber(list_break.count)

  -- update FILENAME#LINE
  for i = index, count do
    list_break["FILENAME"..i] = list_break["FILENAME"..(i+1)]
    list_break["LINE"..i] = list_break["LINE"..(i+1)]
  end
  list_break["FILENAME" .. count+1] = nil
  list_break["LINE" .. count+1] = nil
end

function iup.DebuggerRemoveBreakpoint(list_break, index)
  local last_value = list_break.value
  local line = tonumber(list_break["LINE" .. index])
  local filename = list_break["FILENAME" .. index]
  local count = tonumber(list_break.count)
  
  -- update multitext
  local multitext = iup.DebuggerGetMultitext(filename, true, false) -- find but do NOT open if not found
  if multitext then
    multitext["MARKERDELETE" .. (line - 1)] = 1 -- margin=1
  end

  -- update breakpoints table
  local file_breaks = debugger.breakpoints[filename]
  if file_breaks then 
    file_breaks[line] = nil
    if table.count(file_breaks) == 0 then
      debugger.breakpoints[filename] = nil
    end
  end

  -- update list and FILENAME#LINE
  iup.DebuggerRemoveBreakpointsListIndex(list_break, index)

  if count == 1 then
    -- update buttons, it is now empty
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_BREAK", "ACTIVE", "NO")
  else
    iup.DebuggerRestoreLastListValue(list_break, last_value)
  end
end

function iup.DebuggerAddBreakpoint(list_break, filename, line)
  local last_value = list_break.value

    -- update multitext
    local multitext = iup.DebuggerGetMultitext(filename, true, false) -- find but do NOT open if not found
    if multitext then
      multitext["MARKERADD" .. (line - 1)] = 1 -- margin=1
    end

  -- update breakpoints table
  local file_breaks = debugger.breakpoints[filename]
  if not file_breaks then 
    file_breaks = {} 
    debugger.breakpoints[filename] = file_breaks
  end
  file_breaks[line] = { } -- other breakpoint info can be saved here

  -- update list
  list_break.appenditem = "Line " .. line .. " of \"" .. filename .. "\""
  local count = tonumber(list_break.count)

  -- update FILENAME#LINE
  list_break["FILENAME"..count] = filename
  list_break["LINE"..count] = line

  if count == 1 then
    -- update buttons, it was empty
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_BREAK", "ACTIVE", "Yes")
  end

  -- select the new item
  list_break.value = count
end

function table_compare_lines(elem1, elem2)
  if (elem1.line < elem2.line) then
    return true
  else
    return false
  end
end

function iup.DebuggerBreakpointsChanged(multitext, start_lin, num_lin)
  local filename = multitext.filename
  local list_break = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_BREAK")
  local last_value = list_break.value

  local file_breaks = debugger.breakpoints[filename]
  if not file_breaks then 
    return
  end

  local new_file_breaks = {}

  for line, bp in pairs(file_breaks) do
    if line >= start_lin then
      if num_lin < 0 and line < start_lin - num_lin then
        -- removed lines and removed breakpoint

        -- update multitext
        multitext["MARKERDELETE" .. (start_lin - 1)] = 1 -- margin=1    -- all breakpoints inside the region are collapsed to the start_lin

        -- update breakpoints table
        -- simply don't copy to new_file_breaks

        -- update list and FILENAME#LINE
        iup.DebuggerRemoveBreakpointsListItem(list_break, filename, line)
      else
        -- added or removed lines, just change line in breakpoints

        -- update breakpoints table
        -- copy breakpoint at the new position
        new_file_breaks[line + num_lin] = bp

        -- update list and FILENAME#LINE
        iup.DebuggerUpdateBreakpointsListLines(list_break, filename, line, line + num_lin)
      end
    else
      -- no changes, just copy
      new_file_breaks[line] = bp
    end
  end

  -- update breakpoints table
  if table.count(new_file_breaks) == 0 then
    debugger.breakpoints[filename] = nil
  else
    debugger.breakpoints[filename] = new_file_breaks
  end

  local count = tonumber(list_break.count)
  if count == 0 then
    -- update buttons, it is now empty
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_BREAK", "ACTIVE", "NO")
  else
    iup.DebuggerRestoreLastListValue(list_break, last_value)
  end
end

function iup.DebuggerRemoveAllBreakpoints()
  local list_break = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_BREAK")

  -- update all multitext
  iup.DebuggerSetAttribAllMultitext("MARKERDELETEALL", "1") -- margin=1

  -- update list
  list_break.removeitem = "ALL"

  -- update breakpoints table
  debugger.breakpoints = {}
  
  -- update FILENAME#LINE
  local index = 1
  local filename = list_break["FILENAME" .. index]
  while filename do
    list_break["FILENAME" .. index] = nil
    list_break["LINE" .. index] = nil

    index = index + 1
    filename = list_break["FILENAME" .. index]
  end

  -- update buttons
  iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_BREAK", "ACTIVE", "NO")
end

function iup.DebuggerInitBreakpointsList(list_break)
  -- From FILENAME#LINE, initialize list and breakpoints table
  -- each multitext will be updated when created/loaded, see restoremarkers_cb

  list_break.removeitem = "ALL"
  debugger.breakpoints = {}

  local index = 1
  local filename = list_break["FILENAME" .. index]
  while filename do
    local line = tonumber(list_break["LINE" .. index])

    -- update list
    list_break[index] = "Line " .. line .. " of \"" .. filename .. "\""

    -- update breakpoints table
    local file_breaks = debugger.breakpoints[filename]
    if not file_breaks then 
      file_breaks = {} 
      debugger.breakpoints[filename] = file_breaks
    end
    file_breaks[line] = { } -- other breakpoint info can be saved here

    index = index + 1
    filename = list_break["FILENAME" .. index]
  end

  -- update buttons
  if index > 1 then
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_BREAK", "ACTIVE", "Yes")

    list_break.value = 1 -- select first item on list
  else
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_BREAK", "ACTIVE", "NO")
  end
end

function iup.DebuggerAddBreakpointList()
  local multitext = iup.DebuggerGetCurrentMultitext()
  local suggest_filename = multitext.filename
  if (not suggest_filename) then suggest_filename = "" end
  local suggest_lin, suggest_col = iup.TextConvertPosToLinCol(multitext, multitext.caretpos)

  local status, filename, line = iup.GetParam("Add Breakpoint", setparent_param_cb, "Filename: %s\nLine: %i[1,]\n", suggest_filename, suggest_lin + 1)

  if (status) then
    local list_break = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_BREAK")

    if iup.DebuggerHasBreakpoint(filename, line) then
      iup.MessageError(debugger.main_dialog, "Breakpoint already exists.")
      return
    end

    iup.DebuggerAddBreakpoint(list_break, filename, line)
  end
end

function iup.DebuggerBreaksListAction(list_break, index)
  local filename = list_break["FILENAME" .. index]
  local line = tonumber(list_break["LINE" .. index])
  
  iup.DebuggerSelectLine(filename, line, false) -- do not find
end

function iup.DebuggerBreaksListActivate(list_break, index)
  local filename = list_break["FILENAME" .. index]
  local line = tonumber(list_break["LINE" .. index])

  iup.DebuggerSelectLine(filename, line, true) -- find
end


------------------------------------- Locals -------------------------------------


function iup.DebuggerClearLocalVariablesTree()
  iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_LOCAL", "ACTIVE", "NO")
  iup.DebuggerSetDialogChildAttrib("DEBUG_SET_LOCAL", "ACTIVE", "NO")

  iup.DebuggerSetDialogChildAttrib("DEBUG_TREE_LOCAL", "DELNODE", "ALL")
end

function iup.DebuggerSetLocalVariable()
  local tree_local = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_LOCAL")
  local id = tree_local.value
  if (not id or tonumber(id) < 0) then
    iup.MessageError(debugger.main_dialog, "Select a variable on the list.")
    return
  end
  
  local userdata = iup.TreeGetUserId(tree_local, id)

  local name = userdata.name
  local value = userdata.value
  
  if (value == nil) then value = "nil" end
  local valueType = type(value)
  if valueType ~= "string" and valueType ~= "number" and valueType ~= "boolean" then
    iup.MessageError(debugger.main_dialog, "Can edit only strings, numbers and booleans.")
    return
  end

  local status, newValue = iup.GetParam("Set Local", setparent_param_cb, name.." = ".."%s{true, false and nil are translated to Lua values.}\n", tostring(value))

  if (status) then
    local bol = string.lower(newValue)
    if bol == "true" then
      newValue = true
    elseif bol == "false" then
      newValue = false
    elseif bol == "nil" then
      newValue = nil
    else
      local num = tonumber(newValue)
      if num then
        newValue = num
      end
    end
  
    local list_stack = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_STACK")
    local index = tonumber(list_stack.value)

    -- here there are 4 levels on top of the stack: 
    --   1-DebuggerSetLocalVariable, 
    --   2-LoopStep
    --   3-DebuggerLineHook, 
    --   4-DebuggerHookFunction
    local startLevel = 5
    local level = index - 1 + startLevel  -- this is the level of the function
    
    iup.DebuggerSetLocalValue(tree_local, level, id, newValue)
    iup.DebuggerSetLocalTreeItem(tree_local, id, name, newValue)
  end
end

function iup.DebuggerPrintLocalVariable()
  local tree_local = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_LOCAL")
  local id = tree_local.value

  iup.DebuggerShowConsoleTab()

  local userdata = iup.TreeGetUserId(tree_local, id)
  local pos = userdata.pos
  local value = userdata.value
  
  iup.ConsolePrint(tree_local["TITLE"..id] .. "  (pos="..pos..")")
  iup.ConsolePrintValue(value)
end

function iup.DebuggerPrintAllLocalVariables()
  local tree_local = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_LOCAL")
  local count = tonumber(tree_local.rootcount) -- print all at root only

  iup.DebuggerShowConsoleTab()

  local id = 0
  for i = 0, count-1 do
    local userdata = iup.TreeGetUserId(tree_local, id)
    local pos = userdata.pos
    local value = userdata.value

    iup.ConsolePrint(tree_local["TITLE"..id] .. "  (pos="..pos..")")
    iup.ConsolePrintValue(value)

    id = tree_local["NEXT"..id]
  end
end

function iup.DebuggerSetTreeTableValue(tree, name, id, newValue)
  local parentId = tree["parent"..id]
  local parentUdata = iup.TreeGetUserId(tree, parentId)
  local parent = parentUdata.value
  parent[name] = newValue
end

function iup.DebuggerSetLocalValue(tree_local, level, id, newValue)
  local userdata = iup.TreeGetUserId(tree_local, id)
  local depth = tonumber(tree_local["depth"..id])

  if depth > 0 then
    iup.DebuggerSetTreeTableValue(tree_local, userdata.name, id, newValue)
  else
    local pos = userdata.pos
    local s = string.sub(userdata.name, 1, 3)
    if s == ":: " then
      debug.setupvalue(tree_local.func, pos, newValue)
    else
     level = level + 1 -- this is the level inside this function
     debug.setlocal(level, pos, newValue)
    end
  end

  userdata.value = newValue
end

--[[  UNUSED
function iup.DebuggerGetLocal(tree_local, level, id)
  local name, value
  local userdata = iup.TreeGetUserId(tree_local, id)
  local pos = userdata.pos
  local tree_value = tree_local["TITLE"..id]
  local s = string.sub(tree_value, 1, 3)
  if s == ":: " then
    name, value = debug.getupvalue(tree_local.func, pos)
  else
    level = level + 1 -- this is the level inside this function
    name, value = debug.getlocal(level, pos)
  end
  return name, value
end
]]

function iup.DebuggerLocalVariablesTreeAction(tree_local, id)
  local userdata = iup.TreeGetUserId(tree_local, id)
  local valueType = type(userdata.value)
  if valueType == "string" or valueType == "number" or valueType == "boolean" then
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_LOCAL", "ACTIVE", "Yes")
  else
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_LOCAL", "ACTIVE", "No")
  end
end

function iup.DebuggerLocalVariablesBranchOpenAction(tree_local, id)
  local userdata = iup.TreeGetUserId(tree_local, id)
  local tableValue = userdata.value
  local pos = userdata.pos
  
  if type(tableValue) ~= "table" or userdata.expanded then
    return
  end
  
  local dummy_id = id + 1
  userdata.expanded = true

  id = dummy_id

  -- the same algorithm used in ConsolePrintTable
  local name
  local tmp = {}
  for name, value in ipairs(tableValue) do 
    id = iup.DebuggerAddLocalTreeItem(tree_local, id, name, value, pos)
    tmp[name] = true
  end
  for name, value in pairs(tableValue) do 
    if (not tmp[name]) then
      id = iup.DebuggerAddLocalTreeItem(tree_local, id, name, value, pos)
    end
  end

  iup.DebuggerSetDialogChildAttrib("DEBUG_TREE_LOCAL", "DELNODE"..dummy_id, "SELECTED")
end

function iup.DebuggerGetTreeTitle(tree, id, name, value)
  local depth = tonumber(tree["depth"..id])
  local pref = ""
  local suff = ""
  if depth and depth > 0 then
    pref = "["
    suff = "]"
  end

  local title = pref .. name .. suff .. " = "
  
  if type(value) == "string" then
    title = title .. "\"" .. value .. "\""
  else
    title = title .. tostring(value)
  end
  
  title = title .. " <" .. type(value) .. ">"
  
  return title
end

function iup.DebuggerSetLocalTreeItem(tree_local, id, name, value)
  tree_local["TITLE"..id] = iup.DebuggerGetTreeTitle(tree_local, id, name, value)
end

function iup.DebuggerAddLocalTreeItem(tree_local, id, name, value, pos)
  local userdata = {}
  local lastAddedNode

  if type(value) == "table" then
    tree_local["INSERTBRANCH"..id] = iup.DebuggerGetTreeTitle(tree_local, id, name, value)
    lastAddedNode = tree_local.lastaddnode
    
    tree_local["ADDLEAF"..lastAddedNode] = "dummy"
    userdata.expanded = false
  else
    tree_local["INSERTLEAF"..id] = iup.DebuggerGetTreeTitle(tree_local, id, name, value)
    lastAddedNode = tree_local.lastaddnode
  end
  
  userdata.name = name
  userdata.value = value
  userdata.pos = pos
  
  iup.TreeSetUserId(tree_local, lastAddedNode, userdata)
    
  return tonumber(lastAddedNode)
end

function iup.DebuggerUpdateLocalVariablesTree(level)
  local name, value
  local pos
  local id = -1

  level = level + 1 -- this is the level inside this function

  iup.DebuggerClearLocalVariablesTree()

  local tree_local = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_LOCAL")

  pos = 1
  name, value = debug.getlocal(level, pos)
  while name ~= nil do
    if string.sub(name, 1, 1) ~= "(" then  -- do not include internal variables (loop control variables, temporaries, etc).
      id = iup.DebuggerAddLocalTreeItem(tree_local, id, name, value, pos)
    end

    pos = pos + 1
    name, value = debug.getlocal(level, pos)
  end

  -- vararg (only for Lua >= 5.2, ignored in Lua 5.1)
  pos = -1
  name, value = debug.getlocal(level, pos)
  while name ~= nil do
    name = "vararg[" .. -pos .. "]"
    id = iup.DebuggerAddLocalTreeItem(tree_local, id, name, value, pos)

    pos = pos - 1
    name, value = debug.getlocal(level, pos)
  end

  local call = debug.getinfo(level, "uf")
  if call.nups > 0 then
    pos = 1
    tree_local.func = call.func
    name, value = debug.getupvalue(call.func, pos)
    while name ~= nil do
      name = ":: " .. name
      id = iup.DebuggerAddLocalTreeItem(tree_local, id, name, value, pos)

      pos = pos + 1
      name, value = debug.getupvalue(call.func, pos)
    end
  else
    tree_local.func = nil
  end

  if (id > 0) then
    iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_LOCAL", "ACTIVE", "Yes")
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_LOCAL", "ACTIVE", "Yes")

    tree_local.value = 1 -- select first item on list
  end
end

function iup.DebuggerShowTip(word, line)

  if debugger.debugState ~= DEBUG_PAUSED and debugger.debugState ~= DEBUG_ERRORPAUSED then
    return
  end

  local list_stack = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_STACK")
  local index = tonumber(list_stack.value)

  -- here there are 4 levels on top of the stack: 
  --   1-DebuggerShowTip, 
  --   2-LoopStep
  --   3-DebuggerLineHook, 
  --   4-DebuggerHookFunction
  local startLevel = 5
  local level = index - 1 + startLevel  -- this is the level of the function

  -- check if inside the current function
  local info = debug.getinfo(level, "SlL") -- linedefined, lastlinedefined, activelines
  if info.linedefined == 0 and info.lastlinedefined == 0 then
    if not info.activelines[line] then
      return
    end
  elseif line < info.linedefined or line > info.lastlinedefined then
    return
  end

  -- check for local
  local pos = 1
  local name, value = debug.getlocal(level, pos)
  while name ~= nil do
    if name == word then
      return tostring(value)
    end

    pos = pos + 1
    name, value = debug.getlocal(level, pos)
  end

  -- check for upvalues
  local call = debug.getinfo(level, "uf")
  if call.nups > 0 then
    pos = 1
    name, value = debug.getupvalue(call.func, pos)
    while name ~= nil do
      if name == word then
        return tostring(value)
      end

      pos = pos + 1
      name, value = debug.getupvalue(call.func, pos)
    end
  end

  -- check for global  
  if _G[word] then
    return tostring(_G[word])
  end
end


------------------------------------- Stack -------------------------------------


function iup.DebuggerStackListAction(list_stack, index)
  
  -- here there are 4 levels on top of the stack: 
  --   1-DebuggerStackListAction, 
  --   2-LoopStep
  --   3-DebuggerLineHook, 
  --   4-DebuggerHookFunction
  local startLevel = 5
  local level = index - 1 + startLevel  -- this is the level of the function
  
  iup.DebuggerUpdateLocalVariablesTree(level)
  
  local filename = list_stack["FILENAME" .. index]
  local line = list_stack["LINE" .. index]

  iup.DebuggerSelectLine(filename, tonumber(line), false) -- do not find
end

function iup.DebuggerStackListActivate(list_stack, index)
  local startLevel = 5
  local level = index - 1 + startLevel  -- this is the level of the function
  
  iup.DebuggerUpdateLocalVariablesTree(level)
  
  local filename = list_stack["FILENAME" .. index]
  local line = list_stack["LINE" .. index]
  local source = list_stack["SOURCE" .. index]

  iup.DebuggerSelectLine(filename, tonumber(line), true, source) -- find
end

function iup.DebuggerClearStackList()
  iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_LEVEL", "ACTIVE", "NO")

  iup.DebuggerSetDialogChildAttrib("DEBUG_LIST_STACK", "REMOVEITEM", "ALL")
end

function iup.DebuggerPrintStackLevel()
  local list_stack = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_STACK")
  local index = list_stack.value

  iup.DebuggerShowConsoleTab()

  local defined = list_stack["DEFINED" .. index]

  iup.ConsolePrint(list_stack[index] .. "  (level=" .. index..")")
  iup.ConsolePrint(defined)
end

function iup.DebuggerPrintAllStackLevel()
  local list_stack = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_STACK")
  local count = tonumber(list_stack.count)

  iup.DebuggerShowConsoleTab()

  for index = 1, count do
    local defined = list_stack["DEFINED" .. index]

    iup.ConsolePrint(list_stack[index] .. "  (level=" .. index..")")
    iup.ConsolePrint(defined)
  end
end

function iup.DebuggerUpdateStackList()
  local info, desc, defined
  
  -- here there are 3 levels on top of the stack: 
  --   1-DebuggerUpdateStackList, 
  --   2-DebuggerLineHook, 
  --   3-DebuggerHookFunction
  local startLevel = 4
  local level = startLevel

  iup.DebuggerClearStackList()

  local list_stack = iup.GetDialogChild(debugger.main_dialog, "DEBUG_LIST_STACK")
  
  info = debug.getinfo(level)--, "Snl") -- source, name, namewhat, what, currentline, linedefined
  while  info ~= nil do
    if info.what == "main" then
      desc = "<main>"
    elseif info.name and info.name ~= "" then
      desc = info.name
    else
      desc = "<noname>"
    end
    if _VERSION ~= "Lua 5.1" then
      local call = debug.getinfo(level, "uf")
      local params = ""
      local pos = 1
      local name, value = debug.getlocal(call.func, pos)
      while name ~= nil do
        params = params .. name .. ", "
        pos = pos + 1
        name, value = debug.getlocal(call.func, pos)
      end
      if params ~= "" then 
        if call.isvararg then
          params = params .. "..."
        else
          params = string.sub(params, 1, -3) -- remove last ", "
        end
        desc = desc .. "(" .. params .. ")"
      end
    end
    if info.namewhat ~= "" then
        desc = desc .. " <".. info.namewhat .. ">"
    end
    if info.currentline > 0 then
       desc = desc .. " at line " .. info.currentline
    end

    local filename
    if info.what == "C" then    
      defined = "  [Defined in C"
    else
      local s = string.sub(info.source, 1, 1)
      if s == "@" then
        filename = string.sub(info.source, 2)
        defined = "  [Defined in the file: \"" .. filename .. "\""
      else
        filename = string.sub(info.short_src, 2, -2)
        defined = "  [Defined in a " .. filename
      end
    end
    if info.linedefined > 0 then
       defined = defined .. ", line " .. info.linedefined .. "]"
    else
       defined = defined .. "]"
    end

    local index = (level - startLevel) + 1
    list_stack[index] = desc
    list_stack["DEFINED" .. index] = defined
    list_stack["FILENAME" .. index] = filename
    list_stack["SOURCE" .. index] = info.source
    list_stack["LINE" .. index] = info.currentline

    level = level + 1

    info = debug.getinfo(level)--, "Snl") -- source, name, namewhat, what, currentline, linedefined
  end
  
  if level > startLevel then
    iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_LEVEL", "ACTIVE", "YES")

    list_stack.value = 1 -- select first item on list (startLevel)
    iup.DebuggerUpdateLocalVariablesTree(startLevel)
  end
  
end


----------------------------  Globals       --------------------------

function iup.DebuggerIsExpression(name)
  if string.find(name, "[^%w_]") then -- finds any non alphanumeric character, but allows underscore
    return true
  else
    return false
  end
end

function iup.DebuggerGetGlobalNameFromTree(tree_global, id)
  local userdata = iup.TreeGetUserId(tree_global, id)
  return userdata.globalname
end

function iup.DebuggerGetGlobalValue(name)
  local value
  -- used only for root nodes, and only when debuger is active
  
  if iup.DebuggerIsExpression(name) then
    local cmd, msg = loadstring("return " .. name)
    if not cmd then
      value = "<Invalid Expression> " .. msg
    else
      local result = {pcall(cmd)}
      if result[1] then
        value = result[2]
      else
        value = "<Invalid Expression>" .. result[2]
      end
    end
  else
    value = _G[name]
  end

  return value
end

function iup.DebuggerGlobalsTreeAction(tree_global, id)
  local userdata = iup.TreeGetUserId(tree_global, id)
  local name = userdata.globalname
  if iup.DebuggerIsExpression(name) then
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "NO")
  else
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "YES")
  end
end

function iup.DebuggerGlobalVariablesBranchOpenAction(tree_global, id)
  -- can not expand when debugger is not active  
  if not iup.DebuggerIsActive() then
    return iup.IGNORE
  end
  
  local userdata = iup.TreeGetUserId(tree_global, id)
  local tableValue = userdata.value
  
  if type(tableValue) ~= "table" or userdata.expanded then
    return
  end
  
  local dummy_id = id + 1
  userdata.expanded = true

  id = dummy_id

  -- the same algorithm used in ConsolePrintTable
  local name
  local tmp = {}
  for name, value in ipairs(tableValue) do 
    id = iup.DebuggerAddGlobalsTreeItem(tree_global, id, name, value)
    tmp[name] = true
  end
  for name, value in pairs(tableValue) do 
    if (not tmp[name]) then
      id = iup.DebuggerAddGlobalsTreeItem(tree_global, id, name, value)
    end
  end

  iup.DebuggerSetDialogChildAttrib("DEBUG_TREE_GLOBAL", "DELNODE"..dummy_id, "SELECTED")
end

function iup.DebuggerInitGlobalsTree(tree_global)
  if not tree_global then 
    tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
  end

  local count = tonumber(tree_global.rootcount)

  local id = 0
  for i = 0, count-1 do
    local userdata = iup.TreeGetUserId(tree_global, id)
    local name = userdata.globalname
    tree_global["TITLE"..id] = name
    tree_global["IMAGE"..id] = "IUP_TreeEmpty"
    tree_global["IMAGEEXPANDED"..id] = "IUP_TreeEmpty"
    tree_global["DELNODE"..id] = "CHILDREN"

    id = tree_global["NEXT"..id]
  end

  tree_global.expandall = "NO"

  local count = tonumber(tree_global.rootcount)
  if (count > 0) then
    iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_GLOBAL", "ACTIVE", "Yes")
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "Yes")
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_GLOBAL", "ACTIVE", "Yes")

    tree_global.value = 1 -- select first item on list
    iup.DebuggerGlobalsTreeAction(tree_global, tree_global.value)
  else
    iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_GLOBAL", "ACTIVE", "NO")
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "NO")
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_GLOBAL", "ACTIVE", "NO")
  end
end

function iup.DebuggerUpdateGlobalsTree()
  -- this is called only during debug
  local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
  local count = tonumber(tree_global.rootcount) -- print all at root only

  -- clear all secondary branches (including "dummy")
  local id = 0
  for i = 0, count-1 do
    tree_global["DELNODE"..id] = "CHILDREN"
    id = tree_global["NEXT"..id]
  end
  
  id = 0
  for i = 0, count-1 do
    local userdata = iup.TreeGetUserId(tree_global, id)
    local name = userdata.globalname
    local value = iup.DebuggerGetGlobalValue(name)
    userdata.value = value
    userdata.expanded = false
    
    iup.DebuggerSetGlobalsTreeItem(tree_global, id, name, value)

    id = tree_global["NEXT"..id]
  end

  tree_global.expandall = "NO"
end

function iup.DebuggerSetGlobalsTreeItem(tree_global, id, name, value)
  tree_global["TITLE"..id] = iup.DebuggerGetTreeTitle(tree_global, id, name, value)
  
  if type(value) == "table" then
    tree_global["IMAGE"..id] = "IUP_TreePlus"
    tree_global["IMAGEEXPANDED"..id] = "IUP_TreeMinus"

    -- restore "dummy"
    tree_global["ADDLEAF"..id] = "dummy"
  else
    tree_global["IMAGE"..id] = "IUP_TreeEmpty"
    tree_global["IMAGEEXPANDED"..id] = "IUP_TreeEmpty"
  end
end

function iup.DebuggerAddGlobalsTreeItem(tree_global, id, name, value)
  local userdata = {}
  local lastAddedNode
  
  if type(value) == "table" then
    tree_global["INSERTBRANCH"..id] = iup.DebuggerGetTreeTitle(tree_global, id, name, value)
    lastAddedNode = tree_global.lastaddnode
    tree_global["IMAGE"..lastAddedNode] = "IUP_TreePlus"
    tree_global["IMAGEEXPANDED"..lastAddedNode] = "IUP_TreeMinus"
    
    tree_global["ADDLEAF"..lastAddedNode] = "dummy"
  else
    tree_global["INSERTBRANCH"..id] = iup.DebuggerGetTreeTitle(tree_global, id, name, value)
    lastAddedNode = tree_global.lastaddnode
    tree_global["IMAGE"..lastAddedNode] = "IUP_TreeEmpty"
    tree_global["IMAGEEXPANDED"..lastAddedNode] = "IUP_TreeEmpty"
  end
  
  userdata.globalname = name
  userdata.expanded = false
  userdata.value = value
  
  iup.TreeSetUserId(tree_global, lastAddedNode, userdata)
  
  return lastAddedNode
end

function iup.DebuggerAddGlobal(tree_global, name)
  local id = tonumber(tree_global.last0)
  if not id then id = -1 end
   
  if iup.DebuggerIsActive() then
    local value = iup.DebuggerGetGlobalValue(name)
    return iup.DebuggerAddGlobalsTreeItem(tree_global, id, name, value)
  else
    local userdata = {}
    
    userdata.globalname = name
    userdata.expanded = false
    
    tree_global["INSERTBRANCH"..id] = name
    local lastAddedNode = tree_global.lastaddnode
    tree_global["IMAGE"..lastAddedNode] = "IUP_TreeEmpty"
    tree_global["IMAGEEXPANDED"..lastAddedNode] = "IUP_TreeEmpty"
    
    iup.TreeSetUserId(tree_global, lastAddedNode, userdata)
    return lastAddedNode
  end
end

function iup.DebuggerAddGlobalVariable()
  local status, newName = iup.GetParam("Add Global", setparent_param_cb, "Name = ".."%s\n", "")
  if (status) then
    local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
    local count = tonumber(tree_global.rootcount)
   
    local lastAddedNode = iup.DebuggerAddGlobal(tree_global, newName)

    if (count == 0) then
      iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_GLOBAL", "ACTIVE", "Yes")
      iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "Yes")
      iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_GLOBAL", "ACTIVE", "Yes")
    end
    
    iup.DebuggerGlobalsTreeAction(tree_global, lastAddedNode)
  end
end

function iup.DebuggerRemoveGlobalVariable()
  local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
  local id = tree_global.value
  if (not id or tonumber(id) < 0) then
    iup.MessageError(debugger.main_dialog, "Select a variable or expression on the list.")
    return
  end
  
  -- get the root item
  local parentId = tree_global["parent"..id]
  while parentId do
    id = parentId
    parentId = tree_global["parent"..id]
  end
  
  -- select the next item
  local next_id = tree_global["next"..id]
  if next_id then
    tree_global.value = next_id
  else
    local previous_id = tree_global["previous"..id]
    if previous_id then
      tree_global.value = previous_id
    end
  end

  tree_global["DELNODE"..id] = "SELECTED"

  local count = tonumber(tree_global.rootcount)
  if (count == 0) then
    iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_GLOBAL", "ACTIVE", "No")
    iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "No")
    iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_GLOBAL", "ACTIVE", "No")
  else

    iup.DebuggerGlobalsTreeAction(tree_global, tree_global.value)
  end
end

function iup.DebuggerRemoveAllGlobalVariable()
  local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")

  tree_global["DELNODE"] = "ALL"

  iup.DebuggerSetDialogChildAttrib("DEBUG_PRINT_GLOBAL", "ACTIVE", "No")
  iup.DebuggerSetDialogChildAttrib("DEBUG_SET_GLOBAL", "ACTIVE", "No")
  iup.DebuggerSetDialogChildAttrib("DEBUG_REMOVE_GLOBAL", "ACTIVE", "No")
end

function iup.DebuggerSetGlobalVariable()
  local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
  local id = tree_global.value
  if (not id or tonumber(id) < 0) then
    iup.MessageError(debugger.main_dialog, "Select a variable on the list.")
    return
  end
  
  local userdata = iup.TreeGetUserId(tree_global, id)
  
  local name = userdata.globalname

  if iup.DebuggerIsExpression(name) then
    iup.MessageError(debugger.main_dialog, "Can not change expressions values.")
    return
  end
  
  local value
  local depth = tonumber(tree_global["depth"..id])
  if depth > 0 then
    value = userdata.value
  else
    value = iup.DebuggerGetGlobalValue(name)
  end

  if (value == nil) then value = "nil" end
  local valueType = type(value)
  if valueType ~= "string" and valueType ~= "number" and valueType ~= "boolean" then
    iup.MessageError(debugger.main_dialog, "Can edit only strings, numbers and booleans.")
    return
  end

  local status, newValue = iup.GetParam("Set Global", setparent_param_cb, name.." = ".."%s{true, false and nil are translated to Lua values.}\n", tostring(value))

  if (status) then
    local bol = string.lower(newValue)
    if bol == "true" then
      newValue = true
    elseif bol == "false" then
      newValue = false
    elseif bol == "nil" then
      newValue = nil
    else
      local num = tonumber(newValue)
      if num then
        newValue = num
      end
    end

    if depth > 0 then
      iup.DebuggerSetTreeTableValue(tree_global, name, id, newValue)
    else
      _G[name] = newValue
    end

    if iup.DebuggerIsActive() then
      iup.DebuggerSetGlobalsTreeItem(tree_global, id, name, newValue)
    else
      tree_global["TITLE"..id] = name
      tree_global["IMAGE"..id] = "IUP_TreeEmpty"
      tree_global["IMAGEEXPANDED"..id] = "IUP_TreeEmpty"
    end
  end
end

function iup.DebuggerPrintGlobalVariable()
  local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
  local id = tree_global.value

  iup.DebuggerShowConsoleTab()
  
  local userdata = iup.TreeGetUserId(tree_global, id)
  
  local value
  local depth = tonumber(tree_global["depth"..id])
  if depth > 0 then
    value = userdata.value
  else
    value = iup.DebuggerGetGlobalValue(userdata.globalname)
  end

  iup.ConsolePrint(tree_global["TITLE"..id])
  iup.ConsolePrintValue(value)
end

function iup.DebuggerPrintAllGlobalVariables()
  local tree_global = iup.GetDialogChild(debugger.main_dialog, "DEBUG_TREE_GLOBAL")
  local count = tonumber(tree_global.rootcount) -- print all at root only

  iup.DebuggerShowConsoleTab()

  local id = 0
  for i = 0, count-1 do
    local userdata = iup.TreeGetUserId(tree_global, id)
    local value = iup.DebuggerGetGlobalValue(userdata.globalname)

    iup.ConsolePrint(tree_global["TITLE"..id])
    iup.ConsolePrintValue(value)

    id = tree_global["NEXT"..id]
  end
end


----------------------------  Debug State       --------------------------


function iup.DebuggerGetFuncLevel()
-- level 0 is the current function (getinfo itself); 
-- level 1 is the function that called getinfo (DebuggerGetFuncLevel)
  local func_level = 1
  repeat 
    func_level = func_level + 1
  until debug.getinfo(func_level, "l") == nil  -- only current line, default is all info
  return func_level - 1
end

function iup.DebuggerUpdateState(filename, line)
  if debugger.debugState == DEBUG_STEP_OUT then
    if debugger.stepFuncState == FUNC_STATE_OUTSIDE then
      iup.DebuggerSetState(DEBUG_PAUSED)
      
      debugger.stepFuncState = FUNC_STATE_OUTSIDE
      debugger.stepFuncLevel = debugger.currentFuncLevel
    end
  elseif debugger.debugState == DEBUG_STEP_INTO or
      (debugger.debugState == DEBUG_STEP_OVER and debugger.stepFuncState == FUNC_STATE_OUTSIDE) or
      (debugger.debugState ~= DEBUG_PAUSED and iup.DebuggerHasBreakpoint(filename, line)) then
      iup.DebuggerSetState(DEBUG_PAUSED)
  end
end

function iup.DebuggerErrorMessage(message)
  local dlg = iup.messagedlg
  {
    parentdialog = debugger.main_dialog,
    dialogtype = "ERROR",
    buttons = "OKCANCEL",
    title = "Lua Error!",
  }
  
  dlg.value = message .. "\n\nWould you like to debug at error location?"
    
  dlg:popup(iup.CENTERPARENT, iup.CENTERPARENT)
  local ret = dlg.buttonresponse
  dlg:destroy()

  if ret == "1" then
    return true
  else
    return false
  end
end

function iup.DebuggerForcePause(filename, currentline, source)
  iup.DebuggerSetState(DEBUG_ERRORPAUSED)

  iup.DebuggerHighlightLine(filename, currentline, source)

  iup.DebuggerUpdateGlobalsTree()

  iup.DebuggerUpdateStackList()

  while debugger.debugState == DEBUG_ERRORPAUSED do
    local ret = iup.LoopStep()
    if ret == iup.CLOSE then
      iup.ExitLoop() -- repost to MainLoop
    end
  end

  -- in any change of state, simply stop the debugger
  iup.DebuggerSetState(DEBUG_INACTIVE)
  iup.DebuggerCloseTemporary()
end

function iup.DebuggerTraceBack(msg)
  if not msg then
    return
  end

  -- hook is always at level 2 when called
  local info = debug.getinfo(2, "Sl") -- what, source, currentline
  local s = string.sub(info.source, 1, 1)
  local filename
  if s == "@" then
    filename = string.sub(info.source, 2)
  else
    filename = string.sub(info.short_src, 2, -2)
  end

  iup._TRACEBACK = nil
  debug.sethook() -- turns off the hook

-- NOT necessary
-- msg = debug.traceback(msg)

  iup.ConsolePrint("Lua Error:")
  iup.ConsolePrint(msg)

  if iup.DebuggerErrorMessage(msg) then
    iup.DebuggerForcePause(filename, info.currentline, info.source)
  end
end

function iup.DebuggerLineHook(filename, line, source)
  debugger.currentFuncLevel = iup.DebuggerGetFuncLevel()

  iup.DebuggerUpdateState(filename, line)
  
  if debugger.debugState == DEBUG_PAUSED then
  
    iup.DebuggerHighlightLine(filename, line, source)
    
    iup.DebuggerUpdateGlobalsTree()
    
    iup.DebuggerUpdateStackList()
    
    debug.sethook() -- turns off the hook
    
    while debugger.debugState == DEBUG_PAUSED do
      local ret = iup.LoopStep()
      if ret == iup.CLOSE then
        iup.ExitLoop() -- repost to MainLoop
      end
    end
    
    if iup.DebuggerIsActive() then
      debug.sethook(iup.DebuggerHookFunction, "lcr") -- restore the hook if still active
    end
  end
  
  if debugger.debugState == DEBUG_STOPPED then
    iup.DebuggerEndDebug(true)
  end
end

function iup.DebuggerCallHook()
  if debugger.debugState == DEBUG_STEP_OVER then
    if debugger.stepFuncLevel == 0 then
      local func_level = iup.DebuggerGetFuncLevel()
      debugger.stepFuncState = FUNC_STATE_INSIDE
      debugger.stepFuncLevel = func_level
    end
  end
end

function iup.DebuggerReturnHook(filename, what)
  if filename == debugger.startFile and what == "main" then
    iup.DebuggerSetState(DEBUG_INACTIVE)
  elseif debugger.debugState == DEBUG_STEP_OUT or debugger.debugState == DEBUG_STEP_OVER then
    local func_level = iup.DebuggerGetFuncLevel()
    if debugger.stepFuncLevel == func_level then
      debugger.stepFuncState = FUNC_STATE_OUTSIDE
      debugger.stepFuncLevel = 0
    end
  end
end

function iup.DebuggerHookFunction(event, line)
  -- how many levels we have before the hook was invoked?
  -- hook is always at level 2 when called
  -- Inside a hook, you can call getinfo with level 2 to get more information about the running function
  local info = debug.getinfo(2, "S") -- what, source
  local s = string.sub(info.source, 1, 1)
  local filename
  if s == "@" then
    filename = string.sub(info.source, 2)
  else
    filename = string.sub(info.short_src, 2, -2)
  end

  if iup.DebuggerIsActive() then
    if event == "call" then
      iup.DebuggerCallHook()
    elseif event == "return" then
      iup.DebuggerReturnHook(filename, info.what)
    elseif event == "line" then
      iup.DebuggerLineHook(filename, line, info.source)
    end
  end

  local ret = iup.LoopStep()
  if ret == iup.CLOSE then
    iup.ExitLoop() -- repost to MainLoop
  end
end

function iup.DebuggerStartDebug(filename)
  debugger.startFile = filename

  iup.DebuggerShowDebugTab()

  iup.ConsolePrint("-- Debug start")

  iup.DebuggerSetState(DEBUG_ACTIVE)

  debug.sethook(iup.DebuggerHookFunction, "lcr")

  iup._TRACEBACK = iup.DebuggerTraceBack
end

function iup.DebuggerEndDebug(stop)
  iup._TRACEBACK = nil

  debug.sethook() -- turns off the hook

  iup.DebuggerSetState(DEBUG_INACTIVE)

  iup.DebuggerShowConsoleTab()

  if stop then
    iup.ConsolePrint("-- Debug stop!")
    error() -- abort processing, no error message
  else
    iup.ConsolePrint("-- Debug finish")
  end

  iup.DebuggerCloseTemporary()
end

function iup.DebuggerExit()
  if iup.DebuggerIsActive() then
    iup.DebuggerEndDebug(true) -- make a stop
  end
end
