/** Copyright (c) 2020-2023 The Creators of Simphone

    main: parse command-line arguments and initialize QtSingleApplication and the main (contacts) window

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "qtfix.h"
#include "contacts.h"
#include "chatframe.h"
#include "sounds.h"
#include "settings.h"

#include "../qtsingleapplication/qtlocalpeer.h"
#include "../qtsingleapplication/QtSingleApplication"

#include "../simcore/config.h"

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>

#include <sys/socket.h>
#include <sys/un.h>

#include <unistd.h>
#include <signal.h>
#include <errno.h>

#ifndef _DEBUG
#define fprintf(f, ...)
#endif

static QByteArray g_serverName;

#define GetCurrentProcessId getpid

#ifdef DONOT_DEFINE //defined(SIM_MEMORY_CHECK) && defined(__unix__)
#include <execinfo.h>

void * operator new(std::size_t length)
{
  void * callers[2];
  callers[1] = (void *)-1;
  if (backtrace(callers, 2) <= 1) callers[1] = (void *)1;
  return _sim_new(length ? length : 1, SIMNIL, (const char *)callers[1], unsigned(-SIMPOINTER));
}

void operator delete(void * pointer)
{
  void * callers[2];
  callers[1] = (void *)-1;
  if (backtrace(callers, 2) <= 1) callers[1] = (void *)1;
  _sim_free(pointer, 0, (const char *)callers[1], unsigned(-SIMPOINTER));
}
#endif

static void quitApplication(int signum)
{
  static const char message[] = { 0x00, 0x00, 0x00, 0x06, 0x30, 0x20, 0x51, 0x55, 0x49, 0x54 }; // SINGLE_MESSAGE_QUIT
  struct sockaddr_un server;

  g_terminate_signal = signum;
  signal(signum, SIG_DFL);

  memset(&server, 0, sizeof(server));
  int fd = socket(server.sun_family = AF_UNIX, SOCK_STREAM, 0);
  if (fd >= 0) {
    strncpy(server.sun_path, g_serverName.data(), sizeof(server.sun_path) - 1);
    if (connect(fd, (struct sockaddr *)&server, sizeof(server))) {
      fprintf(stderr, "\nsignal %d: connect() error %d\n", signum, errno);
    } else if (write(fd, message, sizeof(message)) == sizeof(message)) {
      fprintf(stderr, "\nsignal %d\n", signum);
    } else {
      fprintf(stderr, "\nsignal %d: write() error %d\n", signum, errno);
    }
    close(fd);
  } else {
    fprintf(stderr, "\nsignal %d: socket() error %d\n", signum, errno);
  }
}

static void exitApplication(int signum)
{
  signal(signum, SIG_DFL);
  _exit(EXIT_CRASH_CRASHED);
}
#endif

static bool setEnv(QString name, const QString & value)
{
  name.append(value);
#ifdef _WIN32
  wchar_t * ws = wcsdup((wchar_t *)name.utf16());
  return ws && !_wputenv(ws);
#else
  char * s = strdup(name.toUtf8().data());
  return s && !putenv(s);
#endif
}

static QString getFileId(const QString & name, int * err = 0)
{
  QString id;
#ifndef _WIN32
  struct stat buf;

  errno = 0;
  if (!name.isEmpty() && !stat(name.toUtf8().data(), &buf)) {
    qulonglong dev = buf.st_dev;
    qulonglong ino = buf.st_ino;
    if (err) id = QFileInfo(name).fileName().prepend("../").append('/');
    id.append(QString::number(dev)).append('/').append(QString::number(ino));
  }
  if (err) *err = errno;
#else
  BY_HANDLE_FILE_INFORMATION buf;
  SetLastError(0);
  HANDLE handle = 0;
  if (!name.isEmpty()) {
    handle = CreateFileW((wchar_t *)name.utf16(), GENERIC_READ, FILE_SHARE_READ, 0,
                         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
  }
  if (handle && GetFileInformationByHandle(handle, &buf)) {
    qulonglong dev = buf.dwVolumeSerialNumber;
    qulonglong ino = (quint64)buf.nFileIndexHigh << 32 | buf.nFileIndexLow;
    if (err) id = QFileInfo(name).fileName().prepend("../").append('/');
    id.append(QString::number(dev)).append('/').append(QString::number(ino));
  }
  if (err) *err = GetLastError();
  if (handle) CloseHandle(handle);
#endif
  return id;
}

static bool checkSubDir(QString subDir, const QString & dir)
{
  QString id;
  QString temp = getFileId(dir);
  QString current = getFileId(subDir);

  if (!subDir.isEmpty() && !dir.isEmpty() && !temp.isEmpty()) {
    for (int i = 0; i < 1000 && !current.isEmpty() && current != id; i++) {
      if (current == temp) return true;
      id = current;
      current = getFileId(subDir.append("/.."));
    }
  }
  return false;
}

static QString getHelpString(const QString & name)
{
#ifdef __APPLE__
  bool nohidden = QSysInfo::macVersion() == QSysInfo::MV_10_6;
#else
  bool nohidden = false;
#endif
  QString qs = qtfix::tr("Usage: %1\n[options]").arg(name).replace('\n', '\t').append("\n");
#ifdef _WIN32
  qs.append("\n");
#endif
  qs.append(" -portable\t\t").append(qtfix::tr("Store all data to program directory")).append("\n");
  qs.append(" -user \"path\"\t\t").append(qtfix::tr("Set user directory (full or relative path)")).append("\n");
  qs.append(" -pass \"key\"\t\t").append(qtfix::tr("Login with the specified secret key or password")).append("\n");
  qs.append(" -nouser\t\t").append(qtfix::tr("Do not store any data to this computer")).append("\n");
  qs.append(" -minimized\t\t").append(qtfix::tr("Minimize or hide main window after automatic login")).append("\n");
  if (!nohidden) {
    qs.append(" -hidden\t\t").append(qtfix::tr("Stealth mode (do not display icon on the desktop)")).append("\n");
  }
  qs.append(" -nosound\t\t").append(qtfix::tr("Do not play sounds when window is not visible")).append("\n");
  qs.append(" -offline\t\t").append(qtfix::tr("Start in offline mode after successful login")).append("\n");
  qs.append(" -tor\t\t\t").append(qtfix::tr("Start offline, unless TOR mode is on")).append("\n");
  qs.append(" -cmd HIDE\t\t").append(qtfix::tr("Hide another already running instance")).append("\n");
  qs.append(" -cmd SHOW\t\t").append(qtfix::tr("Activate another already running instance")).append("\n");
#ifdef _DEBUG
  qs.append(" -cmd QUIT\t\t").append(qtfix::tr("Close another already running instance")).append("\n");
  qs.append(" -cmd \"cmd\"\t\t").append(qtfix::tr("Run console command at already running instance")).append("\n");
#endif
  qs.append(" -style windows\t\t").append(qtfix::tr("Use Windows-like GUI style")).append("\n");
  qs.append(" -style fusion\t\t").append(qtfix::tr("Use Qt-like GUI style")).append("\n");
  qs.append(" -stylesheet \"file\"\t").append(qtfix::tr("Use the specified QSS file"));
  return qs;
}

int main(int argc, char * argv[])
{
  int simres = sim_init_memory();
  simtype params;
  int autostart = -1;                                         // non-negative if -autostart
  int portable = 0;                                           // non-zero if -portable
  int fontsize = 0;                                           // non-zero if -fontsize
  bool hidden = false;                                        // true if -hidden
  bool nohotkeys = false;                                     // true if -nohotkeys
  QString user;                                               // empty if -nosingle
  QString arguments;                                          // only for logging
  QString message("");                                        // null if -nosingle, non-empty if -cmd
  QStringList args;                                           // arguments to parse
  QStringList autostartArgs(qtfix::fixArguments(argc, argv)); // arguments to give to QSettings
  QtSingleApplication * app = 0;                              // QApplication if -nosingle
  QString language;                                           // argument of -language or null if no -language
  QString libxss = "Xss";
  QString error;
  const char * locale;
  simunsigned time = sim_get_tick();
#ifdef _WIN32
  STARTUPINFO info;
  memset(&info, 0, sizeof(info));
  GetStartupInfo(&info);
  if (info.dwFlags & STARTF_USESHOWWINDOW && info.wShowWindow == SW_HIDE) {
    SimCore::mc_startupMinimized = true;
    hidden = true;
  }
#endif

  if (autostartArgs.isEmpty()) return EXIT_NO_SESSION;
#ifdef __unix__
  QString input;
#define XKB_CONFIG_PATH "/usr/local/share/X11/xkb:/usr/share/X11/xkb:/usr/local/lib/X11/xkb:/usr/lib/X11/xkb"
  if (!setEnv("GTK_IM_MODULE=", qgetenv("SIM_IM_MODULE"))) return EXIT_NO_MEMORY;
  if (qgetenv("QT_XKB_CONFIG_ROOT").isEmpty()) putenv((char *)"QT_XKB_CONFIG_ROOT=" XKB_CONFIG_PATH);
  putenv((char *)"XDG_CACHE_HOME=/this/path/does/not/exist");
#endif
  if (!setEnv("SIMHOME=", "")) return EXIT_NO_MEMORY;
  sim_init_bits(SIM_INIT_BIT_INSTALL);
  char ** argp = qtfix::getArguments();
  while (*argp) args << *argp++;
#ifdef _WIN32
  int newArgc = 0;
  LPWSTR * argw = CommandLineToArgvW(GetCommandLineW(), &newArgc);
  if (argw) {
    args = QStringList();
    for (int i = 0; i < newArgc; ++i) args << QString::fromUtf16((ushort *)argw[i]);
  }
#endif

  for (int i = 1; i < args.size(); ++i) {
    QString arg = args[i].toLower();
    if (arg == "-lang" || arg == "-language") {
      language = i < args.size() - 1 ? args[++i] : "";
    } else if (arg == "-fontsize") {
      fontsize = i < args.size() - 1 ? atoi(args[++i].toUtf8().data()) : 0;
      if (!fontsize) fontsize = -12;
#ifdef __unix__
    } else if (arg == "-input" && i < args.size() - 1) {
      input = args[++i];
#endif
    } else if (arg == "-user" && i < args.size() - 1) {
      SimCore::mc_startupUser = args[++i];
#ifdef _WIN32
      while (SimCore::mc_startupUser.endsWith('/') || SimCore::mc_startupUser.endsWith('\\')) {
        if (SimCore::mc_startupUser.size() == 3 && SimCore::mc_startupUser[1] == ':') break;
        SimCore::mc_startupUser.chop(1);
      }
#else
      while (SimCore::mc_startupUser.endsWith('/')) SimCore::mc_startupUser.chop(1);
#endif
    } else if (arg == "-nouser") {
      SimCore::mc_startupUser = QString();
    } else if (arg == "-portable") {
      portable = 1;
    }
  }

  simtype path = sim_init_path(SIM_PATH_EXE);
  QString exe = sim_get_pointer(path);
  QFileInfo exeInfo(exe);
  sim_string_free(path);
  g_exeName = exe.isEmpty() ? QByteArray() : exeInfo.filePath().toUtf8();

  if (portable && (exe.isEmpty() || exeInfo.path().isEmpty() || !setEnv("SIMHOME=", exeInfo.path()))) portable = -1;
  params = sim_init_param_(SimCore::mc_startupUser.isNull() ? 0 : SimCore::mc_startupUser.toUtf8().data());
  if (sim_get_type(params) == SIMNIL) params = sim_table_new(1);
#ifdef __unix__
  if (input.isNull()) input = sim_table_get_pointer(params, "ui.login.input");
  if (!input.isEmpty() && !setEnv("QT_IM_MODULE=", input)) return EXIT_NO_MEMORY;
#endif
  if (language.isNull()) language = sim_table_get_pointer(params, "ui.login.language");

  char * style = sim_table_get_pointer(params, "ui.login.stylesheetname");
  if (qtfix::getStyleSheetArgument().isEmpty() && style && *style) {
    argv[argc++] = (char *)"-stylesheet";
    argv[argc++] = style;
    argv[argc] = 0;
  }
  if (qtfix::setStyle(sim_table_get_pointer(params, "ui.login.stylename"), style)) {
    argv[argc++] = (char *)"-style";
    argv[argc++] = sim_table_get_pointer(params, "ui.login.stylename");
    argv[argc] = 0;
  }

  int pointSize = int(sim_table_get_number(params, "ui.login.fontsize"));
  int maxPointSize = int(sim_table_get_number(params, "ui.login.maxfontsize"));
  qtfix::setFontSize(fontsize ? fontsize : pointSize >= 8 ? pointSize : -12, maxPointSize);

  QStringList colors;
  simtype arrows = sim_table_get_array_string(params, "ui.color.arrows");
  for (unsigned i = sim_get_length(arrows); i; --i) colors.append(sim_array_get_pointer(arrows, i));
  qtfix::setPixmapColors(colors);

  for (int i = 1; i < args.size(); ++i) {
    bool unknown = false;
    QString arg = args[i].toLower();

    if (arg == "-help" || arg == "-h") {
      QApplication application(argc, argv);
      qtfix::fixApplication(language);
#ifdef _WIN32
      QString help = getHelpString(SIM_UI_NAME);

      help = help.replace("\t\t", "\t").replace("\t\t", "\t").toHtmlEscaped().replace("\n -", "\n<pre>-");
      help = help.replace(" ", "&nbsp;").replace("\t", "</td><td>").replace("\n", "</td></tr><tr><td>");
      QMessageBox::information(0, qtfix::tr("Command-line arguments"), "<table><tr><td>" + help + "</td></tr></table>");
#else
      printf("%s\n", getHelpString(args[0]).toUtf8().data());
#endif
      return EXIT_HELP_REPORTED;
    }
    if (arg == "-crash") {
      QString qs = i < args.size() - 1 ? args[i + 1] : "";
      QString msg = qs;
      int idx = qs.indexOf("\"");

      if (idx >= 0) msg = qs.left(idx);
      qs.replace("\"", "\n");
#ifdef _WIN32
      SetErrorMode(SEM_NOGPFAULTERRORBOX);
#else
      struct rlimit coreLimit;
      memset(&coreLimit, 0, sizeof(coreLimit));
      setrlimit(RLIMIT_CORE, &coreLimit);
      signal(SIGPIPE, SIG_IGN);
      signal(SIGSEGV, exitApplication);

      fflush(stdout);
      fprintf(stderr, "\nSIMPHONE HAS CRASHED.\nPlease, do report this incident to the Creators of Simphone.\n");
      fprintf(stderr, "We are sorry for the inconvenience.\n\n%s\n\n", qs.toUtf8().data());

      for (int fd = 0; fd < 65536; ++fd) {
        struct stat buf;

        if (!fstat(fd, &buf) && buf.st_mode & S_IFSOCK) close(fd);
      }
#endif
      if (!hidden) {
        QApplication application(argc, argv);
        qtfix::fixApplication(language);
        SimCore::readStyleSheet('c');

        msg = "<p><b>" + msg.toHtmlEscaped() + "</b></p><p>";
        msg.append(qtfix::tr("Simphone has crashed.")).append("<br/><br/>");
        msg.append(qtfix::tr("Please, do report this incident to the Creators of Simphone.")).append("<br/><br/>");
        msg.append(qtfix::tr("We are sorry for the inconvenience.")).append("</p>");
        QMessageBox mbox(QMessageBox::Critical, qtfix::tr("Simphone error"), msg, QMessageBox::Ok, 0, Qt::Dialog);
        if (idx >= 0) mbox.setDetailedText(qs);
        mbox.exec();
      }
      return EXIT_CRASH_REPORTED;
    }

    if (arg == "-minimized" || arg == "-minimize") {
      arguments.append(" -minimized");
      SimCore::mc_startupMinimized = true;
    } else if (arg == "-offline") {
      arguments.append(" -offline");
      SimCore::mc_startupOffline = true;
    } else if (arg == "-tor") {
      autostartArgs << arg;
      arguments.append(" -tor");
      SimCore::mc_startupSocks = true;
    } else if (arg == "-nouser" || arg == "-portable") {
      arguments.append(" ").append(arg);
#ifdef __APPLE__
    } else if (arg == "-hidden" && QSysInfo::macVersion() != QSysInfo::MV_10_6) {
#else
    } else if (arg == "-hidden") {
#endif
      autostartArgs << arg;
      arguments.append(" -hidden");
      hidden = true;
    } else if (arg == "-nohotkeys" || arg == "-nohotkey") {
      arguments.append(" ").append(arg);
      nohotkeys = true;
    } else if (arg == "-nosingle" || arg == "---INST") {
      arguments.append(" ").append(arg);
      message = QString();
    } else if (arg == "-noinstall") {
      autostartArgs << arg;
      arguments.append(" -noinstall");
      sim_init_bits(SIM_INIT_BIT_DEFAULT);
    } else if (arg == "-nosound") {
      if (i >= args.size() - 1 || args[i + 1][0] == '-') {
        autostartArgs << arg;
        arguments.append(" -nosound");
        SoundEffects::stopInvisibleSound();
      } else {
        SoundEffects::stopInvisibleSound(args[++i].toUtf8().data());
        autostartArgs << arg << args[i];
        arguments.append(" -nosound ").append(args[i]);
      }
    } else if (arg == "-autostart") {
      if (i >= args.size() - 1 || args[i + 1][0] == '-') {
        autostart = 0;
        arguments.append(" -autostart");
      } else {
        autostart = args[++i].toInt();
        arguments.append(" -autostart ").append(args[i]);
      }
    } else if (arg == "-libxss") {
      if (i >= args.size() - 1 || args[i + 1][0] == '-') {
        libxss = "";
        arguments.append(" ").append(arg);
      } else {
        libxss = args[++i];
        arguments.append(" ").append(arg).append(args[i]);
      }
    } else if (i < args.size() - 1) {
      if (arg == "-user" || arg == "-lang" || arg == "-language" || arg == "-input") {
        arguments.append(" ").append(arg).append(" \"").append(args[++i]).append("\"");
      } else if (arg == "-pass") {
        arguments.append(" -pass PASSWORD");
        SimCore::mc_startupPass = args[++i];
      } else if (arg == "-cmd") {
        message = args[++i];
        if (message == SINGLE_MESSAGE_SHOW) {
          if (SimCore::mc_startupMinimized) message.append(" -minimized");
        } else if (message != SINGLE_MESSAGE_HIDE && message != SINGLE_MESSAGE_QUIT) {
          message.insert(0, SINGLE_MESSAGE_EXEC " ");
        }
        arguments.append(" -cmd \"").append(message).append("\"");
      } else if (arg == "-style" || arg == "-stylesheet" || arg == "-fontsize") {
        ++i;
      } else if (arg.left(7) != "-style=" && arg.left(12) != "-stylesheet=") {
        unknown = true;
      }
    } else if (arg.left(7) != "-style=" && arg.left(12) != "-stylesheet=") {
      if (arg != "-fontsize" && arg != "-language" && arg != "-lang") unknown = true;
    }

    if (unknown) {
      for (int j = 0; j < autostartArgs.size(); ++j) {
        if (autostartArgs[j] == arg) unknown = false;
      }
      if (unknown) printf(SIM_UI_NAME ": unknown argument %s\n", arg.toUtf8().data());
    }
  }
  Settings::setAutostartArguments(autostartArgs);
  Contacts::setExtensions(libxss, !nohotkeys);

  if (simres != SIM_OK) {
    if (!hidden || !SimCore::mc_startupMinimized) {
      QApplication application(argc, argv);
      qtfix::fixApplication(language);

      error = qtfix::tr("Memory checker initialization not successful (error %1)").arg(simres);
      qtfix::execMessageBox(true, error, qtfix::tr("We are sorry for the inconvenience."), 0);
    }
    return EXIT_NO_MEMORY;
  }

  if (portable < 0) {
    if (!hidden || !SimCore::mc_startupMinimized) {
      QApplication application(argc, argv);
      qtfix::fixApplication(language);

      error = qtfix::tr("Portable application is not available with this system.");
      error.append("\n\n").append(qtfix::tr("We are sorry for the inconvenience."));
      QMessageBox::critical(0, qtfix::tr("Cannot find program directory"), error);
    }
    return EXIT_NO_DIRECTORY;
  }

  if (autostart >= 0) {
    simtype param = sim_table_get_type_number(params, "ui.login.autostart");
    if (sim_get_type(param) != SIMNIL) autostart = int(sim_get_number(param));
  }
  if (autostart > 0) {
#ifdef _WIN32
    Sleep(autostart * 1000);
#else
    sleep(autostart);
#endif
  }

  if (/*!SimCore::mc_startupUser.isNull() &&*/ !exe.isEmpty()) {
#ifdef _WIN32
    if (checkSubDir(exeInfo.dir().path(), QDir::tempPath())) {
#else
    if (checkSubDir(exeInfo.dir().path(), QDir::tempPath()) || checkSubDir(exeInfo.dir().path(), "/tmp")) {
#endif
      if (!hidden || !SimCore::mc_startupMinimized) {
        QApplication application(argc, argv);
        qtfix::fixApplication(language);

        QMessageBox::critical(0, qtfix::tr("Started from a temporary directory"),
                              qtfix::tr("Unpack Simphone from the archive to another convenient location and try"
                                        " again.\n\nThe easiest way to do so is drag and drop Simphone to your desktop"
                                        " and then start it from the desktop.\n\nSimphone will now exit. Thank you."));
      }
      return EXIT_TEMP_DIRECTORY;
    }
  }

  if (!message.isNull() && !SimCore::mc_startupUser.isNull()) {
    user = sim_get_pointer(path = sim_init_path_(SimCore::mc_startupUser.toUtf8().data(), autostart < 0));
    sim_string_free(path);
  }

#ifdef __APPLE__
  if (hidden && putenv((char *)"QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM=1")) return EXIT_NO_MEMORY;
#endif

  if (!user.isEmpty()) {
    int err;
    QString id = getFileId(user, &err);

    if (id.isEmpty()) {
      if (!hidden || !SimCore::mc_startupMinimized) {
        QString qs = "<p><b>";
        QApplication application(argc, argv);
        qtfix::fixApplication(language);
        QString simerr = SimCore::getError(err);

        qs.append(qtfix::tr("Accessing user directory not successful (%1)").arg(err).replace(" ", "&nbsp;"));
        qs.append("</b><br/><br/>").append(simerr.toHtmlEscaped().replace("\n", "<br/>"));
        qs.append("<br/><br/><b>").append(user.toHtmlEscaped()).append("</b><br/><br/>");
        if (autostart >= 0) {
          QMessageBox::StandardButton button;
          const QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
          QString qs2 = qtfix::tr("If you cannot avoid this error, push <b>%1</b> to remove Simphone autostart"
                                  " from the system registry. Otherwise, close this window and try again.<br/><br/>"
                                  "Do you want to remove Simphone autostart now?");
          qs.append(qs2.arg(qApp->translate("QShortcut", "Yes")));
          button = QMessageBox::question(0, qtfix::tr("Simphone error"), qs.append("</p>"), buttons, QMessageBox::No);
          if (button == QMessageBox::Yes && (err = Settings::setAutostart(0, false)) != 0) {
            error = qtfix::tr("Setting up autostart not successful (error %1)").arg(err);
            qtfix::execMessageBox(true, error, qtfix::tr("Simphone could not write to the system registry."), 0);
          }
        } else {
          qs.append(qtfix::tr("Mount this directory and try again. Simphone will now exit.<br/><br/>If you cannot"
                              " avoid this error, start Simphone with the <b>-nosingle</b> command-line argument."));
          QMessageBox::critical(0, qtfix::tr("Simphone error"), qs.append("</p>"));
        }
      }
      return EXIT_NO_AUTOSTART;
    }
    app = new QtSingleApplication(id, argc, argv);
  } else {
    app = (QtSingleApplication *)new QApplication(argc, argv);
  }

  locale = qtfix::fixApplication(language);
  time = sim_get_tick() - time;
  if (time > 20000) {
    printf("\n" SIM_UI_NAME ": %s\n", qtfix::tr("initialization took %1 seconds").arg(time / 1000).toUtf8().data());
  }

  if (hidden) {
    if (SimCore::mc_startupMinimized) {
      sim_init_bits(SIM_INIT_BIT_DEFAULT); // -noinstall
      SimCore::hide();
    } else {
      SimCore::unhide();
    }
  }

  if (g_exeName.isEmpty()) g_exeName = QCoreApplication::applicationFilePath().toUtf8();
#ifdef _WIN32
  g_exeName.replace('/', '\\');
#endif
  app->setOrganizationDomain("org");
  app->setOrganizationName(SIM_UI_NAME);
  app->setApplicationName(SIM_UI_NAME);
  app->setApplicationVersion(PACKAGE_VERSION);

  int result = EXIT_CANNOT_START;
  quint64 pid = 0;

  if (!user.isEmpty()) {
    QFile file(user.append("/pid"));

    if (file.open(QIODevice::ReadOnly | QIODevice::Text | QIODevice::Unbuffered)) {
      QString pidStr = file.readLine();
      pid = pidStr.toULongLong();
      file.close();
    }
  }
#ifdef _DEBUG
  if (!qgetenv("SIMPHONE_NODHT").isEmpty()) sim_init_bits(SIM_INIT_BIT_NODHT);
#endif
  if (message.isEmpty()) simres = SimCore::create();
  if (locale && !strcmp(locale, "C") && sim_error_init_language(-1) < 0) locale = "0";

  if (!user.isEmpty() && app->isRunning()) {
    int delay = int(sim_table_get_number(params, "ui.login.wait"));
    bool running = false;

    delay = delay ? delay * 1000 : 1000;
    if (simres == SIM_OK && message.isEmpty()) {
      SimCore::get()->destroy();
    } else {
      running = SimCore::checkProcess(pid, false);
    }

    QString rpcMessage = QString::number(GetCurrentProcessId());
    bool ok = app->sendMessage(rpcMessage.append(" ").append(message.isEmpty() ? SINGLE_MESSAGE_SHOW : message), delay);

    if (message.isEmpty()) {
      result = EXIT_CANNOT_KILL;
      if (!running) goto fail;
#ifdef _WIN32
      if (ok) Sleep(delay);
#else
      if (ok) sleep(delay / 1000);
#endif
      if (!SimCore::checkProcess(pid, false)) return SimCore::execProcess();
      if (SimCore::isHidden() != 'h') {
        QString qs = qtfix::tr("Simphone (pid %1) is already running but not responding.\n\n"
                               "Do you want to terminate the non-responsive process?");
        const QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
        qs = qs.arg(pid);
        if (QMessageBox::question(0, qtfix::tr("Simphone error"), qs, buttons, QMessageBox::Yes) != QMessageBox::No) {
          if (!SimCore::checkProcess(pid, false) || SimCore::killProcess(pid, delay)) {
#ifndef _WIN32
            QFile::remove(user);
#endif
            return SimCore::execProcess();
          }
          qs = qtfix::tr("Simphone (pid %1) could not be terminated (error %2)").arg(pid).arg(errno).append("\n\n");
          qs.append(qtfix::tr("Please, try again. If this error message persists, restart your computer."));
          QMessageBox::critical(0, qtfix::tr("Simphone error"), qs);
        } else {
          if (!SimCore::checkProcess(pid, false)) return SimCore::execProcess();
        }
      }
    }
  } else if (simres != SIM_OK) {
    if (!user.isEmpty()) {
      delete app->peer;
      app->peer = 0;
    }
    result = EXIT_CANNOT_INIT;
  fail:
    if (simres == SIM_OK) simres = SIM_FILE_LOCKED;
    if (SimCore::isHidden() != 'h') {
      QString simerr = SimCore::getError(simres);
      if (simres == SIM_FILE_LOCKED) {
        if (SimCore::checkProcess(pid, false)) simerr.append(qtfix::tr(" (pid %1)").arg(pid));
        simerr.append("\n\n");
        simerr.append(qtfix::tr("Please, try again. If this error message persists, restart your computer."));
      }
      if (autostart < 0 || simres != SIM_FILE_LOCKED) {
        qtfix::execMessageBox(true, qtfix::tr("Start not successful (%1)").arg(simres), simerr, 0);
      }
    }
  } else if (message.isEmpty()) {
#ifndef _WIN32
    if (!user.isEmpty()) {
      g_serverName = app->peer->server->fullServerName().toUtf8();
      signal(SIGTERM, quitApplication);
      signal(SIGINT, quitApplication);
      signal(SIGHUP, SIG_IGN);
    }
#else
    SetProcessShutdownParameters(0x100, 0);
    SetProcessShutdownParameters(0, 0);
#endif

    log_debug_("ui", "init%s (pid = %lld) %lld ms '%s %s'\n", arguments.toUtf8().data(),
               quint64(GetCurrentProcessId()), time, QLocale().bcp47Name().toUtf8().data(), locale);
    if (autostart >= 0 && !SimParam::get("ui.login.autostarted")) SimParam::set("ui.login.autostarted", 1, false);
    if (fontsize) SimParam::set("ui.login.fontsize", qMin(SimParam::getMaxValue("ui.login.fontsize"), fontsize), true);

    {
      SimCore::readStyleSheet('i');
      Contacts contacts;
      contacts.setAutoStart(autostart >= 0);
      new ChatFrames(); // do not destroy ChatFrames before Contacts. it will crash on shutdown.

      if (!user.isEmpty()) {
        app->connect(app, &QtSingleApplication::messageReceived, SimCore::get(), &SimCore::onRPCmessageReceived);
      }
      app->setQuitOnLastWindowClosed(false);
      app->connect(app, SIGNAL(commitDataRequest(QSessionManager &)),
                   &contacts, SLOT(onCommitDataRequest(QSessionManager &)));
      app->connect(app, SIGNAL(saveStateRequest(QSessionManager &)),
                   &contacts, SLOT(onSaveStateRequest(QSessionManager &)));
      app->connect(app, SIGNAL(aboutToQuit()), &contacts, SLOT(onAboutToQuit()));

      QTimer::singleShot(0, SimCore::get(), SLOT(onLogin()));
      result = app->exec();

      //ChatFrames::get()->closeAll();
      contacts.setQuitting();
      contacts.close();
#ifdef __unix__
      if (contacts.isCommitted()) app = 0;
#endif
    }
    log_debug_("ui", "exit %d (pid %lld)\n", result, quint64(GetCurrentProcessId()));

    sim_table_free(params);
    SimCore::get()->destroy();
  }

  delete app;
  return result;
}
