/******************************************************************************
 * Copyright (c) 2000-2025 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   
 *   Baranyi, Botond – initial implementation
 *
 ******************************************************************************/

#include "Debugger.hh"
#include "DebugCommands.hh"
#include "Communication.hh"
#include "../common/pattern.hh"
#include "Param_Types.hh"
#include "DebuggerUI.hh"
#include <unistd.h>
#include <pwd.h>

//////////////////////////////////////////////////////
////////////////// TTCN3_Debugger ////////////////////
//////////////////////////////////////////////////////

#define BUFFER_INCREASE 100

TTCN3_Debugger ttcn3_debugger;

void TTCN3_Debugger::switch_state(const char* p_state_str)
{
  if (!strcmp(p_state_str, "on")) {
    if (active) {
      print(DRET_NOTIFICATION, "The debugger is already switched on.");
    }
    else {
      active = TRUE;
      print(DRET_SETTING_CHANGE, "Debugger switched on.");
    }
  }
  else if(!strcmp(p_state_str, "off")) {
    if (!active) {
      print(DRET_NOTIFICATION, "The debugger is already switched off.");
    }
    else {
      active = FALSE;
      print(DRET_SETTING_CHANGE, "Debugger switched off.");
    }
  }
  else {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'on' or 'off'.");
  }
}

static boolean is_numeric(const char* p_str)
{
  size_t len = strlen(p_str);
  for (size_t i = 0; i < len; ++i) {
    if (p_str[i] < '0' || p_str[i] > '9') {
      return FALSE;
    }
  }
  return TRUE;
}

void TTCN3_Debugger::set_breakpoint(const char* p_module, const char* p_location,
                                    const char* batch_file)
{
  int line = 0;
  char* function = NULL;
  if (is_numeric(p_location)) {
    // it's a line breakpoint
    line = strtol(p_location, NULL, 10);
  }
  else {
    // it's a function breakpoint
    function = mcopystr(p_location);
  }
  char* loc_str = function != NULL ? mprintf("function '%s'", function) :
    mprintf("line %d", line);
  size_t pos = find_breakpoint(p_module, line, function);
  if (pos == breakpoints.size()) {
    breakpoint_t bp;
    bp.module = mcopystr(p_module);
    bp.line = line;
    bp.function = function;
    bp.batch_file = batch_file != NULL ? mcopystr(batch_file) : NULL;
    breakpoints.push_back(bp);
    print(DRET_SETTING_CHANGE, "Breakpoint added in module '%s' at %s %s%s%s.",
      p_module, loc_str,
      batch_file != NULL ? "with batch file '" : "with no batch file",
      batch_file != NULL ? batch_file : "", batch_file != NULL ? "'" : "");
  }
  else {
    Free(function);
    if (breakpoints[pos].batch_file != NULL) {
      if (batch_file != NULL) {
        if (!strcmp(batch_file, breakpoints[pos].batch_file)) {
          print(DRET_NOTIFICATION, "Breakpoint already set in module '%s' at "
            "%s with batch file '%s'.",
            p_module, loc_str, batch_file);
        }
        else {
          print(DRET_SETTING_CHANGE, "Batch file was changed from '%s' to '%s' for "
            "breakpoint in module '%s' at %s.", breakpoints[pos].batch_file,
            batch_file, p_module, loc_str);
        }
      }
      else {
        print(DRET_SETTING_CHANGE, "Batch file '%s' removed from breakpoint in "
          "module '%s' at %s.", breakpoints[pos].batch_file, p_module, loc_str);
      }
      Free(breakpoints[pos].batch_file);
    }
    else {
      if (batch_file != NULL) {
        print(DRET_SETTING_CHANGE, "Batch file '%s' added to breakpoint in module "
          "'%s' at %s.", batch_file, p_module, loc_str);
      }
      else {
        print(DRET_NOTIFICATION, "Breakpoint already set in module '%s' at %s "
          "with no batch file.", p_module, loc_str);
      }
    }
    breakpoints[pos].batch_file = batch_file != NULL ? mcopystr(batch_file) : NULL;
  }
  Free(loc_str);
}

void TTCN3_Debugger::remove_breakpoint(const char* p_module, const char* p_location)
{
  boolean all_breakpoints = !strcmp(p_module, "all");
  if (p_location != NULL) {
    if (!strcmp(p_location, "all")) {
      boolean found = FALSE;
      for (size_t i = breakpoints.size(); i > 0; --i) {
        if (!strcmp(breakpoints[i - 1].module, p_module)) {
          found = TRUE;
          Free(breakpoints[i - 1].module);
          Free(breakpoints[i - 1].batch_file);
          breakpoints.erase_at(i - 1);
        }
      }
      if (found) {
        print(DRET_SETTING_CHANGE, "Removed all breakpoints in module '%s'.", p_module);
      }
      else {
        print(DRET_NOTIFICATION, "No breakpoints found in module '%s'.", p_module);
      }
      return;
    }
    else {
      if (all_breakpoints) {
        print(DRET_NOTIFICATION, "Unexpected 2nd argument, when the first "
          "argument is 'all'.");
        return;
      }
      int line = 0;
      char* function = NULL;
      if (is_numeric(p_location)) {
        // it's a line breakpoint
        line = strtol(p_location, NULL, 10);
      }
      else {
        // it's a function breakpoint
        function = mcopystr(p_location);
      }
      char* loc_str = function != NULL ? mprintf("function '%s'", function) :
        mprintf("line %d", line);
      size_t pos = find_breakpoint(p_module, line, function);
      if (pos != breakpoints.size()) {
        Free(breakpoints[pos].module);
        Free(breakpoints[pos].function);
        Free(breakpoints[pos].batch_file);
        breakpoints.erase_at(pos);
        print(DRET_SETTING_CHANGE, "Breakpoint removed in module '%s' from %s.",
          p_module, loc_str);
      }
      else {
        print(DRET_NOTIFICATION, "No breakpoint found in module '%s' at %s.",
          p_module, loc_str);
      }
      Free(loc_str);
      return;
    }
  }
  else if (!all_breakpoints) {
    print(DRET_NOTIFICATION, "2 arguments expected, when the first argument is "
      "not 'all'.");
    return;
  }
  // delete all breakpoints if we got this far
  if (breakpoints.empty()) {
    print(DRET_NOTIFICATION, "No breakpoints found.");
  }
  else {
    for (size_t i = 0; i < breakpoints.size(); ++i) {
      Free(breakpoints[i].module);
      Free(breakpoints[i].function);
      Free(breakpoints[i].batch_file);
    }
    breakpoints.clear();
    print(DRET_SETTING_CHANGE, "Removed all breakpoints.");
  }
}

void TTCN3_Debugger::set_automatic_breakpoint(const char* p_event_str,
                                              const char* p_state_str,
                                              const char* p_batch_file)
{
  boolean new_state;
  if (!strcmp(p_state_str, "on")) {
    new_state = TRUE;
  }
  else if(!strcmp(p_state_str, "off")) {
    new_state = FALSE;
  }
  else {
    print(DRET_NOTIFICATION, "Argument 2 is invalid. Expected 'on' or 'off'.");
    return;
  }
  const char* sbp_type_str;
  boolean state_changed;
  char** old_batch_file_ptr;
  if (!strcmp(p_event_str, "fail")) {
    state_changed = (fail_behavior.trigger != new_state);
    fail_behavior.trigger = new_state;
    old_batch_file_ptr = &fail_behavior.batch_file;
    sbp_type_str = "fail verdict";
  }
  else if (!strcmp(p_event_str, "error")) {
    state_changed = (error_behavior.trigger != new_state);
    error_behavior.trigger = new_state;
    old_batch_file_ptr = &error_behavior.batch_file;
    sbp_type_str = "error verdict";
  }
  else {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'error' or 'fail'.");
    return;
  }
  if (state_changed) {
    print(DRET_SETTING_CHANGE, "Automatic breakpoint at %s switched %s%s%s%s.",
      sbp_type_str, new_state ? "on" : "off",
      new_state ? (p_batch_file != NULL ? " with batch file '" : " with no batch file") : "",
      (p_batch_file != NULL && new_state) ? p_batch_file : "",
      (p_batch_file != NULL && new_state) ? "'" : "");
  }
  else {
    if (new_state) {
      if (*old_batch_file_ptr != NULL) {
        if (p_batch_file != NULL) {
          if (!strcmp(p_batch_file, *old_batch_file_ptr)) {
            print(DRET_NOTIFICATION, "Automatic breakpoint at %s was already "
              "switched on with batch file '%s'.", sbp_type_str, p_batch_file);
          }
          else {
            print(DRET_SETTING_CHANGE, "Batch file was changed from '%s' to '%s' "
              "for automatic breakpoint at %s.", *old_batch_file_ptr, p_batch_file,
              sbp_type_str);
          }
        }
        else {
          print(DRET_SETTING_CHANGE, "Batch file '%s' removed from automatic "
            "breakpoint at %s.", *old_batch_file_ptr, sbp_type_str);
        }
      }
      else {
        if (p_batch_file != NULL) {
          print(DRET_SETTING_CHANGE, "Batch file '%s' added to automatic breakpoint "
            "at %s.", p_batch_file, sbp_type_str);
        }
        else {
          print(DRET_NOTIFICATION, "Automatic breakpoint at %s was already "
              "switched on with no batch file.", sbp_type_str);
        }
      }
    }
    else {
      print(DRET_NOTIFICATION, "Automatic breakpoint at %s was already switched off.");
    }
  }
  Free(*old_batch_file_ptr);
  *old_batch_file_ptr = p_batch_file != NULL ? mcopystr(p_batch_file) : NULL;
}

void TTCN3_Debugger::print_settings()
{
  // on/off switch
  add_to_result("Debugger is switched %s.\n", active ? "on" : "off");
  // output
  char* final_file_name = finalize_file_name(output_file_name);
  char* file_str = output_file != NULL ? mprintf("file '%s'", final_file_name) : NULL;
  Free(final_file_name);
  add_to_result("Output is printed to %s%s%s.\n",
    send_to_console ? "the console" : "",
    (send_to_console && output_file != NULL) ? " and to " : "",
    output_file != NULL ? file_str : "");
  Free(file_str);
  // global batch file
  add_to_result("Global batch file%s%s.\n", global_batch_file != NULL ? ": " : "",
    global_batch_file != NULL ? global_batch_file : " not set");
  // function call data configuration
  add_to_result("Function call data ");
  if (function_calls.cfg == CALLS_TO_FILE) {
    char* final_file_name2 = finalize_file_name(function_calls.file.name);
    add_to_result("sent to file '%s'.\n", final_file_name2);
    Free(final_file_name2);
  }
  else {
    add_to_result("buffer size: ");
    if (function_calls.cfg == CALLS_STORE_ALL) {
      add_to_result("infinite.\n");
    }
    else {
      add_to_result("%d.\n", function_calls.buffer.size);
    }
  }
  // user breakpoints
  if (breakpoints.empty()) {
    add_to_result("No user breakpoints.\n");
  }
  else {
    add_to_result("User breakpoints:\n");
    for (size_t i = 0; i < breakpoints.size(); ++i) {
      const breakpoint_t& bp = breakpoints[i];
      add_to_result("%s ", bp.module);
      if (bp.function == NULL) {
        add_to_result("%d", bp.line);
      }
      else {
        add_to_result("%s", bp.function);
      }
      if (bp.batch_file != NULL) {
        add_to_result(" %s", bp.batch_file);
      }
      add_to_result("\n");
    }
  }
  // automatic breakpoints
  add_to_result("Automatic breakpoints:\nerror %s %s\nfail %s %s",
    error_behavior.trigger ? "on" : "off",
    error_behavior.batch_file != NULL ? error_behavior.batch_file : "",
    fail_behavior.trigger ? "on" : "off",
    fail_behavior.batch_file != NULL ? fail_behavior.batch_file : "");
}

#define STACK_LEVEL (stack_level >= 0) ? (size_t)stack_level : (call_stack.size() - 1)

void TTCN3_Debugger::print_call_stack()
{
  for (size_t i = call_stack.size(); i != 0; --i) {
    add_to_result("%d.\t", (int)call_stack.size() - (int)i + 1);
    call_stack[i - 1].function->print_function();
    if (i - 1 == (STACK_LEVEL)) {
      // mark the active stack level with an asterisk
      add_to_result("*");
    }
    if (i != 1) {
      add_to_result("\n");
    }
  }
}

void TTCN3_Debugger::set_stack_level(int new_level)
{
  if (!halted) {
    print(DRET_NOTIFICATION, "Stack level can only be set if test execution is halted.");
  }
  else if (new_level <= 0 || (size_t)new_level > call_stack.size()) {
    print(DRET_NOTIFICATION, "Invalid new stack level. Expected 1 - %d.",
      (int)call_stack.size());
  }
  else {
    stack_level = (int)call_stack.size() - new_level;
    call_stack[stack_level].function->print_function();
    print(DRET_NOTIFICATION, "Stack level set to:\n%d.\t%s", new_level, command_result);
    Free(command_result);
    command_result = NULL;
  }
}

void TTCN3_Debugger::print_variable(const char* p_var_name)
{
  const variable_t* var = call_stack[STACK_LEVEL].function->find_variable(p_var_name);
  if (var != NULL) {
    add_to_result("[%s] %s%s%s := %s", var->type_name,
      var->module != NULL ? var->module : "", var->module != NULL ? "." : "",
      var->name, (const char*)var->print_function(*var));
  }
  else {
    add_to_result("Variable '%s' not found.", p_var_name);
  }
}

void TTCN3_Debugger::overwrite_variable(const char* p_var_name,
                                        int p_value_element_count,
                                        char** p_value_elements)
{
  variable_t* var = call_stack[STACK_LEVEL].function->find_variable(p_var_name);
  if (var != NULL) {
    if (var->set_function == NULL) {
      print(DRET_NOTIFICATION, "Constant variables cannot be overwritten.");
    }
    else {
      char* new_value_str = NULL;
      for (int i = 0; i < p_value_element_count; ++i) {
        if (i != 0) {
          new_value_str = mputc(new_value_str, ' ');
        }
        new_value_str = mputstr(new_value_str, p_value_elements[i]);
      }
      Module_Param* parsed_mp = process_config_debugger_value(new_value_str);
      // an error message has already been displayed if parsed_mp is NULL
      if (parsed_mp != NULL) {
        try {
          Debugger_Value_Parsing debug_value_parsing;
          boolean handled = var->set_function(*var, *parsed_mp);
          if (!handled) {
            print(DRET_NOTIFICATION, "Variables of type '%s' cannot be overwritten.",
              var->type_name);
          }
          else {
            add_to_result("[%s] %s := %s", var->type_name, var->name,
              (const char*)var->print_function(*var));
          }
        }
        catch (const TC_Error&) {
          // do nothing, an error message has already been displayed in this case
        }
        delete parsed_mp;
      }
    }
  }
  else {
    print(DRET_NOTIFICATION, "Variable '%s' not found.", p_var_name);
  }
}

void TTCN3_Debugger::clean_up_function_calls()
{
  if (function_calls.cfg == CALLS_TO_FILE) {
    if (!TTCN_Runtime::is_hc()) {
      fclose(function_calls.file.ptr);
    }
    Free(function_calls.file.name);
  }
  else if (!TTCN_Runtime::is_hc()) {
    if (function_calls.buffer.size != 0) {
      if (function_calls.buffer.end != -1) {
        for (int i = function_calls.buffer.start;
             i != function_calls.buffer.end;
             i = (i + 1) % function_calls.buffer.size) {
          Free(function_calls.buffer.ptr[i]);
        }
        Free(function_calls.buffer.ptr[function_calls.buffer.end]);
      }
      Free(function_calls.buffer.ptr);
    }
  }
}

void TTCN3_Debugger::configure_function_calls(const char* p_config,
                                              const char* p_file_name)
{
  function_call_data_config_t cfg;
  // check the command's parameters before actually changing anything
  if (strcmp(p_config, "file") == 0) {
    cfg = CALLS_TO_FILE;
  }
  else if (strcmp(p_config, "all") == 0) {
    cfg = CALLS_STORE_ALL;
  }
  else if (is_numeric(p_config)) {
    cfg = CALLS_RING_BUFFER;
  }
  else {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'file', 'all' or "
      "ring buffer size.");
    return;
  }
  FILE* new_fp = NULL;
  char* final_file_name = NULL;
  boolean same_setting = FALSE;
  int new_size = 0;
  switch (cfg) {
  case CALLS_TO_FILE:
    if (p_file_name != NULL) {
      if (function_calls.cfg == CALLS_TO_FILE &&
          strcmp(p_file_name, function_calls.file.name) == 0) {
        // don't reopen it if it's the same file as before
        same_setting = TRUE;
      }
      else if (!TTCN_Runtime::is_hc()) {
        // don't open any files on HCs, just store settings for future PTCs
        final_file_name = finalize_file_name(p_file_name);
        new_fp = fopen(final_file_name, TTCN_Runtime::is_mtc() ? "w" : "a");
        if (new_fp == NULL) {
          print(DRET_NOTIFICATION, "Failed to open file '%s' for writing.", final_file_name);
          Free(final_file_name);
          return;
        }
      }
    }
    else {
      print(DRET_NOTIFICATION, "Argument 2 (file name) is missing.");
      return;
    }
    break;
  case CALLS_RING_BUFFER:
    new_size = strtol(p_config, NULL, 10);
    same_setting = function_calls.cfg == CALLS_RING_BUFFER &&
      function_calls.buffer.size == new_size;
    break;
  case CALLS_STORE_ALL:
    same_setting = function_calls.cfg == CALLS_STORE_ALL;
    break;
  }
  if (!same_setting) {
    clean_up_function_calls();
    function_calls.cfg = cfg;
    switch (cfg) {
    case CALLS_TO_FILE:
      function_calls.file.name = mcopystr(p_file_name);
      if (!TTCN_Runtime::is_hc()) {
        function_calls.file.ptr = new_fp;
      }
      break;
    case CALLS_RING_BUFFER:
      function_calls.buffer.size = new_size;
      // no break
    case CALLS_STORE_ALL:
      function_calls.buffer.start = 0;
      function_calls.buffer.end = -1;
      if (new_size != 0 && !TTCN_Runtime::is_hc()) {
        // don't allocate buffers on HCs, just store settings for future PTCs
        function_calls.buffer.ptr = (char**)Malloc(new_size * sizeof(char*));
      }
      else {
        function_calls.buffer.ptr = NULL;
      }
      break;
    }
  }
  switch (cfg) {
  case CALLS_TO_FILE:
    print(DRET_SETTING_CHANGE, "Debugger %sset to not store function call data, but to "
      "send them to file '%s'.", same_setting ? "was already " : "", final_file_name);
    Free(final_file_name);
    break;
  case CALLS_RING_BUFFER:
    if (new_size == 0) {
      print(DRET_SETTING_CHANGE, "Debugger %sset to not store function call data.",
        same_setting ? "was already " : "");
    }
    else {
      print(DRET_SETTING_CHANGE, "Debugger %sset to store only the last %d function calls.",
        same_setting ? "was already " : "", new_size);
    }
    break;
  case CALLS_STORE_ALL:
    print(DRET_SETTING_CHANGE, "Debugger %sset to store all function call data.",
      same_setting ? "was already " : "");
    break;
  }
}

void TTCN3_Debugger::print_function_calls(const char* p_amount)
{
  if (function_calls.cfg == CALLS_TO_FILE || function_calls.buffer.size == 0 ||
      function_calls.buffer.end == -1) {
    print(DRET_NOTIFICATION, "No function calls are stored.");
    return;
  }
  int amount;
  int limit = (function_calls.cfg == CALLS_RING_BUFFER &&
               function_calls.buffer.start == (function_calls.buffer.end + 1) %
               function_calls.buffer.size) ?
    function_calls.buffer.size : function_calls.buffer.end + 1;
  boolean invalid_arg = FALSE;
  if (p_amount == NULL || strcmp(p_amount, "all") == 0) {
    amount = limit;
  }
  else if (is_numeric(p_amount)) {
    amount = strtol(p_amount, NULL, 10);
    if (amount == 0) {
      invalid_arg = TRUE;
    }
    else if (amount > limit) {
      amount = limit;
    }
  }
  else {
    invalid_arg = TRUE;
  }
  if (invalid_arg) {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'all' or non-zero "
      "integer value (number of calls).");
    return;
  }
  for (int i = (function_calls.buffer.end - amount + function_calls.buffer.size + 1) %
       function_calls.buffer.size; amount > 0;
       i = (i + 1) % function_calls.buffer.size, --amount) {
    add_to_result(function_calls.buffer.ptr[i]);
    if (amount > 1) {
      add_to_result("\n");
    }
  }
}

void TTCN3_Debugger::set_output(const char* p_output_type, const char* p_file_name)
{
  FILE* new_fp = NULL;
  boolean file, console;
  boolean same_file = FALSE;
  char* final_file_name = NULL;
  // check the command's parameters before actually changing anything
  if (!strcmp(p_output_type, "console")) {
    file = FALSE;
    console = TRUE;
  }
  else if (!strcmp(p_output_type, "file")) {
    file = TRUE;
    console = FALSE;
  }
  else if (!strcmp(p_output_type, "both")) {
    file = TRUE;
    console = TRUE;
  }
  else {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'console', 'file' or 'both'.");
    return;
  }
  if (file) {
    if (p_file_name == NULL) {
      print(DRET_NOTIFICATION, "Argument 2 (output file name) is missing.");
      return;
    }
    if (output_file_name != NULL && !strcmp(p_file_name, output_file_name)) {
      // don't reopen it if it's the same file as before
      same_file = TRUE;
    }
    else if (!TTCN_Runtime::is_hc()) {
      // don't open any files on HCs, just store settings for future PTCs
      final_file_name = finalize_file_name(p_file_name);
      new_fp = fopen(final_file_name, TTCN_Runtime::is_mtc() ? "w" : "a");
      if (new_fp == NULL) {
        print(DRET_NOTIFICATION, "Failed to open file '%s' for writing.", final_file_name);
        Free(final_file_name);
        return;
      }
    }
  }
  // print the change notification to the old output
  char* file_str = file ? mprintf("file '%s'", final_file_name) : NULL;
  Free(final_file_name);
  print(DRET_SETTING_CHANGE, "Debugger set to print its output to %s%s%s.",
    console ? "the console" : "", (console && file) ? " and to " : "",
    file ? file_str : "");
  if (file) {
    Free(file_str);
  }
  if (!same_file && !TTCN_Runtime::is_hc()) {
    if (output_file != NULL) {
      fclose(output_file);
    }
    output_file = new_fp;
  }
  send_to_console = console;
  Free(output_file_name);
  if (file) {
    output_file_name = mcopystr(p_file_name);
  }
}

void TTCN3_Debugger::set_global_batch_file(const char* p_state_str,
                                           const char* p_file_name)
{
  boolean delete_old = FALSE;
  boolean copy_new = FALSE;
  if (!strcmp(p_state_str, "on")) {
    if (p_file_name != NULL) {
      if (global_batch_file != NULL) {
        if (!strcmp(p_file_name, global_batch_file)) {
          print(DRET_NOTIFICATION, "Global batch file was already switched on "
            "and set to '%s'.", p_file_name);
        }
        else {
          print(DRET_SETTING_CHANGE, "Global batch file changed from '%s' to '%s'.",
            global_batch_file, p_file_name);
          delete_old = TRUE;
          copy_new = TRUE;
        }
      }
      else {
        print(DRET_SETTING_CHANGE, "Global batch file switched on and set to '%s'.",
          p_file_name);
        copy_new = TRUE;
      }
    }
    else {
      print(DRET_NOTIFICATION, "Missing batch file name argument.");
    }
  }
  else if (!strcmp(p_state_str, "off")) {
    if (global_batch_file != NULL) {
      print(DRET_SETTING_CHANGE, "Global batch file switched off.");
      delete_old = TRUE;
    }
    else {
      print(DRET_NOTIFICATION, "Global batch file was already switched off.");
    }
  }
  else {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'on' or 'off'.");
  }
  if (delete_old) {
    Free(global_batch_file);
    global_batch_file = NULL;
  }
  if (copy_new) {
    global_batch_file = mcopystr(p_file_name);
  }
}

void TTCN3_Debugger::step(stepping_t p_stepping_type)
{
  if (!halted) {
    print(DRET_NOTIFICATION, "Stepping commands can only be used when test "
      "execution is halted.");
    return;
  }
  stepping_type = p_stepping_type;
  stepping_stack_size = call_stack.size();
  if (!TTCN_Runtime::is_single()) {
    TTCN_Communication::send_debug_continue_req();
  }
  resume();
}

void TTCN3_Debugger::run_to_cursor(const char* p_module, const char* p_location)
{
  // all processes receive this command, since the specified location might be
  // reached in any process, even a PTC that hasn't been created yet
  if (!halted) {
    print(DRET_NOTIFICATION, "The 'run to' command can only be used when test "
      "execution is halted.");
    return;
  }
  temporary_breakpoint.module = mcopystr(p_module);
  if (is_numeric(p_location)) {
    temporary_breakpoint.line = strtol(p_location, NULL, 10);
    temporary_breakpoint.function = NULL;
  }
  else {
    temporary_breakpoint.line = 0;
    temporary_breakpoint.function = mcopystr(p_location);
  }
  resume();
}

void TTCN3_Debugger::halt(const char* p_batch_file, boolean p_run_global_batch)
{
  if (!halted) {
    halted = TRUE;
    Free(temporary_breakpoint.module);
    temporary_breakpoint.module = NULL;
    temporary_breakpoint.line = 0;
    Free(temporary_breakpoint.function);
    temporary_breakpoint.function = NULL;
    if (!TTCN_Runtime::is_hc()) {
      stepping_type = NOT_STEPPING;
      stack_level = call_stack.size() - 1;
      print(DRET_NOTIFICATION, "Test execution halted.");
      if (p_batch_file != NULL) {
        if (TTCN_Runtime::is_single()) {
          TTCN_Debugger_UI::execute_batch_file(p_batch_file);
        }
        else {
          TTCN_Communication::send_debug_batch(p_batch_file);
        }
      }
      else if (p_run_global_batch && global_batch_file != NULL) {
        if (TTCN_Runtime::is_single()) {
          TTCN_Debugger_UI::execute_batch_file(global_batch_file);
        }
        else {
          TTCN_Communication::send_debug_batch(global_batch_file);
        }
      }
      if (TTCN_Runtime::is_single()) {
        if (halted && !halt_at_start) {
          resume();
        }
        else {
          TTCN_Debugger_UI::read_loop();
        }
      }
      else {
        TTCN_Communication::process_debug_messages();
      }
    }
  }
  else {
    print(DRET_NOTIFICATION, "Test execution is already halted.");
  }
}

void TTCN3_Debugger::resume()
{
  if (halted) {
    halted = FALSE;
    stack_level = -1;
    print(DRET_NOTIFICATION, "Test execution resumed.");
  }
  else {
    print(DRET_NOTIFICATION, "Test execution is not halted.");
  }
}

void TTCN3_Debugger::exit_(const char* p_what)
{
  if (!strcmp(p_what, "test")) {
    exiting = FALSE;
  }
  else if (!strcmp(p_what, "all")) {
    exiting = TRUE;
  }
  else {
    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'test' or 'all'.");
    return;
  }
  halted = FALSE;
  if (!TTCN_Runtime::is_hc()) {
    print((exiting && TTCN_Runtime::is_mtc()) ? DRET_EXIT_ALL : DRET_NOTIFICATION,
      "Exiting %s.", exiting ? "test execution" : "current test");
    TTCN_Runtime::stop_execution();
  }
}

size_t TTCN3_Debugger::find_breakpoint(const char* p_module, int p_line,
                                       const char* p_function) const
{
  for (size_t i = 0; i < breakpoints.size(); ++i) {
    if (!strcmp(breakpoints[i].module, p_module) && 
        ((p_line != 0 && breakpoints[i].line == p_line) || 
         (p_function != NULL && breakpoints[i].function != NULL &&
          strcmp(breakpoints[i].function, p_function) == 0))) {
      return i;
    }
  }
  return breakpoints.size();
}

TTCN3_Debugger::variable_t* TTCN3_Debugger::find_variable(const void* p_value) const
{
  for (size_t i = 0; i < variables.size(); ++i) {
    if (variables[i]->value == p_value) {
      return variables[i];
    }
  }
  return NULL;
}

char* TTCN3_Debugger::finalize_file_name(const char* p_file_name_skeleton)
{
  if (p_file_name_skeleton == NULL) {
    return NULL;
  }
  size_t len = strlen(p_file_name_skeleton);
  size_t next_idx = 0;
  char* ret_val = NULL;
  for (size_t i = 0; i < len - 1; ++i) {
    if (p_file_name_skeleton[i] == '%') {
      ret_val = mputstrn(ret_val, p_file_name_skeleton + next_idx, i - next_idx);
      switch (p_file_name_skeleton[i + 1]) {
      case 'e': // %e -> executable name
        ret_val = mputstr(ret_val, TTCN_Logger::get_executable_name());
        break;
      case 'h': // %h -> host name
        ret_val = mputstr(ret_val, TTCN_Runtime::get_host_name());
        break;
      case 'p': // %p -> process ID
        ret_val = mputprintf(ret_val, "%ld", (long)getpid());
        break;
      case 'l': { // %l -> login name
        setpwent();
        struct passwd *p = getpwuid(getuid());
        if (NULL != p) {
          ret_val = mputstr(ret_val, p->pw_name);
        }
        endpwent();
        break; }
      case 'r': // %r -> component reference
        if (TTCN_Runtime::is_single()) {
          ret_val = mputstr(ret_val, "single");
        }
        else if (TTCN_Runtime::is_mtc()) {
          ret_val = mputstr(ret_val, "mtc");
        }
        else if (TTCN_Runtime::is_ptc()) {
          ret_val = mputprintf(ret_val, "%d", (component)self);
        }
        break;
      case 'n': // %n -> component name
        if (TTCN_Runtime::is_mtc()) {
          ret_val = mputstr(ret_val, "MTC");
        }
        else if (TTCN_Runtime::is_ptc()) {
          ret_val = mputstr(ret_val, TTCN_Runtime::get_component_name());
        }
        break;
      case '%': // %% -> single %
        ret_val = mputc(ret_val, '%');
        break;
      default: // unknown sequence -> leave it as it is 
        ret_val = mputstrn(ret_val, p_file_name_skeleton + i, 2);
        break;
      }
      next_idx = i + 2;
      ++i;
    }
  }
  if (next_idx < len) {
    ret_val = mputstr(ret_val, p_file_name_skeleton + next_idx);
  }
  return ret_val;
}

void TTCN3_Debugger::test_execution_started()
{
  if (function_calls.cfg != CALLS_TO_FILE) {
    if (function_calls.buffer.size != 0 && function_calls.buffer.end != -1) {
      for (int i = function_calls.buffer.start;
           i != function_calls.buffer.end;
           i = (i + 1) % function_calls.buffer.size) {
        Free(function_calls.buffer.ptr[i]);
      }
      Free(function_calls.buffer.ptr[function_calls.buffer.end]);
    }
    if (function_calls.cfg == CALLS_STORE_ALL) {
      Free(function_calls.buffer.ptr);
      function_calls.buffer.ptr = NULL;
      function_calls.buffer.size = 0;
    }
    function_calls.buffer.start = 0;
    function_calls.buffer.end = -1;
  }
  exiting = FALSE;
  if (TTCN_Runtime::is_single()) {
    TTCN_Debugger_UI::init();
    if (initial_batch_file) {
      halt(initial_batch_file, FALSE);
    }
    else if (halt_at_start) {
      halt(NULL, FALSE);
    }
  }
  halt_at_start = TRUE;
}

void TTCN3_Debugger::test_execution_finished()
{
  stepping_type = NOT_STEPPING;
  Free(temporary_breakpoint.module);
  temporary_breakpoint.module = NULL;
  temporary_breakpoint.line = 0;
  Free(temporary_breakpoint.function);
  temporary_breakpoint.function = NULL;
  last_breakpoint_entry.module = NULL;
  last_breakpoint_entry.line = 0;
  last_breakpoint_entry.stack_size = 0;
  if (TTCN_Runtime::is_single()) {
    TTCN_Debugger_UI::clean_up();
  }
}

void TTCN3_Debugger::print(int return_type, const char* fmt, ...) const
{
  if (TTCN_Runtime::is_hc()) {
    // don't display anything while on the HC process
    return;
  }
  va_list parameters;
  va_start(parameters, fmt);
  char* str = mprintf_va_list(fmt, parameters);
  va_end(parameters);
  if (TTCN_Runtime::is_single()) {
    if (send_to_console) {
      TTCN_Debugger_UI::print(str);
    }
  }
  else {
    TTCN_Communication::send_debug_return_value(return_type, send_to_console ? str : NULL);
  }
  if (output_file != NULL) {
    fseek(output_file, 0, SEEK_END); // in case multiple processes are writing the same file
    fputs(str, output_file);
    fputc('\n', output_file);
    fflush(output_file);
  }
  Free(str);
}

TTCN3_Debugger::TTCN3_Debugger()
{
  enabled = FALSE;
  active = FALSE;
  halted = FALSE;
  output_file = NULL;
  output_file_name = NULL;
  send_to_console = TRUE;
  function_calls.cfg = CALLS_STORE_ALL;
  function_calls.buffer.size = 0;
  function_calls.buffer.start = 0;
  function_calls.buffer.end = -1;
  function_calls.buffer.ptr = NULL;
  last_breakpoint_entry.module = NULL;
  last_breakpoint_entry.line = 0;
  last_breakpoint_entry.stack_size = 0;
  stack_level = -1;
  fail_behavior.trigger = FALSE;
  fail_behavior.batch_file = NULL;
  error_behavior.trigger = FALSE;
  error_behavior.batch_file = NULL;
  global_batch_file = NULL;
  command_result = NULL;
  last_variable_list = NULL;
  stepping_type = NOT_STEPPING;
  stepping_stack_size = 0;
  temporary_breakpoint.module = NULL;
  temporary_breakpoint.line = 0;
  temporary_breakpoint.function = NULL;
  temporary_breakpoint.batch_file = NULL; // not used
  exiting = FALSE;
  halt_at_start = FALSE;
  initial_batch_file = NULL;
}

TTCN3_Debugger::~TTCN3_Debugger()
{
  if (output_file != NULL) {
    fclose(output_file);
    Free(output_file_name);
  }
  for (size_t i = 0; i < breakpoints.size(); ++i) {
    Free(breakpoints[i].module);
    Free(breakpoints[i].function);
    Free(breakpoints[i].batch_file);
  }
  for (size_t i = 0; i < global_scopes.size(); ++i) {
    delete global_scopes[i].scope;
  }
  for (size_t i = 0; i < component_scopes.size(); ++i) {
    delete component_scopes[i].scope;
  }
  for (size_t i = 0; i < variables.size(); ++i) {
    delete variables[i];
  }
  Free(fail_behavior.batch_file);
  Free(error_behavior.batch_file);
  Free(global_batch_file);
  clean_up_function_calls();
  Free(last_variable_list);
}

TTCN3_Debug_Scope* TTCN3_Debugger::add_global_scope(const char* p_module)
{
  named_scope_t global_scope;
  global_scope.name = p_module;
  global_scope.scope = new TTCN3_Debug_Scope();
  global_scopes.push_back(global_scope);
  return global_scope.scope;
}

TTCN3_Debug_Scope* TTCN3_Debugger::add_component_scope(const char* p_component)
{
  named_scope_t component_scope;
  component_scope.name = p_component;
  component_scope.scope = new TTCN3_Debug_Scope();
  component_scopes.push_back(component_scope);
  return component_scope.scope;
}

void TTCN3_Debugger::set_return_value(const CHARSTRING& p_value)
{
  if (active && !call_stack.empty()) {
    call_stack[call_stack.size() - 1].function->set_return_value(p_value);
  }
}

void TTCN3_Debugger::breakpoint_entry(int p_line)
{
  if (active && !call_stack.empty()) {
    const char* module_name = call_stack[call_stack.size() - 1].function->get_module_name();
    boolean trigger = FALSE;
    const char* trigger_type;
    int actual_line;
    const char* batch_file = NULL;
    switch (p_line) {
    case SBP_FAIL_VERDICT:
      trigger = fail_behavior.trigger;
      trigger_type = "Automatic breakpoint (fail verdict) reached at";
      actual_line = TTCN_Location::get_line_number();
      batch_file = fail_behavior.batch_file;
      break;
    case SBP_ERROR_VERDICT:
      trigger = error_behavior.trigger;
      trigger_type = "Automatic breakpoint (error verdict) reached at";
      actual_line = TTCN_Location::get_line_number();
      batch_file = error_behavior.batch_file;
      break;
    default: // code lines
      // first: make sure it's not the same breakpoint entry as last time
      if (p_line == last_breakpoint_entry.line &&
          module_name == last_breakpoint_entry.module) {
        break;
      }
      actual_line = p_line;
      // second: check if a stepping operation ends here
      if (stepping_type == STEP_INTO ||
          (stepping_type == STEP_OVER && call_stack.size() <= stepping_stack_size) ||
          (stepping_type == STEP_OUT && call_stack.size() < stepping_stack_size)) {
        trigger = TRUE;
        trigger_type = "Stepped to";
        break;
      }
      const char* function_name =
        call_stack[call_stack.size() - 1].function->get_function_name();
      // third: check if this is the destination of a 'run to cursor' operation
      if (temporary_breakpoint.module != NULL &&
          strcmp(module_name, temporary_breakpoint.module) == 0 &&
          (p_line == temporary_breakpoint.line ||
           (temporary_breakpoint.function != NULL &&
            last_breakpoint_entry.stack_size == call_stack.size() - 1 &&
            strcmp(temporary_breakpoint.function, function_name) == 0))) {
        trigger = TRUE;
        trigger_type = "Ran to";
        break;
      }
      // fourth: check if the location matches one of the breakpoints in the list
      size_t pos = find_breakpoint(module_name, p_line, NULL);
      if (pos == breakpoints.size() &&
          last_breakpoint_entry.stack_size == call_stack.size() - 1) {
        // this is the first breakpoint entry in the function,
        // check for a matching function breakpoint
        pos = find_breakpoint(module_name, 0, function_name);
      }
      if (pos != breakpoints.size()) {
        trigger = TRUE;
        batch_file = breakpoints[pos].batch_file;
      }
      trigger_type = "User breakpoint reached at";
      break;
    }
    if (trigger) {
      print(DRET_NOTIFICATION, "%s line %d in module '%s'.",
        trigger_type, actual_line, module_name);
      if (!TTCN_Runtime::is_single()) {
        TTCN_Communication::send_debug_halt_req();
      }
      halt(batch_file, TRUE);
    }
    last_breakpoint_entry.module = module_name;
    last_breakpoint_entry.line = p_line;
    last_breakpoint_entry.stack_size = call_stack.size();
  }
}

CHARSTRING TTCN3_Debugger::print_base_var(const TTCN3_Debugger::variable_t& p_var)
{
  const void* ptr = p_var.set_function != NULL ? p_var.value : p_var.cvalue;
  TTCN_Logger::begin_event_log2str();
  if (!strcmp(p_var.type_name, "bitstring")) {
    ((const BITSTRING*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "bitstring template")) {
    ((const BITSTRING_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "boolean")) {
    ((const BOOLEAN*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "boolean template")) {
    ((const BOOLEAN_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "charstring")) {
    ((const CHARSTRING*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "charstring template")) {
    ((const CHARSTRING_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "float")) {
    ((const FLOAT*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "float template")) {
    ((const FLOAT_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "hexstring")) {
    ((const HEXSTRING*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "hexstring template")) {
    ((const HEXSTRING_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "integer")) {
    ((const INTEGER*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "integer template")) {
    ((const INTEGER_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "objid")) {
    ((const OBJID*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "objid template")) {
    ((const OBJID_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "octetstring")) {
    ((const OCTETSTRING*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "octetstring template")) {
    ((const OCTETSTRING_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "universal charstring")) {
    ((const UNIVERSAL_CHARSTRING*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "universal charstring template")) {
    ((const UNIVERSAL_CHARSTRING_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "verdicttype")) {
    ((const VERDICTTYPE*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "verdicttype template")) {
    ((const VERDICTTYPE_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "component")) {
    ((const COMPONENT*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "component template")) {
    ((const COMPONENT_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "port")) {
    ((const PORT*)ptr)->log(); // virtual
  }
  else if (!strcmp(p_var.type_name, "default")) {
    ((const DEFAULT*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "default template")) {
    ((const DEFAULT_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "timer")) {
    ((const TIMER*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "NULL")) {
    ((const ASN_NULL*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "NULL template")) {
    ((const ASN_NULL_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "CHARACTER STRING")) {
    ((const CHARACTER_STRING*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "CHARACTER STRING template")) {
    ((const CHARACTER_STRING_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "EMBEDDED PDV")) {
    ((const EMBEDDED_PDV*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "EMBEDDED PDV template")) {
    ((const EMBEDDED_PDV_template*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "EXTERNAL")) {
    ((const EXTERNAL*)ptr)->log();
  }
  else if (!strcmp(p_var.type_name, "EXTERNAL template")) {
    ((const EXTERNAL_template*)ptr)->log();
  }
  else {
    TTCN_Logger::log_event_str("<unrecognized value or template>");
  }
  return TTCN_Logger::end_event_log2str();
}

boolean TTCN3_Debugger::set_base_var(variable_t& p_var, Module_Param& p_new_value)
{
  if (!strcmp(p_var.type_name, "bitstring")) {
    ((BITSTRING*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "bitstring template")) {
    ((BITSTRING_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "boolean")) {
    ((BOOLEAN*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "boolean template")) {
    ((BOOLEAN_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "charstring")) {
    ((CHARSTRING*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "charstring template")) {
    ((CHARSTRING_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "float")) {
    ((FLOAT*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "float template")) {
    ((FLOAT_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "hexstring")) {
    ((HEXSTRING*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "hexstring template")) {
    ((HEXSTRING_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "integer")) {
    ((INTEGER*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "integer template")) {
    ((INTEGER_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "objid")) {
    ((OBJID*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "objid template")) {
    ((OBJID_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "octetstring")) {
    ((OCTETSTRING*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "octetstring template")) {
    ((OCTETSTRING_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "universal charstring")) {
    ((UNIVERSAL_CHARSTRING*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "universal charstring template")) {
    ((UNIVERSAL_CHARSTRING_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "verdicttype")) {
    ((VERDICTTYPE*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "verdicttype template")) {
    ((VERDICTTYPE_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "component")) {
    ((COMPONENT*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "component template")) {
    ((COMPONENT_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "default")) {
    ((DEFAULT*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "default template")) {
    ((DEFAULT_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "NULL")) {
    ((ASN_NULL*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "NULL template")) {
    ((ASN_NULL_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "CHARACTER STRING")) {
    ((CHARACTER_STRING*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "CHARACTER STRING template")) {
    ((CHARACTER_STRING_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "EMBEDDED PDV")) {
    ((EMBEDDED_PDV*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "EMBEDDED PDV template")) {
    ((EMBEDDED_PDV_template*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "EXTERNAL")) {
    ((EXTERNAL*)p_var.value)->set_param(p_new_value);
  }
  else if (!strcmp(p_var.type_name, "EXTERNAL template")) {
    ((EXTERNAL_template*)p_var.value)->set_param(p_new_value);
  }
  else {
    return FALSE;
  }
  return TRUE;
}

void TTCN3_Debugger::add_to_result(const char* fmt, ...)
{
  va_list parameters;
  va_start(parameters, fmt);
  command_result = mputprintf_va_list(command_result, fmt, parameters);
  va_end(parameters);
}

void TTCN3_Debugger::add_function(TTCN3_Debug_Function* p_function)
{
  function_call_t function_call;
  if (call_stack.empty()) {
    test_execution_started();
    function_call.caller_line = 0;
  }
  else {
    function_call.caller_line = last_breakpoint_entry.line;
  }
  function_call.function = p_function;
  call_stack.push_back(function_call);
}

void TTCN3_Debugger::add_scope(TTCN3_Debug_Scope* p_scope)
{
  if (active && !call_stack.empty()) {
    call_stack[call_stack.size() - 1].function->add_scope(p_scope);
  }
}

void TTCN3_Debugger::remove_function(TTCN3_Debug_Function* p_function)
{
  if (!call_stack.empty() && call_stack[call_stack.size() - 1].function == p_function) {
    boolean removing_test_case = call_stack[call_stack.size() - 1].function->is_test_case();
    int caller_line = call_stack[call_stack.size() - 1].caller_line;
    call_stack.erase_at(call_stack.size() - 1);
    if (call_stack.empty()) {
      test_execution_finished();
    }
    if (caller_line != 0 && (stepping_type == STEP_INTO || stepping_type == STEP_OUT ||
        (stepping_type == STEP_OVER && call_stack.size() != stepping_stack_size))) {
      breakpoint_entry(caller_line);
    }
    if (exiting && TTCN_Runtime::is_single() && !call_stack.empty() && removing_test_case &&
        call_stack[call_stack.size() - 1].function->is_control_part()) {
      // 'exit all' was requested while executing a test case called by a control
      // part, which means the test case caught the original TC_End exception;
      // another exception must be thrown to stop the control part, too
      throw TC_End();
    }
  }
}

void TTCN3_Debugger::remove_scope(TTCN3_Debug_Scope* p_scope)
{
  if (!call_stack.empty()) {
    call_stack[call_stack.size() - 1].function->remove_scope(p_scope);
  }
}

TTCN3_Debugger::variable_t* TTCN3_Debugger::add_variable(const void* p_value,
                                                         const char* p_name,
                                                         const char* p_type,
                                                         const char* p_module,
                                                         TTCN3_Debugger::print_function_t p_print_function)
{
  if (call_stack.empty()) {
    // no call stack yet, so this is a global or component variable
    variable_t* var = find_variable(p_value);
    if (var == NULL) {
      var = new TTCN3_Debugger::variable_t;
      var->cvalue = p_value;
      var->name = p_name;
      var->type_name = p_type;
      var->module = p_module;
      var->print_function = p_print_function;
      var->set_function = NULL;
      variables.push_back(var);
    }
    return var;
  }
  else if (active) {
    // it's a local variable for the top-most function
    return call_stack[call_stack.size() - 1].function->add_variable(
      p_value, p_name, p_type, p_module, p_print_function);
  }
  return NULL;
}

TTCN3_Debugger::variable_t* TTCN3_Debugger::add_variable(void* p_value,
                                                         const char* p_name,
                                                         const char* p_type,
                                                         const char* p_module,
                                                         TTCN3_Debugger::print_function_t p_print_function,
                                                         TTCN3_Debugger::set_function_t p_set_function)
{
  if (call_stack.empty()) {
    // no call stack yet, so this is a global or component variable
    variable_t* var = find_variable(p_value);
    if (var == NULL) {
      var = new TTCN3_Debugger::variable_t;
      var->value = p_value;
      var->name = p_name;
      var->type_name = p_type;
      var->module = p_module;
      var->print_function = p_print_function;
      var->set_function = p_set_function;
      variables.push_back(var);
    }
    return var;
  }
  else if (active) {
    // it's a local variable for the top-most function
    return call_stack[call_stack.size() - 1].function->add_variable(
      p_value, p_name, p_type, p_module, p_print_function, p_set_function);
  }
  return NULL;
}

void TTCN3_Debugger::remove_variable(const variable_t* p_var)
{
  if (active && !call_stack.empty()) {
    call_stack[call_stack.size() - 1].function->remove_variable(p_var);
  }
}

const TTCN3_Debug_Scope* TTCN3_Debugger::get_global_scope(const char* p_module) const
{
  for (size_t i = 0; i < global_scopes.size(); ++i) {
    if (strcmp(global_scopes[i].name, p_module) == 0) {
      return global_scopes[i].scope;
    }
  }
  return NULL;
}

const TTCN3_Debug_Scope* TTCN3_Debugger::get_component_scope(const char* p_component) const
{
  for (size_t i = 0; i < component_scopes.size(); ++i) {
    if (strcmp(component_scopes[i].name, p_component) == 0) {
      return component_scopes[i].scope;
    }
  }
  return NULL;
}

void TTCN3_Debugger::store_function_call(char* p_snapshot)
{
  if (function_calls.cfg == CALLS_RING_BUFFER && function_calls.buffer.size == 0) {
    Free(p_snapshot);
    return;
  }
  // append timestamp to the beginning of the snapshot
  timeval tv;
  gettimeofday(&tv, NULL);
  struct tm *lt = localtime(&tv.tv_sec);
  if (lt != NULL) {
    char* temp = mprintf("%02d:%02d:%02d.%06ld\t%s", lt->tm_hour, lt->tm_min,
      lt->tm_sec, tv.tv_usec, p_snapshot);
    Free(p_snapshot);
    p_snapshot = temp;
  }
  switch (function_calls.cfg) {
  case CALLS_TO_FILE:
    fseek(function_calls.file.ptr, 0, SEEK_END); // in case multiple processes are writing the same file
    fputs(p_snapshot, function_calls.file.ptr);
    Free(p_snapshot);
    fputc('\n', function_calls.file.ptr);
    fflush(function_calls.file.ptr);
    break;
  case CALLS_RING_BUFFER: {
    boolean first = function_calls.buffer.end == -1;
    function_calls.buffer.end = (function_calls.buffer.end + 1) %
      function_calls.buffer.size;
    function_calls.buffer.ptr[function_calls.buffer.end] = p_snapshot;
    if (!first && function_calls.buffer.start == function_calls.buffer.end) {
      function_calls.buffer.start = (function_calls.buffer.start + 1) %
        function_calls.buffer.size;
    }
    break; }
  case CALLS_STORE_ALL:
    if (function_calls.buffer.end == function_calls.buffer.size - 1) {
      function_calls.buffer.size += BUFFER_INCREASE;
      function_calls.buffer.ptr = (char**)Realloc(function_calls.buffer.ptr,
        function_calls.buffer.size * sizeof(char*));
    }
    ++function_calls.buffer.end;
    function_calls.buffer.ptr[function_calls.buffer.end] = p_snapshot;
    break;
  }
}

#define CHECK_NOF_ARGUMENTS(exp_num) \
  if (exp_num != p_argument_count) { \
    print(DRET_NOTIFICATION, "Invalid number of arguments. Expected %d, got %d.", \
      (int)exp_num, (int)p_argument_count); \
    return; \
  }

#define CHECK_NOF_ARGUMENTS_RANGE(min, max) \
  if ((int)min > p_argument_count || (int)max < p_argument_count) { \
    print(DRET_NOTIFICATION, "Invalid number of arguments. Expected at least %d " \
      "and at most %d, got %d.", (int)min, (int)max, p_argument_count); \
    return; \
  }

#define CHECK_NOF_ARGUMENTS_MIN(min) \
  if ((int)min > p_argument_count) { \
    print(DRET_NOTIFICATION, "Invalid number of arguments. Expected at least %d, got %d.", \
      (int)min, p_argument_count); \
    return; \
  }

#define CHECK_INT_ARGUMENT(arg_idx) \
  { \
    if (!is_numeric(p_arguments[arg_idx])) { \
      print(DRET_NOTIFICATION, "Argument %d is not an integer.", (int)(arg_idx + 1)); \
      return; \
    } \
  }

#define CHECK_CALL_STACK(print_msg) \
  if (!active) { \
    if (print_msg) { \
      print(DRET_NOTIFICATION, "This command can only be used if the debugger " \
        "is switched on."); \
    } \
    return; \
  } \
  if (call_stack.empty()) { \
    if (print_msg) { \
      print(DRET_NOTIFICATION, "This command can only be used if the debugger's " \
        "call stack is not empty."); \
    } \
    return; \
  }

void TTCN3_Debugger::execute_command(int p_command, int p_argument_count,
                                     char** p_arguments)
{
  if (!enabled) {
    return;
  }
  for (int i = 0; i < p_argument_count; ++i) {
    if (p_arguments[i] == NULL) {
      print(DRET_NOTIFICATION, "Argument %d is a null pointer.", i + 1);
      return;
    }
  }
  switch (p_command) {
  case D_SWITCH:
    CHECK_NOF_ARGUMENTS(1)
    switch_state(p_arguments[0]);
    break;
  case D_SET_BREAKPOINT:
    CHECK_NOF_ARGUMENTS_RANGE(2, 3)
    set_breakpoint(p_arguments[0], p_arguments[1],
      (p_argument_count == 3) ? p_arguments[2] : NULL);
    break;
  case D_REMOVE_BREAKPOINT:
    CHECK_NOF_ARGUMENTS_RANGE(1, 2)
    remove_breakpoint(p_arguments[0], (p_argument_count == 2) ? p_arguments[1] : NULL);
    break;
  case D_SET_AUTOMATIC_BREAKPOINT:
    CHECK_NOF_ARGUMENTS_RANGE(2, 3)
    set_automatic_breakpoint(p_arguments[0], p_arguments[1],
      (p_argument_count == 3) ? p_arguments[2] : NULL);
    break;
  case D_SET_OUTPUT:
    CHECK_NOF_ARGUMENTS_RANGE(1, 2)
    set_output(p_arguments[0], (p_argument_count == 2) ? p_arguments[1] : NULL);
    break;
  case D_SET_GLOBAL_BATCH_FILE:
    CHECK_NOF_ARGUMENTS_RANGE(1, 2)
    set_global_batch_file(p_arguments[0], (p_argument_count == 2) ? p_arguments[1] : NULL);
    break;
  case D_FUNCTION_CALL_CONFIG:
    CHECK_NOF_ARGUMENTS_RANGE(1, 2)
    configure_function_calls(p_arguments[0], (p_argument_count == 2) ? p_arguments[1] : NULL);
    break;
  case D_PRINT_SETTINGS:
    CHECK_NOF_ARGUMENTS(0)
    print_settings();
    break;
  case D_PRINT_CALL_STACK:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS(0)
    print_call_stack();
    break;
  case D_SET_STACK_LEVEL:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS(1)
    CHECK_INT_ARGUMENT(0)
    set_stack_level(str2int(p_arguments[0]));
    break;
  case D_LIST_VARIABLES:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS_RANGE(0, 2)
    call_stack[STACK_LEVEL].function->list_variables(
      (p_argument_count > 0) ? p_arguments[0] : NULL,
      (p_argument_count == 2) ? p_arguments[1] : NULL);
    break;
  case D_PRINT_VARIABLE:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS_MIN(1)
    for (int i = 0; i < p_argument_count; ++i) {
      if (i != 0) {
        add_to_result("\n");
      }
      if (!strcmp(p_arguments[i], "$")) {
        // '$' refers to the result of the last D_LIST_VARIABLES command
        // these variable names are separated by spaces
        if (last_variable_list != NULL) {
          size_t len = mstrlen(last_variable_list);
          size_t start = 0;
          for (size_t j = 0; j < len; ++j) {
            if (last_variable_list[j] == ' ') {
              // extract the variable name before this space
              char* var_name = mcopystrn(last_variable_list + start, j - start);
              print_variable(var_name);
              Free(var_name);
              add_to_result("\n");
              start = j + 1;
            }
          }
          // extract the last (or only) variable name
          char* var_name = mcopystrn(last_variable_list + start, len - start);
          print_variable(var_name);
          Free(var_name);
        }
        else {
          add_to_result("No previous " D_LIST_VARIABLES_TEXT " result.");
        }
      }
      else {
        print_variable(p_arguments[i]);
      }
    }
    break;
  case D_OVERWRITE_VARIABLE:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS_MIN(2)
    overwrite_variable(p_arguments[0], p_argument_count - 1, p_arguments + 1);
    break;
  case D_PRINT_FUNCTION_CALLS:
    CHECK_NOF_ARGUMENTS_RANGE(0, 1)
    print_function_calls((p_argument_count > 0) ? p_arguments[0] : NULL);
    break;
  case D_STEP_OVER:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS(0)
    step(STEP_OVER);
    break;
  case D_STEP_INTO:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS(0)
    step(STEP_INTO);
    break;
  case D_STEP_OUT:
    CHECK_CALL_STACK(TRUE)
    CHECK_NOF_ARGUMENTS(0)
    step(STEP_OUT);
    break;
  case D_RUN_TO_CURSOR:
    if (!TTCN_Runtime::is_hc() && !TTCN_Runtime::is_single()) {
      CHECK_CALL_STACK(TTCN_Runtime::is_mtc())
    }
    CHECK_NOF_ARGUMENTS(2)
    run_to_cursor(p_arguments[0], p_arguments[1]);
    break;
  case D_HALT:
    if (!TTCN_Runtime::is_hc() && !TTCN_Runtime::is_single()) {
      CHECK_CALL_STACK(TTCN_Runtime::is_mtc())
    }
    CHECK_NOF_ARGUMENTS(0)
    halt(NULL, FALSE);
    break;
  case D_CONTINUE:
    CHECK_NOF_ARGUMENTS(0)
    resume();
    break;
  case D_EXIT:
    if (!TTCN_Runtime::is_hc() && !TTCN_Runtime::is_single()) {
      CHECK_CALL_STACK(TTCN_Runtime::is_mtc())
    }
    CHECK_NOF_ARGUMENTS(1)
    exit_(p_arguments[0]);
    break;
  case D_SETUP:
    CHECK_NOF_ARGUMENTS_MIN(11)
    if (strlen(p_arguments[0]) > 0) {
      switch_state(p_arguments[0]);
    }
    if (strlen(p_arguments[1]) > 0) {
      set_output(p_arguments[1], p_arguments[2]);
    }
    if (strlen(p_arguments[3]) > 0) {
      set_automatic_breakpoint("error", p_arguments[3],
        strlen(p_arguments[4]) > 0 ? p_arguments[4] : NULL);
    }
    if (strlen(p_arguments[5]) > 0) {
      set_automatic_breakpoint("fail", p_arguments[5],
        strlen(p_arguments[6]) > 0 ? p_arguments[6] : NULL);
    }
    if (strlen(p_arguments[7]) > 0) {
      set_global_batch_file(p_arguments[7],
        strlen(p_arguments[8]) > 0 ? p_arguments[8] : NULL);
    }
    if (strlen(p_arguments[9]) > 0) {
      configure_function_calls(p_arguments[9],
        strlen(p_arguments[10]) > 0 ? p_arguments[10] : NULL);
    }
    for (int i = 11; i < p_argument_count; i += 3) {
      set_breakpoint(p_arguments[i], p_arguments[i + 1],
        strlen(p_arguments[i + 2]) > 0 ? p_arguments[i + 2] : NULL);
    }
    break;
  default:
    print(DRET_NOTIFICATION, "Invalid command received (ID: %d).", p_command);
    return;
  }
  if (command_result != NULL) {
    print(DRET_DATA, command_result);
    if (p_command == D_LIST_VARIABLES) {
      Free(last_variable_list);
      last_variable_list = command_result;
    }
    else {
      Free(command_result);
    }
    command_result = NULL;
  }
}

void TTCN3_Debugger::init_PTC_settings()
{
  if (output_file == NULL && output_file_name != NULL) {
    char* final_file_name = finalize_file_name(output_file_name);
    output_file = fopen(final_file_name, "a");
    if (output_file == NULL) {
      print(DRET_NOTIFICATION, "Failed to open file '%s' for writing.", final_file_name);
    }
    Free(final_file_name);
  }
  if (function_calls.cfg == CALLS_TO_FILE) {
    char* final_file_name = finalize_file_name(function_calls.file.name);
    function_calls.file.ptr = fopen(final_file_name, "a");
    if (function_calls.file.ptr == NULL) {
      print(DRET_NOTIFICATION, "Failed to open file '%s' for writing.", final_file_name);
    }
    Free(final_file_name);
  }
  else if (function_calls.cfg == CALLS_RING_BUFFER && function_calls.buffer.size != 0) {
    function_calls.buffer.ptr = (char**)Malloc(function_calls.buffer.size * sizeof(char*));
  }
}

//////////////////////////////////////////////////////
//////////////// TTCN3_Debug_Scope ///////////////////
//////////////////////////////////////////////////////

TTCN3_Debug_Scope::TTCN3_Debug_Scope()
{
  ttcn3_debugger.add_scope(this);
}

TTCN3_Debug_Scope::~TTCN3_Debug_Scope()
{
  for (size_t i = 0; i < variables.size(); ++i) {
    ttcn3_debugger.remove_variable(variables[i]);
  }
  ttcn3_debugger.remove_scope(this);
}

void TTCN3_Debug_Scope::add_variable(const void* p_value,
                                     const char* p_name,
                                     const char* p_type,
                                     const char* p_module,
                                     TTCN3_Debugger::print_function_t p_print_function)
{
  TTCN3_Debugger::variable_t* var = ttcn3_debugger.add_variable(p_value, p_name,
    p_type, p_module, p_print_function);
  if (var != NULL) {
    variables.push_back(var);
  }
}

void TTCN3_Debug_Scope::add_variable(void* p_value,
                                     const char* p_name,
                                     const char* p_type,
                                     const char* p_module,
                                     TTCN3_Debugger::print_function_t p_print_function,
                                     TTCN3_Debugger::set_function_t p_set_function)
{
  TTCN3_Debugger::variable_t* var = ttcn3_debugger.add_variable(p_value, p_name,
    p_type, p_module, p_print_function, p_set_function);
  if (var != NULL) {
    variables.push_back(var);
  }
}

TTCN3_Debugger::variable_t* TTCN3_Debug_Scope::find_variable(const char* p_name) const
{
  for (size_t i = 0; i < variables.size(); ++i) {
    TTCN3_Debugger::variable_t* var = variables[i];
    if (strcmp(var->name, p_name) == 0) {
      // the string matches the variable's name
      return var;
    }
    else if (var->module != NULL) {
      size_t name_len = strlen(var->name);
      size_t mod_len = strlen(var->module);
      size_t len = strlen(p_name);
      if (len == mod_len + name_len + 1 && p_name[mod_len] == '.' &&
          strncmp(p_name, var->module, mod_len) == 0 &&
          strncmp(p_name + mod_len + 1, var->name, name_len) == 0) {
        // the string matches the variable's name prefixed by its module name
        return var;
      }
    }
  }
  return NULL;
}

void TTCN3_Debug_Scope::list_variables(regex_t* p_posix_regexp, bool& p_first,
                                       const char* p_module) const
{
  for (size_t i = 0; i < variables.size(); ++i) {
    if (p_posix_regexp == NULL ||
        regexec(p_posix_regexp, variables[i]->name, 0, NULL, 0) == 0) {
      boolean imported = p_module != NULL && strcmp(p_module, variables[i]->module) != 0;
      ttcn3_debugger.add_to_result("%s%s%s%s", p_first ? "" : " ",
        imported ? variables[i]->module : "", imported ? "." : "", variables[i]->name);
      p_first = FALSE;
    }
  }
}

//////////////////////////////////////////////////////
/////////////// TTCN3_Debug_Function /////////////////
//////////////////////////////////////////////////////

TTCN3_Debug_Function::TTCN3_Debug_Function(const char* p_name,
                                           const char* p_type,
                                           const char* p_module,
                                           const charstring_list& p_parameter_names,
                                           const charstring_list& p_parameter_types,
                                           const char* p_component_name)
: function_name(p_name), function_type(p_type), module_name(p_module)
, parameter_names(new charstring_list(p_parameter_names))
, parameter_types(new charstring_list(p_parameter_types))
{
  ttcn3_debugger.add_function(this);
  global_scope = ttcn3_debugger.get_global_scope(p_module);
  component_scope = (p_component_name != NULL) ?
    ttcn3_debugger.get_component_scope(p_component_name) : NULL;
  if (function_name == NULL) {
    function_name = p_module; // for control parts
  }
}

TTCN3_Debug_Function::~TTCN3_Debug_Function()
{
  if (ttcn3_debugger.is_on()) {
    char* snapshot = mprintf("[%s]\tfinished\t%s(", function_type, function_name);
    if (parameter_names->size_of() > 0) {
      for (int i = 0; i < parameter_names->size_of(); ++i) {
        if (i > 0) {
          snapshot = mputstr(snapshot, ", ");
        }
        snapshot = mputprintf(snapshot, "[%s] %s := ", (const char*)((*parameter_types)[i]),
          (const char*)((*parameter_names)[i]));
        if ((*parameter_types)[i] == "out" || (*parameter_types)[i] == "inout") {
          const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]);
          snapshot = mputstr(snapshot, parameter->print_function(*parameter));
        }
        else {
          snapshot = mputc(snapshot, '-');
        }
      }
    }
    snapshot = mputc(snapshot, ')');
    if (return_value.is_bound()) {
      snapshot = mputprintf(snapshot, " returned %s", (const char*)return_value);
    }
    ttcn3_debugger.store_function_call(snapshot);
  }
  for (size_t i = 0; i < variables.size(); ++i) {
    delete variables[i];
  }
  delete parameter_names;
  delete parameter_types;
  ttcn3_debugger.remove_function(this);
}

TTCN3_Debugger::variable_t* TTCN3_Debug_Function::add_variable(const void* p_value,
                                                               const char* p_name,
                                                               const char* p_type,
                                                               const char* p_module,
                                                               TTCN3_Debugger::print_function_t p_print_function)
{
  if (ttcn3_debugger.is_on()) {
    TTCN3_Debugger::variable_t* var = new TTCN3_Debugger::variable_t;
    var->cvalue = p_value;
    var->name = p_name;
    var->type_name = p_type;
    var->module = p_module;
    var->print_function = p_print_function;
    var->set_function = NULL;
    variables.push_back(var);
    return var;
  }
  return NULL;
}

TTCN3_Debugger::variable_t* TTCN3_Debug_Function::add_variable(void* p_value,
                                                               const char* p_name,
                                                               const char* p_type,
                                                               const char* p_module,
                                                               TTCN3_Debugger::print_function_t p_print_function,
                                                               TTCN3_Debugger::set_function_t p_set_function)
{
  if (ttcn3_debugger.is_on()) {
    TTCN3_Debugger::variable_t* var = new TTCN3_Debugger::variable_t;
    var->value = p_value;
    var->name = p_name;
    var->type_name = p_type;
    var->module = p_module;
    var->print_function = p_print_function;
    var->set_function = p_set_function;
    variables.push_back(var);
    return var;
  }
  return NULL;
}

void TTCN3_Debug_Function::set_return_value(const CHARSTRING& p_value)
{
  return_value = p_value;
}

void TTCN3_Debug_Function::initial_snapshot() const
{
  if (ttcn3_debugger.is_on()) {
    char* snapshot = mprintf("[%s]\tstarted \t%s(", function_type, function_name);
    if (parameter_names->size_of() > 0) {
      for (int i = 0; i < parameter_names->size_of(); ++i) {
        if (i > 0) {
          snapshot = mputstr(snapshot, ", ");
        }
        snapshot = mputprintf(snapshot, "[%s] %s := ", (const char*)((*parameter_types)[i]),
          (const char*)((*parameter_names)[i]));
        if ((*parameter_types)[i] == "in" || (*parameter_types)[i] == "inout") {
          const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]);
          snapshot = mputstr(snapshot, parameter->print_function(*parameter));
        }
        else {
          snapshot = mputc(snapshot, '-');
        }
      }
    }
    snapshot = mputstr(snapshot, ")");
    ttcn3_debugger.store_function_call(snapshot);
  }
}

void TTCN3_Debug_Function::add_scope(TTCN3_Debug_Scope* p_scope)
{
  scopes.push_back(p_scope);
}

void TTCN3_Debug_Function::remove_scope(TTCN3_Debug_Scope* p_scope)
{
  if (!scopes.empty() && scopes[scopes.size() - 1] == p_scope) {
    scopes.erase_at(scopes.size() - 1);
  }
}

void TTCN3_Debug_Function::remove_variable(const TTCN3_Debugger::variable_t* p_var)
{
  for (size_t i = 0; i < variables.size(); ++i) {
    if (variables[i] == p_var) {
      variables.erase_at(i);
      delete p_var;
      break;
    }
  }
}

TTCN3_Debugger::variable_t* TTCN3_Debug_Function::find_variable(const char* p_name) const
{
  for (size_t i = 0; i < variables.size(); ++i) {
    if (strcmp(variables[i]->name, p_name) == 0) {
      return variables[i];
    }
  }
  // it's not a local variable, it might still be a global or component variable
  if (component_scope != NULL) {
    TTCN3_Debugger::variable_t* res = component_scope->find_variable(p_name);
    if (res != NULL) {
      return res;
    }
  }
  return (global_scope != NULL) ? global_scope->find_variable(p_name) : NULL;
}

void TTCN3_Debug_Function::print_function() const
{
  ttcn3_debugger.add_to_result("[%s]\t%s(", function_type, function_name);
  if (parameter_names->size_of() > 0) {
    for (int i = 0; i < parameter_names->size_of(); ++i) {
      if (i > 0) {
        ttcn3_debugger.add_to_result(", ");
      }
      const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]);
      ttcn3_debugger.add_to_result("[%s] %s := %s", (const char*)(*parameter_types)[i],
        (const char*)(*parameter_names)[i], (const char*)parameter->print_function(*parameter));
    }
  }
  ttcn3_debugger.add_to_result(")");
}

void TTCN3_Debug_Function::list_variables(const char* p_scope, const char* p_filter) const
{
  boolean first = TRUE;
  boolean list_local = FALSE;
  boolean list_global = FALSE;
  boolean list_comp = FALSE;
  if (p_scope == NULL || !strcmp(p_scope, "all")) {
    list_local = TRUE;
    list_global = TRUE;
    list_comp = TRUE;
  }
  else if (!strcmp(p_scope, "local")) {
    list_local = TRUE;
  }
  else if (!strcmp(p_scope, "global")) {
    list_global = TRUE;
  }
  else if (!strcmp(p_scope, "comp")) {
    list_comp = TRUE;
  }
  else {
    ttcn3_debugger.print(DRET_NOTIFICATION, "Argument 1 is invalid. "
      "Expected 'local', 'global', 'comp' or 'all'.");
    return;
  }
  regex_t* posix_regexp = NULL;
  if (p_filter != NULL) {
    char* posix_str = TTCN_pattern_to_regexp(p_filter);
    if (posix_str == NULL) {
      ttcn3_debugger.print(DRET_NOTIFICATION, "Argument 2 is invalid. "
        "Expected a valid TTCN-3 character pattern.");
      return;
    }
    posix_regexp = new regex_t;
    int ret_val = regcomp(posix_regexp, posix_str, REG_EXTENDED | REG_NOSUB);
    Free(posix_str);
    if (ret_val != 0) {
      char msg[512];
      regerror(ret_val, posix_regexp, msg, sizeof(msg));
      regfree(posix_regexp);
      delete posix_regexp;
      ttcn3_debugger.print(DRET_NOTIFICATION, "Compilation of POSIX regular "
        "expression failed.");
      return;
    }
  }
  if (list_local) {
    for (size_t i = 0; i < variables.size(); ++i) {
      if (posix_regexp == NULL ||
          regexec(posix_regexp, variables[i]->name, 0, NULL, 0) == 0) {
        ttcn3_debugger.add_to_result("%s%s", first ? "" : " ", variables[i]->name);
        first = FALSE;
      }
    }
  }
  if (list_global && global_scope != NULL && global_scope->has_variables()) {
    global_scope->list_variables(posix_regexp, first, module_name);
  }
  if (list_comp && component_scope != NULL && component_scope->has_variables()) {
    component_scope->list_variables(posix_regexp, first, NULL);
  }
  if (first) {
    ttcn3_debugger.print(DRET_NOTIFICATION, "No variables found.");
  }
  if (posix_regexp != NULL) {
    regfree(posix_regexp);
    delete posix_regexp;
  }
}

boolean TTCN3_Debug_Function::is_control_part() const
{
  return !strcmp(function_type, "control");
}

boolean TTCN3_Debug_Function::is_test_case() const
{
  return !strcmp(function_type, "testcase");
}
