/******************************************************************************
 * Copyright (c) 2000-2021 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:
 *   Baji, Laszlo
 *   Balasko, Jeno
 *   Beres, Szabolcs
 *   Delic, Adam
 *   Feher, Csaba
 *   Kovacs, Ferenc
 *   Lovassy, Arpad
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Janos Zoltan – initial implementation
 *   Szalai, Gabor
 *   Zalanyi, Balazs Andor
 *   Pandi, Krisztian
 *
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include "../common/dbgnew.hh"
#include "../common/version_internal.h"
#include "Logger.hh"
#include "Snapshot.hh"
#include "Port.hh"
#include "Module_list.hh"
#include "Runtime.hh"
#include "Component.hh"
#include "Encdec.hh"
#include "TitanLoggerApi.hh"
#include "TCov.hh"
#if defined(LINUX) && !defined(ALPINE_LINUX)
#include <execinfo.h>
#endif  

#ifdef LICENSE
#include "../common/license.h"
#endif

#include <signal.h>

const char * stored_argv = "Unidentified program";

static const char segfault[] = ": Segmentation fault occurred\n";
static const char abortcall[] = ": Abort was called\n";

void signal_handler(int signum)
{
  int retval;
  retval = write(STDERR_FILENO, stored_argv, strlen(stored_argv));
  if(signum==SIGSEGV){
  retval = write(STDERR_FILENO, segfault , sizeof(segfault)-1); // sizeof includes \0
  } else {
  retval = write(STDERR_FILENO, abortcall , sizeof(abortcall)-1); // sizeof includes \0
  }
#if defined(LINUX) && !defined(ALPINE_LINUX)
  int nptrs;
  void *buffer[100];
  nptrs = backtrace(buffer, 100);
  backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO);
  
  fflush(stderr);
#endif
  (void)retval;
  TTCN_Logger::close_file();

  signal(SIGABRT, SIG_DFL);
  abort();
}

static void usage(const char* program_name)
{
  fprintf(stderr, "\n"
    "usage: %s [-h] [-b file] configuration_file\n"
    "   or  %s -l\n"
    "   or  %s -p\n"
    "   or  %s -v\n"
    "\n"
    "OPTIONS:\n"
    "	-b file:	run specified batch file at start (debugger must be activated)\n"
    "	-h:		automatically halt execution at start (debugger must be activated)\n"
    "	-l:		list startable test cases and control parts\n"
    "	-p:		list module parameters\n"
    "	-v:		show version and module information\n",
    program_name, program_name, program_name, program_name);
}

int main(int argc, char *argv[])
{
  stored_argv = argv[0];
  struct sigaction act;
  act.sa_handler = signal_handler;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  sigaction(SIGSEGV, &act, 0);
  sigaction(SIGABRT, &act, 0);

#ifdef MEMORY_DEBUG
  debug_new_counter.set_program_name(argv[0]);
#endif
  errno = 0;
  int c, i, ret_val = EXIT_SUCCESS;
  boolean bflag = FALSE, hflag = FALSE, lflag = FALSE, vflag = FALSE, pflag = FALSE,
    errflag = FALSE;
  const char *config_file = NULL;
  TTCN_Module *only_runnable = Module_List::single_control_part();

  while ((c = getopt(argc, argv, "b:hlvp")) != -1) {
    switch (c) {
    case 'b':
      if (bflag || lflag || vflag || pflag) errflag = TRUE; // duplicate or conflicting
      else {
        bflag = TRUE;
        ttcn3_debugger.set_initial_batch_file(optarg);
      }
      break;
    case 'h':
      if (hflag || lflag || vflag || pflag) errflag = TRUE; // duplicate or conflicting
      else {
        hflag = TRUE;
        ttcn3_debugger.set_halt_at_start();
      }
      break;
    case 'l':
      if (lflag || vflag || pflag || bflag || hflag) errflag = TRUE; // duplicate or conflicting
      else lflag = TRUE;
      break;
    case 'p':
      if (pflag || lflag || vflag || bflag || hflag) errflag = TRUE; // duplicate or conflicting
      else pflag = TRUE;
      break;
    case 'v':
      if (lflag || vflag || pflag || bflag || hflag) errflag = TRUE; // duplicate or conflicting
      else vflag = TRUE;
      break;
    default:
      errflag = TRUE;
    }
  }

  if (!errflag) {
    if (lflag || vflag || pflag) {
      if (optind != argc) errflag = TRUE; // -l, -p or -v and non-option arg
    } else {
      if (optind > argc - 1) { // no config file argument
        errflag = (only_runnable == 0);
      }
      else config_file = argv[optind];
    }
  }

  if (errflag) {
    if (argc == 1) fputs("TTCN-3 Test Executor (single mode), version "
      PRODUCT_NUMBER "\n", stderr);
    usage(argv[0]);
    TCov::close_file();
    return EXIT_FAILURE;
  } else if (lflag || pflag) {
    try {
      // create buffer for error messages
      TTCN_Logger::initialize_logger();
      Module_List::pre_init_modules();
      if (lflag) {
        Module_List::list_testcases();
      }
      else { // pflag
        Module_List::list_modulepars();
      }
    } catch (...) {
      ret_val = EXIT_FAILURE;
    }
    TTCN_Logger::terminate_logger();
    TCov::close_file();
    return ret_val;
  } else if (vflag) {
    fputs("TTCN-3 Test Executor (single mode)\n"
      "Version: " VERSION_STRING "\n"
      "Build date (Base Library): " __DATE__ " " __TIME__ "\n"
      "Base Library was compiled with: " C_COMPILER_VERSION "\n\n"
      COPYRIGHT_STRING "\n\n", stderr);
#ifdef LICENSE
    print_license_info();
    putc('\n', stderr);
#endif
    fputs("Module information:\n", stderr);
    Module_List::print_version();
    TCov::close_file();
    return EXIT_SUCCESS;
  }

  fputs("TTCN-3 Test Executor (single mode), version " PRODUCT_NUMBER "\n",
    stderr);

#ifdef LICENSE
  init_openssl();
  license_struct lstr;
  load_license(&lstr);
  if (!verify_license(&lstr)) {
    free_license(&lstr);
    free_openssl();
    exit(EXIT_FAILURE);
  }
  if (!check_feature(&lstr, FEATURE_SINGLE)) {
    fputs("The license key does not allow test execution in single mode.\n",
      stderr);
    TCov::close_file();
    return EXIT_FAILURE;
  }
  free_license(&lstr);
  free_openssl();
#endif

  self = MTC_COMPREF;
  TTCN_Runtime::set_state(TTCN_Runtime::SINGLE_CONTROLPART);
  TTCN_Runtime::install_signal_handlers();
  TTCN_Snapshot::initialize();
  TTCN_Logger::initialize_logger();
  TTCN_Logger::set_executable_name(argv[0]);
  TTCN_Logger::set_start_time();

  try {
    TTCN_Logger::log_executor_runtime(
      TitanLoggerApi::ExecutorRuntime_reason::executor__start__single__mode);
    Module_List::pre_init_modules();

    if (config_file != 0) {
      fprintf(stderr, "Using configuration file: `%s'\n", config_file);
      TTCN_Logger::log_configdata(
        TitanLoggerApi::ExecutorConfigdata_reason::using__config__file, config_file);
    }

    TTCN_Snapshot::check_fd_setsize();

    boolean config_file_failure =
      config_file && !process_config_file(config_file);
    TTCN_Runtime::load_logger_plugins();
    // Quick return if no config file.
    TTCN_Runtime::set_logger_parameters();
    TTCN_Logger::open_file();
    TTCN_Logger::write_logger_settings();
    if (config_file_failure) goto fail;
    // Config file parsed or no config file: we may be able to run.
    if (config_file) {
      if (++optind != argc) {
        // There are further commandline arguments after config file.
        // Override testcase list.
        // First, throw away the old list parsed from the config file.
        for (i = 0; i < execute_list_len; i++) {
          Free(execute_list[i].module_name);
          Free(execute_list[i].testcase_name);
        }

        // The new execute list length is known in advance.
        execute_list_len = argc - optind;
        execute_list = (execute_list_item *)Realloc(
          execute_list, execute_list_len * sizeof(*execute_list));

        expstring_t testcase_names = memptystr(); // collects names for printout

        for (i = optind; i < argc; ++i) {
          testcase_names = mputstr(testcase_names, argv[i]);
          testcase_names = mputc(testcase_names, '\t');

          char *dot = strchr(argv[i], '.');
          if (dot != 0) {
            *dot++ = '\0'; // cut the string into two
            if (!strcmp(dot, "control"))
              dot = 0;
          }
          execute_list[i-optind].module_name = mcopystr(argv[i]);
          execute_list[i-optind].testcase_name = dot ? mcopystr(dot) : dot;
          // do not copy NULL pointer, it results in non-0 empty string
        } // next i
        fprintf(stderr, "Overriding testcase list: %s\n", testcase_names);
        TTCN_Logger::log_configdata(
          TitanLoggerApi::ExecutorConfigdata_reason::overriding__testcase__list,
          testcase_names);
        Free(testcase_names);
      }
    }

    if (execute_list_len == 0 && only_runnable) {
      // No config file or correct config file without EXECUTE section,
      // AND precisely one control part: run that one.
      execute_list_len = 1;
      execute_list = (execute_list_item *)Malloc(sizeof(*execute_list));
      execute_list[0].module_name = mcopystr(only_runnable->get_name());
      execute_list[0].testcase_name = 0; // control part
    }

    if (execute_list_len > 0) { // we have something to run
      Module_List::log_param();
      Module_List::post_init_modules();

      for (i = 0; i < execute_list_len; i++) {
        if (ttcn3_debugger.is_exiting()) {
          break;
        }
        if (execute_list[i].testcase_name == NULL)
          Module_List::execute_control(execute_list[i].module_name);
        else if (!strcmp(execute_list[i].testcase_name, "*"))
          Module_List::execute_all_testcases(
            execute_list[i].module_name);
        else
          Module_List::execute_testcase(execute_list[i].module_name,
            execute_list[i].testcase_name);
      }
    } else {
      TTCN_warning("Nothing to run!");
fail:
      ret_val = EXIT_FAILURE;
    }
  } catch (...) {
    TTCN_Logger::log_str(TTCN_Logger::ERROR_UNQUALIFIED,
      "Fatal error. Aborting execution.");
    ret_val = EXIT_FAILURE;
  }
  TTCN_Runtime::restore_signal_handlers();
  TTCN_Runtime::log_verdict_statistics();
  TTCN_Logger::log_executor_runtime(
    TitanLoggerApi::ExecutorRuntime_reason::executor__finish__single__mode);
  TTCN_Logger::close_file();
  TCov::close_file();
  // close_file() must be called before the information is lost
  // close_file() WRITES to log

  TTCN_Logger::clear_parameters();
  PORT::clear_parameters();
  COMPONENT::clear_component_names();
  TTCN_EncDec::clear_error();

  for (i = 0; i < execute_list_len; i++) {
    Free(execute_list[i].module_name);
    Free(execute_list[i].testcase_name);
  }
  Free(execute_list);

  TTCN_Logger::terminate_logger();
  TTCN_Snapshot::terminate();
  TTCN_Runtime::clean_up();

  return ret_val;
}
