/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2007-02-08
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <time.h>
#include <QtCore>

#if defined(Q_OS_WIN)
#  include <unistd.h>
#  include <windows.h>
#endif

#if defined(Q_OS_UNIX) && !defined(Q_CC_SUN) && !defined(NO_BACKTRACE)
#  include <execinfo.h>
#endif

#include <signal.h>
#include <cxxabi.h>
#ifndef Q_OS_WIN
#  include <sys/ioctl.h>
#  include <sys/sysctl.h>
#  include <fcntl.h>
#  include <sys/types.h>
#  include <sys/param.h>
#endif

#include "CoreApplicationPrivate.h"
#include "StandardStream.h"
#include "Thread.h"
#include "TraceBug.h"
#include "Global.h"
#include "StringSection.h"
#include "PackageInfo.h"
#include "Message.h"
#include "File.h"
#include "PathTranslator.h"
#include "XMLClass.h"
#include "XMLStringStream.h"
#include "XMLClassFactory.h"
#include "MemoryCheckerPrivate.h"
#include "QGpCoreToolsVersion.h"
#include "Thread.h"

namespace QGpCoreTools {

  bool GlobalObject::inherits(const char *)
  {
    return false;
  }

  CoreApplicationPrivate * CoreApplicationPrivate::_self=nullptr;
  QString CoreApplicationPrivate::_crashingThread;
  QStringList CoreApplicationPrivate::_argumentList;

  /*!
    \class CoreApplicationPrivate CoreApplicationPrivate.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  /*!
    Initialize the application and analyse arguments.
  */
  void CoreApplicationPrivate::init(int & argc, char ** argv, bool setPath)
  {
    if(!_self) {
      _self=this;
    } else {
      qWarning("Only one instance of Application or CoreApplication is allowed.");
      abort();
    }

    // All conversion from char * to QString will assume UTF-8
#if(QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
    QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
#endif
    if(setPath) {
      setLibraryPath();
    }

    srand(static_cast<uint>(time(nullptr))); // init random generator

#ifdef DO_MEMORY_CHECK
    // Activate recording for undeleted blocks
    MemoryCheckerPrivate::setRecord(true);
#endif
    _settings=nullptr;
    _globalSettings=nullptr;
    // Record application main stream
    _thread=QThread::currentThread();
    // Default stream
    setStream(new StandardStream(stderr), _thread);
    // Message handler is initialized by CoreApplication and Application
    _messageHandler=nullptr;
    // Facility to translate path if absolute file path stored in files are changed
    _pathTranslator=nullptr;
    // Size of terminal
    _terminalCols=0;
    _terminalRows=0;
    // Maximum number of thread for parallel jobs. On environment like OAR, maximum number of thread
    // takes CPU affinity list into account.
    Thread::initCPUAffinityList();
    _maximumThreadCount=QThread::idealThreadCount(); // CPU Affinity is not considered by Qt
    _maximumThreadCount=Thread::idealThreadCount();  // Uses maximumThreadCount initialized before
    // Debug or not user interrupts, can be turned on or off when waiting for stdin for instance.
    _debugUserInterrupts=false;
    _reportBugs=false;
    _warningAsError=false;
    _batch=true;    // Core application are by default in batch
                    // But their argument list is saved except if
                    // option -batch is explicitly used

    // Check arguments
    if(_argumentList.isEmpty()) {
      setArgumentList(argc, argv);
    }
    int i, j;
    j=1;
    for(i=1; i<argc; i++) {
      QByteArray arg=argv[i];
      if(arg=="--version" || arg=="-version") {
        QTextStream sOut(stdout);
        sOut << "Versions:\n" << version() << Qt::flush;
        ::exit(0);
      } else if(arg=="--app-version" || arg=="-app-version") {
        QTextStream sOut(stdout);
        sOut << constructorApplicationName() << " " << version(constructorApplicationName()) << Qt::endl;
        ::exit(0);
      } else if(arg=="--verbosity" || arg=="-verbosity") {
        checkOptionArg(i, argc, argv, true);
        AbstractStream::setApplicationVerbosity(atoi(argv[i]));
      } else if(arg=="-warning-critical") {
        _warningAsError=true;
      } else if(arg=="-locale") {
        checkOptionArg(i, argc, argv, true);
        QLocale::setDefault(QLocale(argv[i]));
      } else if(arg=="-sleep") {
        checkOptionArg(i, argc, argv, true);
        sleep(atoi(argv[i])*1000);
      } else if(arg=="-j" || arg=="-jobs") {
        checkOptionArg(i, argc, argv, true);
        _maximumThreadCount=atoi(argv[i]);
        QTextStream sOut(stderr);
        if(_maximumThreadCount<1) {
          sOut << tr("Negative or null maximum number of jobs (option -j or -jobs)") << Qt::endl;
          ::exit(2);
        }
        if(_maximumThreadCount>2*QThread::idealThreadCount()) {
          QTextStream sOut(stderr);
          sOut << tr("Too many parallel jobs compared to the number of physical cores (option -j or -jobs).\n"
              "Maximum value is %1.").arg(QThread::idealThreadCount()) << Qt::endl;
          ::exit(2);
        }
#ifdef Q_OS_MAC
      } else if(arg.left(5)=="-psn_") { // does nothing: just get rid of that argument
#endif // Q_OS_MAC
      } else {
        argv[j++]=argv[i];
      }
    }
    if(j < argc) {
      argv[j]=nullptr;
      argc=j;
    }

    _numberEngine=new Number;
  }

  /*!
    Description of destructor still missing
  */
  CoreApplicationPrivate::~CoreApplicationPrivate()
  {
    delete _numberEngine;
    delete _globalSettings;
    delete _settings;
  }

  /*!
    In normal destructor, QCoreApplication is no longer available, hence strange behavior.
    This function must be called by CoreApplication and other related objects
  */
  void CoreApplicationPrivate::destructorCleanUp()
  {
    delete _pathTranslator;
    XMLClassFactory::clearFactories();
    delete _messageHandler;
    qDeleteAll(_plugins);
    TraceLog::clearMainVector();
      // Cleanup global pointers
    qDeleteAll(_globalPointers);
    // Clear streams
    foreach(AbstractStream * s, _streams) {
      AbstractStream::removeReference(s);
    }
    checkBlocks(false, true);
    if(!_errors.isEmpty()) {
      reportBugNow(Message::Warning, "Quitting application with remaining error messages" );
    }
  }

  void CoreApplicationPrivate::initInternalDebugger(bool reportBugs)
  {
    _reportBugs=reportBugs;
    if(_reportBugs) {
      // Redirect exception signals
      signal (SIGABRT, osSignal);
      signal (SIGSEGV, osSignal);
#ifndef Q_OS_WIN
      signal (SIGBUS, osSignal);
#endif
      // User interrupts
      _debugUserInterrupts=true;
      signal (SIGINT, osSignal);
      signal (SIGTERM, osSignal);
      // Redirect ASSERT messages
#if(QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
      qInstallMessageHandler(messageOutput);
#else
      qInstallMsgHandler(messageOutput);
#endif
    } else {
      // Redirect ASSERT messages
#if(QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
      qInstallMessageHandler(messageOutputAbort);
#else
      qInstallMsgHandler(messageOutputAbort);
#endif
    }
  }

  void CoreApplicationPrivate::debugUserInterrupts(bool d)
  {
    TRACE;
    if(_reportBugs) {
      if(_debugUserInterrupts!=d) {
        if(d) {
          signal(SIGINT, osSignal);
          signal(SIGTERM, osSignal);
        } else {
          signal(SIGINT, nullptr);
          signal(SIGTERM, nullptr);
        }
        _debugUserInterrupts=d;
      }
    }
  }

  /*!
    Check whether atomic operations are available for current processor and issue
    warnings if not satisfied. Implemented for Qt>=4.4.0. Streams must be initialized
  */
  void CoreApplicationPrivate::checkAtomicOperations()
  {
#if(QT_VERSION >= QT_VERSION_CHECK(4, 4, 0))
    /* Currently not used in sesarray package so left commented
    if( !QAtomicInt::isReferenceCountingNative()) {
      App::stream(1) << "WARNING: Atomic 'reference counting' is not available for your processor" << endl;
    }
    if(!QAtomicInt::isFetchAndAddNative()) {
      App::stream(1) << "WARNING: Atomic 'fetch and add' is not available for your processor" << endl;
    }
    */
    if(!QAtomicInt::isTestAndSetNative()) {
      App::log(1, "WARNING: Atomic 'test and set' is not available for your processor\n");
    }
    if(!QAtomicInt::isFetchAndStoreNative()) {
      App::log(1, "WARNING: Atomic 'fetch and store' is not available for your processor\n");
    }
#endif
  }

  /*!
    Internal use only: needed in CoreApplication and Application constructors.
    There QCoreApplication::applicationName() is not yet available. As progam is executed
    this function no longer returns the application name if plugins are loaded.
  */
  QString CoreApplicationPrivate::constructorApplicationName()
  {
    return PackageInfo::list()->last().package();
  }

  /*!
    Returns a complete description of all versions
  */
  QString CoreApplicationPrivate::version()
  {
    QString str="  Qt                  ";
    str+=qVersion();
    str+="\n";
    for(QList<PackageInfo>::iterator it=PackageInfo::list()->begin();it!=PackageInfo::list()->end(); it++ ) {
      str+="  "+it->package().leftJustified(20,' ')+it->version();
      if(it->distribution().isEmpty()) {
        str+="\n";
      } else {
        str+=" from "+it->distribution()+"\n";
      }
      str+="      (commit "+it->gitCommit()+")\n";
    }
    return str;
  }

  /*!
    Returns the version of \a item
  */
  QString CoreApplicationPrivate::version(QString item)
  {
    for(QList<PackageInfo>::iterator it=PackageInfo::list()->begin();it!=PackageInfo::list()->end(); it++ ) {
      if(it->package()==item) {
        return it->version();
      }
    }
    return QString();
  }

  /*!
    Returns the authorship message
  */
  QString CoreApplicationPrivate::authors()
  {
    QString appName=QCoreApplication::applicationName();
    for(QList<PackageInfo>::iterator it=PackageInfo::list()->begin();it!=PackageInfo::list()->end(); it++ ) {
      if(it->package()==appName) {
        return it->authors();
      }
    }
    return QString();
  }

  void CoreApplicationPrivate::initTranslations()
  {
    QLocale current;
    QString name=current.name();
    App::log(1, QString("Current Locale='%1' decimal point='%2'\n").arg(name).arg(QLocale().decimalPoint()));
    for(QList<PackageInfo>::iterator it=PackageInfo::list()->begin();it!=PackageInfo::list()->end(); it++ ) {
      QString langFile=it->package()+"."+name;
      QTranslator * t=new QTranslator(QCoreApplication::instance());
      QDir d(it->shareDir());
      d.cd("lang");
      if(t->load(langFile, d.absolutePath())) {
        QCoreApplication::instance()->installTranslator(t);
      } else {
        delete t;
        switch (current.language()) {
        case QLocale::C:
        case QLocale::English:
          break;
        default: {
            // In any case the graphical log viewer is still not started. Hence this message will be lost
            // to system terminal. For command line tools (e.g. gpdcreport) these kind of message must be
            // sent through stderr and the redirection of the main application stream is done after the
            // application initialization. Therefore we use the normal stderr directly.
            // Remove the warning... rather useless, kept only for debug.
            App::log(1, QString("No translation found for package %1 and language %2\n").arg(it->package()).arg(name));
          }
          break;
        }
      }
    }
    // We are usually not dealing with prices... never include group separators
    current.setNumberOptions(QLocale::OmitGroupSeparator);
    QLocale::setDefault(current);
  }

  void CoreApplicationPrivate::setMemoryVerbose(bool v)
  {
#ifdef DO_MEMORY_CHECK
    MemoryCheckerPrivate::setVerbose(v);
#else
    Q_UNUSED(v)
#endif
  }

  /*!
    Check for undeleted blocks
  */
  void CoreApplicationPrivate::checkBlocks(bool showLib, bool showUndeleted, const char* file, int line)
  {
#ifdef DO_MEMORY_CHECK
    MemoryCheckerPrivate::checkBlocks(showLib, showUndeleted, file, line);
#else
    Q_UNUSED(showLib)
    Q_UNUSED(showUndeleted)
    Q_UNUSED(file)
    Q_UNUSED(line)
#endif
  }

  /*!
    Check for damaged blocks
  */
  bool CoreApplicationPrivate::isBlockDamaged(void* v, const char* file, int line)
  {
#ifdef DO_MEMORY_CHECK
    return MemoryCheckerPrivate::isBlockDamaged(v, file, line);
#else
    Q_UNUSED(v)
    Q_UNUSED(file)
    Q_UNUSED(line)
    return false;
#endif
  }

  /*!
    Print block information
  */
  void CoreApplicationPrivate::printBlockInfo(void * ptr, const char* file, int line)
  {
#ifdef DO_MEMORY_CHECK
    MemoryCheckerPrivate::printBlockInfo(ptr, file, line);
#else
    Q_UNUSED(ptr)
    Q_UNUSED(file)
    Q_UNUSED(line)
#endif
  }

  void CoreApplicationPrivate::setMessageHandler(Message * m)
  {
    delete _messageHandler;
    _messageHandler=m;
  }

  bool CoreApplicationPrivate::freezeStream(bool b) const
  {
    return stream()->setActive(!b);
  }

  void CoreApplicationPrivate::setStream(AbstractStream * s, QThread * thread)
  {
    if(!thread) {
      thread=QThread::currentThread();
    }
    QMap<QThread *, AbstractStream *>::iterator it;
    it=_streams.find(thread);
    if(it!=_streams.end()) {
      AbstractStream * original=it.value();
      AbstractStream::removeReference(original);
      it.value()=s;
    } else {
      _streams.insert(thread, s);
    }
    s->addReference();
  }

  /*!
    Begins a redirection to stream \a s.
  */
  void CoreApplicationPrivate::beginRedirectStream(AbstractStream * s, QThread * thread)
  {
    if(!thread) {
      thread=QThread::currentThread();
    }
    QMap<QThread *, AbstractStream *>::iterator it;
    it=_streams.find(thread);
    ASSERT(it!=_streams.end());
    AbstractStream * original=it.value();
    original->setNext(s);
    s->setPrevious(original);
    AbstractStream::removeReference(original);
    it.value()=s;
    s->addReference();
  }

  /*!
    Ends a redirection to stream \a s.
  */
  void CoreApplicationPrivate::endRedirectStream(AbstractStream * s, QThread * thread)
  {
    ASSERT(s->previous());
    if(!thread) {
      thread=QThread::currentThread();
    }
    if(s->next()) { // This is not the current stream, another redirection is active
      s->next()->setPrevious(s->previous()); // s might be deleted here according to reference counting
    } else {
      QMap<QThread *, AbstractStream *>::iterator it;
      it=_streams.find(thread);
      ASSERT(it!=_streams.end());
      ASSERT(it.value()==s);
      AbstractStream * original=s->previous();
      original->setNext(nullptr);
      it.value()=original;
      original->addReference();
      AbstractStream::removeReference(s);
    }
  }

  /*!
    Returns a reference to the current stream of \a thread.
  */
  AbstractStream * CoreApplicationPrivate::stream(QThread * thread) const
  {
    if(!thread) {
      thread=QThread::currentThread();
    }
    QMap<QThread *, AbstractStream *>::const_iterator it;
    it=_streams.find(thread);
    if(it==_streams.end()) { // Current thread has no attached stream, back to application
                             // default stream.
       it=_streams.find(_thread);
    }
    return it.value();
  }

  /*!
    Remove references attached to \a thread.
  */
  void CoreApplicationPrivate::threadDeleted(Thread * thread)
  {
    QMap<QThread *, AbstractStream *>::const_iterator it;
    it=_streams.find(thread);
    if(it!=_streams.end()) {
      AbstractStream::removeReference(it.value());
      _streams.remove(thread);
    }
  }

  /*!
    Receives all message from Qt library, stack warning or output bugs
  */
#if(QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
  void CoreApplicationPrivate::messageOutput(QtMsgType type, const QMessageLogContext &, const QString & msg)
  {
    qInstallMessageHandler(nullptr); // avoid recursion
    switch (type) {
    case QtDebugMsg:
#ifdef QT_NO_DEBUG
      instance()->reportBug(Message::Information, msg);
#else
      printf("[qDebug] Information: %s\n", msg.toLatin1().data());
#endif
      break;
    case QtInfoMsg:
      instance()->reportBug(Message::Information, msg.toLatin1().data());
      break;
    case QtWarningMsg:
      instance()->reportBug(Message::Warning, msg.toLatin1().data());
      break;
    case QtCriticalMsg:
      instance()->reportBug(Message::Critical, msg.toLatin1().data());
      break;
    case QtFatalMsg:
      instance()->reportBugNow(Message::Fatal, msg.toLatin1().data());
      ::exit(2);
    }
    qInstallMessageHandler(messageOutput);
  }
#else
  void CoreApplicationPrivate::messageOutput(QtMsgType type, const char *msg)
  {
    qInstallMsgHandler(0); // avoid recursion
    switch (type) {
    case QtDebugMsg:
#ifdef QT_NO_DEBUG
      instance()->reportBug(Message::Information, msg);
#else
      printf("[qDebug] Information: %s\n", msg);
#endif
      break;
    case QtWarningMsg:
      instance()->reportBug(Message::Warning, msg);
      break;
    case QtCriticalMsg:
      instance()->reportBug(Message::Critical, msg);
      break;
    case QtFatalMsg:
      instance()->reportBugNow(Message::Fatal, msg);
      ::exit(2);
    }
    qInstallMsgHandler(messageOutput);
  }
#endif

  /*!
    Receive all message from Qt library, stack warning or output bugs
  */
#if(QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
  void CoreApplicationPrivate::messageOutputAbort(QtMsgType type, const QMessageLogContext &, const QString & msg)
  {
    qInstallMessageHandler(nullptr); // avoid recursion
    switch (type) {
    case QtDebugMsg:
    case QtInfoMsg:
      printf("[qDebug] Information: %s\n", msg.toLatin1().data());
      break;
    case QtWarningMsg:
      printf("[qDebug] Warning: %s\n", msg.toLatin1().data());
      break;
    case QtCriticalMsg:
      printf("[qDebug] Critical: %s\n", msg.toLatin1().data());
      break;
    case QtFatalMsg:
      printf("[qDebug] Fatal: %s\n", msg.toLatin1().data());
      abort();
    }
    qInstallMessageHandler(messageOutputAbort);
  }
#else
  void CoreApplicationPrivate::messageOutputAbort(QtMsgType type, const char * msg)
  {
    qInstallMsgHandler(0); // avoid recursion
    switch (type) {
    case QtDebugMsg:
      printf("[qDebug] Information: %s\n", msg);
      break;
    case QtWarningMsg:
      printf("[qDebug] Warning: %s\n", msg);
      break;
    case QtCriticalMsg:
      printf("[qDebug] Critical: %s\n", msg);
      break;
    case QtFatalMsg:
      printf("[qDebug] Fatal: %s\n", msg);
      abort();
    }
    qInstallMsgHandler(messageOutputAbort);
  }
#endif

  /*!
    Cross-platform sleep main thread for \a ms milliseconds
  */
  void CoreApplicationPrivate::sleep(int ms)
  {
#ifdef Q_OS_WIN
    ::Sleep(ms);
#else
    if(ms>10000)
      ::sleep(ms/1000);
    else
      ::usleep(ms*1000);
#endif
  }

  /*!
    In case an exception occurs during normal execution
  */
  void CoreApplicationPrivate::osSignal(int sig)
  {
    static QAtomicInt lock=false;
    if(lock.fetchAndStoreOrdered(true)) {
      sleep(10000);
      fprintf(stderr, "Exception occured while processing exceptions.\n"
                      "No backtrace information can be provided\n"
                      "Eventually kill remaining application instances:\n");
      QString appName=QCoreApplication::applicationName();
      fprintf(stderr, "  pgrep -a %s\n"
                      "  pkill %s\n",
              appName.toLocal8Bit().data(),
              appName.toLocal8Bit().data());
      ::exit(2);
    }
    _crashingThread=currentThreadName();
    switch (sig) {
    case SIGABRT:
      instance()->reportBugNow(Message::Fatal, "Abort Signal");
      break;
    case SIGINT:
      instance()->reportBugNow(Message::Fatal, "Interrupted Signal");
      break;
    case SIGTERM:
      instance()->reportBugNow(Message::Fatal, "Terminated Signal");
      break;
    case SIGSEGV:
      instance()->reportBugNow(Message::Fatal, "Segmentation fault Signal");
      break;
#ifndef Q_OS_WIN
    case SIGBUS:
      instance()->reportBugNow(Message::Fatal, "Bus error Signal");
      break;
#endif
    default:
      break;
    }
    printf("exit\n");
    ::exit(2);
  }

  /*!
    Delayed bug report to user (wait for end of program).
    Not fatal error message. Stack it and report only when quitting or on fatal error
  */
  void CoreApplicationPrivate::reportBug(Message::Severity sev, const QString & msg)
  {
    QString str=bugInfo(sev, msg);
    if(msg.startsWith("QNetworkReplyHttpImplPrivate")) return;
    printf("[bug report] %s: %s\n", Message::severityString(sev).toLatin1().data(), msg.toLatin1().data());
    // Bug submitted to trolltech internal to QWorkspace on closing application
    if(msg.startsWith("QCoreApplication::postEvent: Unexpected null receiver")) return;
    // Message output under Mac by graphical applications
    //if(msgBA.startsWith("QObject::moveToThread")) return;
    // Message output given when restoring workspaces after an upgrade
    if(msg.startsWith("QMainWindow::restoreState()")) return;
    // Message output given by Qt especially under Windows
    if(msg.startsWith("QObject::startTimer")) return;
    if(msg.startsWith("QFileSystemWatcher: failed to add paths:")) return;
    // Common message received under Linux for Qt 4.7
    if(msg.startsWith("X Error: BadWindow (invalid Window parameter) 3")) return;
    if(msg.startsWith("QSslSocket: cannot resolve")) return;
    if(msg.startsWith("QXcbConnection:")) return;
    if(msg.startsWith("QML debugging is enabled.")) return;
    if(msg.startsWith("X Error")) {
      /*static bool userNotWarned=true;
      if (userNotWarned) {
        userNotWarned=false;
        Message::warning(MSG_ID, translate("CoreApplicationPrivate", "X11 server error"),
                             tr("If you are running this application through a ssh connection, "
                                "you can avoid X11 errors and improve the graphical quality by a proper configuration of your X11 server. "
                                "See http://www.geopsy.org/faq.html for details."));
      }*/
      //return;
    }
    // Message received when paper size is not supported by print driver
    if(msg.startsWith("QPrinter::setPaperSize: Illegal paper size")) return;
    // Message received under X11, when closing many Geopsy.org applications in one action
    if(msg.startsWith("QClipboard::setData: Cannot set X11 selection owner for CLIPBOARD")) return;
    if(msg.startsWith("Numeric sorting unsupported on Windows versions older than Windows 7")) return;
    if(msg.startsWith("QNetworkReplyHttpImplPrivate::_q_startOperation was called more than once")) return;
    if(msg.startsWith("QMdiArea::closeAllSubWindows:null pointer")) return;
    if(msg.startsWith("failed to create compose table")) return;
    if(msg.startsWith("Application asked to unregister timer")) {
      reportBugNow(Message::Fatal, msg); // debug those warnings
    }
    if(_warningAsError) {
      reportBugNow(sev, msg);
    } else {
      _errors << str;
    }
  }

  void CoreApplicationPrivate::askForUserInterrupt()
  {
    if(!hasGui()) printf("\n"); // Avoid uggly output if interrupt occured while printing something.
    if(Message::question(MSG_ID, tr("User interrupt"),
                            tr("Is this process blocked? Answer 'Yes' to generate a bug report."),
                            Message::yes(), Message::no())==Message::Answer1) {
      ::exit(0);
    }
  }

  /*!
    Immediate bug report to user (fatal or quitting application)
  */
  void CoreApplicationPrivate::reportBugNow(Message::Severity sev, const QString &msg)
  {
    XMLStringStream sError(QIODevice::WriteOnly);
    XMLClass::writeChildren(sError, "Error", bugInfo(sev, msg));
    if(!_errors.isEmpty()) {
      for(int i=_errors.count()-1;i>=0;i--) {
        XMLClass::writeChildren(sError, "Error", _errors.at(i));
      }
      _errors.clear();
    }
    XMLStringStream sReport(QIODevice::WriteOnly);
    XMLClass::writeChildren(sReport, "CrashReport", sError.toString() + appInfo());
    QString str=sReport.toString();
    QProcess p;
    p.setProcessChannelMode(QProcess::ForwardedChannels);
    if(msg=="Terminated Signal" || msg=="Interrupted Signal") {
      if(!hasGui()) instance()->askForUserInterrupt();
      p.start("\""+QCoreApplication::applicationFilePath()+"\" -reportint");
    } else {
      p.start("\""+QCoreApplication::applicationFilePath()+"\" -reportbug");
    }
    p.write(str.toLatin1().data());
    p.write("END\n");
    if(p.waitForStarted(10000)) {
      while(!p.waitForFinished(10000)) {}
    } else {
      reportBugDirectly(str);
    }
  }

  /*!

  */
  void CoreApplicationPrivate::reportBugDirectly(QString bug)
  {
    QString bugFile=File::uniqueName("geopsy_bug_report.html", QDir::temp());
    QFile f(bugFile);
    if(f.open(QIODevice::WriteOnly)) {
      QTextStream s(&f);
      s << htmlBugReport(encodeToHtml(bug));
      App::log(tr("\nA bug report has just been generated (%1).\n"
                  "To help debugging this software, please open it in a web browser.\n"
                  "You will be redirected to http://www.geopsy.org/bugs/backtrace.php.\n"
                  "We sincerely apologize for the inconvenience.\n").arg(bugFile));
    } else {
      App::log(bug+tr("\nTo help debugging this software, please open a web browser\n"
                      "and submit this bug report to http://www.geopsy.org/bugs/backtrace.php.\n"
                      "We sincerely apologize for the inconvenience.\n"));
    }
  }

  /*!
    Analyse current stack and return it as a string
  */
  QString CoreApplicationPrivate::backTrace()
  {
    QString str;
#ifdef TRACE_ENABLED
    QStringList tList=Thread::threadNames();
    Thread::lockThreads();
    QList< QStack<const TraceStamp *> * > sList=Thread::stacks();
    for(int i=0; i<sList.count(); i++ ) {
      str += "---- thread ";
      str += tList.at(i);
      str += "\n";
      str += Trace::backTrace(sList.at(i));
    }
    Thread::unlockThreads();
#elif defined(Q_OS_UNIX) && !defined(Q_CC_SUN) && !defined(NO_BACKTRACE)
    void *frames[50];
    size_t size;
    char ** strings;
    size_t i;
    size=backtrace (frames, 50);
    strings=backtrace_symbols (frames, size);

    size_t functionLen=0;
    int status;
    // Demangle, see doc at http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/namespaceabi.html
    for(i=0; i < size; i++) {
      // get value between (...+
      char * mangledName=strchr(strings[i], '(' );
      if(mangledName) {
        char * mangledNameEnd=strchr(mangledName, '+' );
        if(mangledNameEnd) {
          char * mangledString=new char[mangledNameEnd-mangledName];
          strncpy(mangledString, mangledName+1, mangledNameEnd-mangledName-1);
          mangledString[mangledNameEnd-mangledName-1]='\0';
          char * functionName=abi::__cxa_demangle(mangledString, nullptr, &functionLen, &status);
          str+=functionName;
          delete [] mangledString;
        } else {
          str+=strings[i];
        }
      } else {
        str+=strings[i];
      }
      str+="\n";
    }
    /*str+="---- raw function names\n";
    for(i=0; i < size; i++) {
      str+=strings[i];
      str+="\n";
    }*/

    free (strings);
#endif
    return str;
  }

  /*!
    Analyse current stack for instrumented bugs and return it as a string
  */
  QString CoreApplicationPrivate::backTraceBug()
  {
    QString str;
    QStringList tList=Thread::threadNames();
    Thread::lockThreads();
    QList< QStack<TraceBug *> * > sList=Thread::bugStacks();
    for(int i=0; i<sList.count(); i++ ) {
      str += "---- thread ";
      str += tList.at(i);
      str += "\n";
      str += TraceBug::backTrace(sList.at(i));
    }
    QList< QVector<TraceLog *> * > lList=Thread::logVectors();
    for(int i=0; i<lList.count(); i++ ) {
      str += "---- thread ";
      str += tList.at(i);
      str += "\n";
      str += TraceLog::backTrace(lList.at(i));
    }
    Thread::unlockThreads();
    return str;
  }

  QString CoreApplicationPrivate::bugInfo(Message::Severity sev, const QString& msg)
  {
    XMLStringStream s(QIODevice::WriteOnly);
    XMLClass::writeProperty(s, "severity", Message::severityString(sev));
    XMLClass::writeProperty(s, "message", msg);
    XMLClass::writeProperty(s, "time", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"));
    XMLClass::writeProperty(s, "threads", Thread::threadNames().join(","));
    XMLClass::writeProperty(s, "currentThread", _crashingThread);
    XMLClass::writeProperty(s, "stack", backTrace());
    XMLClass::writeProperty(s, "bugStack", backTraceBug());
    return s.toString();
  }

  /*!
    Returns name of currently running thread
  */
  QString CoreApplicationPrivate::currentThreadName()
  {
    Thread * t=qobject_cast<Thread *>(QThread::currentThread());
    if(t) {
      return t->objectName();
    } else {
      return "main thread";
    }
  }

  QString CoreApplicationPrivate::appInfo()
  {
    XMLStringStream s(QIODevice::WriteOnly);
    XMLClass::writeProperty(s, "application", QCoreApplication::applicationName());
    XMLClass::writeProperty(s, "version", version());
    XMLClass::writeProperty(s, "system", QSysInfo::productType()+" "+QSysInfo::productVersion());
    return s.toString();
  }

  void CoreApplicationPrivate::showHelp(int& i, int argc, char ** argv, ApplicationHelp * ( *help) ())
  {
    ApplicationHelp * h=help();
    setHelp(h);
    if(checkOptionArg(i, argc, argv, false)) {
      h->exec(argv[i]);
    } else {
      h->exec();
    }
    delete h;
  }

  void CoreApplicationPrivate::setHelp(ApplicationHelp * h)
  {
    QList<QByteArray> sections=h->sections();
    std::sort(sections.begin(), sections.end());
    QString str=tr("Shows help. ARG may be a level (0,1,2...) or a section keyword. "
                   "Accepted keywords are: all, html, latex, generic, debug");
    for(QList<QByteArray>::iterator it=sections.begin();it!=sections.end();it++) {
      str+= ", " + *it;
    }
    h->addGroup(tr("Generic"), "generic", 4);
    h->addOption(tr("-h, -help [ARG]"), str);
    h->addOption(tr("-args [MAX]"), tr("List the argument history diplaying MAX entries (default=50). "
                                       "Üse '-args 0' to print all entries recorded so far."));
    h->addOption(tr("-rargs [INDEX]"), tr("Reuse argument list with INDEX. See '-args' to get the available argument lists."
                                          "Wihtout INDEX the last argument list is used."));
    h->addOption(tr("-version"), tr("Show version information"));
    h->addOption(tr("-app-version"), tr("Show short version information"));
    h->addOption(tr("-j, -jobs <N>"), tr("Allow a maximum of N simulteneous jobs for parallel "
                                         "computations (default=%1).").arg(QThread::idealThreadCount()));
    h->addOption(tr("-verbosity <V>"), tr("Set level of verbosity (default=0)"));
    h->addOption(tr("-locale <L>"), tr("Set current locale"));
    h->addOption(tr("-batch"), tr("Prevent the storage of user history (e.g. -args or recent file menus). "
                                  "Use it when running inside a script, to prevent pollution of user history."));
    h->addOption(tr("-qt-plugin-paths"), tr("Print the list of paths where Qt plugins are search."));
    h->addGroup(tr("Debug"), "debug", 5);
    h->addOption(tr("-nobugreport"),
                 tr("Does not generate bug reports in case of error."));
    h->addOption(tr("-reportbug"),
                 tr("Starts bug report dialog, information about bug is passed through stdin. This "
                    "option is used internally to report bugs if option -nobugreport is not "
                    "specified."));
    h->addOption(tr("-warning-critical"),
                 tr("Consider warnings as critical, stops execution and issue a bug report. Mainly "
                    "used for debug, to be able to start the debugger even after a warning."));
    h->addOption(tr("-reportint"),
                 tr("Starts bug report dialog, information about interruption is passed through stdin. This "
                    "option is used internally to report interruptions if option -nobugreport is not "
                    "specified."));
    h->addOption(tr("-sleep <S>"),
                 tr("Sleep for S seconds at the beginning to let, for instance, a debugger to connect."));
  }

  /*!
    Check if option argument at \a i is available. If not and if \a mandatory is true, the application exits
    with error code 2. Else, it returns true if an argument is available.

    This function can be used even without a CoreApplication running.
  */
  bool CoreApplicationPrivate::checkOptionArg(int& i, int argc, char ** argv, bool mandatory)
  {
    i++;
    if(i<argc) {
      bool ok;
      if(argv[i][0]=='-' ) {
        QByteArray option(argv[i]);
        option.toDouble(&ok);
        if(ok) { // Argument is a number
          return true;
        }
        if(option.contains(" ")) { // Argument contains spaces, certainly not an option
          return true;
        }
      } else return true;
    }
    if(mandatory) {
      // Do not use app because it is not sure whether it is already created.
      QTextStream(stdout) << tr("Option '%1' requires an argument, see -help for details.\n").arg(argv[i-1]);
      ::exit(2);
    }
    i--; // Not a mandatory argument
    return false;
  }

  /*!
    After parsing all options, warns the user if there some unrecognized arguments.
    Returns true if there is no more arguement left.

    Use this function when options are partially parsed.
  */
  bool CoreApplicationPrivate::checkRemainingArgs(int argc, char ** argv)
  {
    QTextStream s(stderr);
    for(int i=1; i<argc; i++) {
      QByteArray arg=argv[i];
      if(arg[0]=='-') {
        App::log(tr("bad option '%1', see -help for details.\n").arg(argv[i]));
        return false;
      }
    }
    return true;
  }

  /*!
    Read stdin until a line ending by 'END'.
    Mostly used by '-reportbug' option to read backtrace infromation.
  */
  QString CoreApplicationPrivate::getStdin()
  {
    TRACE;
    QString str;
    while(true) {
      QString l=File::readLine(false);
      if(l=="END\n") break;
      str+=l;
    }
    return str;
  }

  /*!
    Convert all remaining arguments into file names (accepting wildchars)
  */
  QStringList CoreApplicationPrivate::getFileList(int argc, char ** argv)
  {
    QStringList files;
    for(int index=1; index<argc; index++) {
      files.append(argv[index]);
    }
    // Replace file names that include "*" with all their corresponding entries
    QDir d;
    QStringList result;
    QString file;
    foreach(file, files) {
      QFileInfo fi (file);
      QDir dloc(d);
      dloc.cd(fi.dir().path());
      QStringList filters;
      filters << fi.fileName();
      QStringList list=dloc.entryList(filters);
      if(list.isEmpty()) {
        App::log(tr("File '%1' not found\n").arg(file));
      }
      foreach(file, list) {
        result << dloc.absoluteFilePath(file);
      }
    }
    return result;
  }

  void CoreApplicationPrivate::addGlobalObject(GlobalObject * ptr)
  {
    instance()->_globalPointers.append(ptr);
  }

  void CoreApplicationPrivate::removeGlobalObject(GlobalObject * ptr)
  {
    int i=instance()->_globalPointers.indexOf(ptr);
    if(i>-1) instance()->_globalPointers.removeAt(i);
  }

  void CoreApplicationPrivate::removeGlobalObjects(const char * className)
  {
    QMutableListIterator<GlobalObject *> it(instance()->_globalPointers);
    GlobalObject * ptr;
    while(it.hasNext()) {
      ptr=it.next();
      if(ptr->inherits(className)) {
        delete ptr;
        it.remove();
      }
    }
  }

  /*!
    Return an integer that "uniquely" identifies the user. In fact this is just random number stored in QSettings.
    It is initialized only if not found in QSettings. This is mainly used to identify different users behind
    a proxy server. Each user is then identified by its IP address or http proxy address (if any) and this integer number.
  */
  int CoreApplicationPrivate::userId()
  {
    QSettings& reg=CoreApplication::instance()->globalSettings();
    if(reg.contains("id")) {
      return reg.value("id").toInt();
    } else {
      int id=rand();
      reg.setValue("id",id);
      return id;
    }
  }

  /*!
    Return a translated path. If not successuful (user cancel), the string is empty.
  */
  QString CoreApplicationPrivate::translatePath(const QString& originalFile, const PathTranslatorOptions& options)
  {
    if(!_pathTranslator) {
      _pathTranslator=new PathTranslator;
    }
    return _pathTranslator->translate(originalFile, options);
  }

  int CoreApplicationPrivate::terminalRows() const
  {
    if(_terminalRows==0) {
      terminalSize();
    }
    return _terminalRows;
  }

  int CoreApplicationPrivate::terminalCols() const
  {
    if(_terminalCols==0) {
      terminalSize();
    }
    return _terminalCols;
  }

  /*!
    Get the terminal size

    Originaly copied from ps command written by Albert Cahalan (Copyright 1998-2002),
    file global.c, package procps-3.2.7

    Comments of the original function (set_screen_size(void)):

    The rules:
      1. Defaults are implementation-specific. (ioctl,termcap,guess)
      2. COLUMNS and LINES override the defaults. (standards compliance)
      3. Command line options override everything else.
      4. Actual output may be more if the above is too narrow.

    SysV tends to spew semi-wide output in all cases. The args
    will be limited to 64 or 80 characters, without regard to
    screen size. So lines of 120 to 160 chars are normal.
    Tough luck if you want more or less than that! HP-UX has a
    new "-x" option for 1024-char args in place of comm that
    we'll implement at some point.

    BSD tends to make a good effort, then fall back to 80 cols.
    Use "ww" to get std::numeric_limits<double>::infinity(). This is nicer for "ps | less"
    and "watch ps". It can run faster too.
  */
  void CoreApplicationPrivate::terminalSize() const
  {
  /*
   * Try not to overflow the output buffer:
   *    32 pages for env+cmd
   *    64 kB pages on IA-64
   *    4 chars for "\377", or 1 when mangling to '?'  (ESC_STRETCH)
   *    plus some slack for other stuff
   * That is about 8.5 MB on IA-64, or 0.6 MB on i386
   *
   * Sadly, current kernels only supply one page of env/command data.
   * The buffer is now protected with a guard page, and via other means
   * to avoid hitting the guard page.
   */

  /* output buffer size */
  // since we mangle to '?' this is 1 (would be 4 for octal escapes)
#define ESC_STRETCH 1
#define OUTBUF_SIZE (2 * 64*1024 * ESC_STRETCH)
#ifdef Q_OS_WIN
    _terminalCols=80;
    _terminalRows=1000;
#else
    struct winsize ws;
    char *columns; /* Unix98 environment variable */
    char *lines;   /* Unix98 environment variable */

    do {
      int fd;
      if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)!=-1 && ws.ws_col>0 && ws.ws_row>0) break;
      if(ioctl(STDERR_FILENO, TIOCGWINSZ, &ws)!=-1 && ws.ws_col>0 && ws.ws_row>0) break;
      if(ioctl(STDIN_FILENO,  TIOCGWINSZ, &ws)!=-1 && ws.ws_col>0 && ws.ws_row>0) break;
      fd=open("/dev/tty", O_NOCTTY|O_NONBLOCK|O_RDONLY);
      if(fd!=-1){
        int ret=ioctl(fd, TIOCGWINSZ, &ws);
        close(fd);
        if(ret!=-1 && ws.ws_col>0 && ws.ws_row>0) break;
      }
      // TODO: ought to do tgetnum("co") and tgetnum("li") here
      ws.ws_col=80;
      ws.ws_row=24;
    } while(0);
    _terminalCols=ws.ws_col;  // hmmm, NetBSD subtracts 1
    _terminalRows=ws.ws_row;
    //fprintf(stderr,"Terminal size %i*%i\n",_terminalCols,_terminalRows);

    // TODO: delete this line
    //if(!isatty(STDOUT_FILENO)) _terminalCols=OUTBUF_SIZE;

    columns=getenv("COLUMNS");
    if(columns && *columns){
      long t;
      char *endptr;
      t=strtol(columns, &endptr, 0);
      if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) _terminalCols=(int)t;
    }

    lines  =getenv("LINES");
    if(lines && *lines){
      long t;
      char *endptr;
      t=strtol(lines, &endptr, 0);
      if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) _terminalRows=(int)t;
    }

    if((_terminalCols<9) || (_terminalRows<2))
      fprintf(stderr,"Your %dx%d screen size is bogus. Expect trouble.\n",
        _terminalCols, _terminalRows
      );
#endif
  }

  /*!
    Stores all installed and running plugins of the application
  */
  void CoreApplicationPrivate::addPlugin(QObject * p)
  {
    TRACE;
    if(!_plugins.contains(p)) {
      _plugins.append(p);
    }
  }

  /*!
    Delete \a p only if it is not an installed and running plugins
  */
  void CoreApplicationPrivate::deletePlugin(QObject * p)
  {
    TRACE;
    if(!_plugins.contains(p)) {
      delete p;
    }
  }

  QString CoreApplicationPrivate::htmlBugReport(const QString& platform, const QString& email, const QString &userInfo)
  {
    QString h;
    h="<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
      "<html>\n"
      "<head>\n"
      "  <title>Report a bug to Geopsy.org</title>\n"
      "</head>\n"
      "<body>\n"
      "<form action=\"http://www.geopsy.org/bugs/backtrace.php\" method=\"post\">\n"
      "  <b>Your email*:</b> <input name=\"email\" type=\"text\" value=\"%2\" size=\"45\"/>\n"
      "  <input type=\"submit\" value=\"Submit\"/><br/>\n"
      "  <b>Environment and crash information:</b><br/>\n"
      "  <textarea name=\"systemInfo\" rows=\"20\" cols=\"80\" readonly>\n"
      "%3\n"
      "</textarea><br/>\n"
      "  <b>Additional information:</b><br/>\n"
      "  Can you reproduce the application crash? How did it happen?...<br/>\n"
      "<textarea name=\"userInfo\" rows=\"20\" cols=\"80\">%4</textarea><br/>\n"
      "</form>\n"
      "</body></html>";
    return h.arg(email).arg(platform).arg(userInfo);
  }

  void CoreApplicationPrivate::printLibraryPaths() const
  {
    QStringList list=QCoreApplication::libraryPaths();
    QTextStream s(stdout);
    QString p;
    foreach(p, list) {
      s << p << "\n";
    }
    s << Qt::flush;
  }

  QList<QStringList> CoreApplicationPrivate::argumentLists()
  {
    QList<QStringList> list;
    QSettings& reg=CoreApplication::instance()->settings();
    QList<QVariant> tmplist=reg.value("applicationArguments").toList();
#if(QT_VERSION >= QT_VERSION_CHECK(4, 7, 0))
    list.reserve(tmplist.count());
#endif
    for(QList<QVariant>::const_iterator it=tmplist.begin(); it!=tmplist.end(); it++) {
      list.append(it->toStringList());
    }
    return list;
  }

  void CoreApplicationPrivate::printArgumentLists(int max) const
  {
    QList<QStringList> list=argumentLists();
    QTextStream s(stdout);
    s << tr("# History of command line arguments:\n");
    if(max>list.count() || max==0) {
      max=list.count();
    }
    for(int i=max-1; i>=0; i--) {
      const QStringList& args=list.at(i);
      QStringList::const_iterator ita=args.begin();
      QString arg=*ita;
      arg.replace(" ", "\\ ");
      s << QString("%1: ").arg(i) << arg;
      for(ita++; ita!=args.end(); ita++) {
        arg=*ita;
        arg.replace(" ", "\\ ");
        s << " " << arg;
      }
      s << "\n";
    }
    s << Qt::flush;
  }

  void CoreApplicationPrivate::setArgumentList(int argc, char ** argv)
  {
    _argumentList.reserve(argc);
    for(int i=1; i<argc; i++) {
      QByteArray arg=argv[i];
      if(arg=="-non-interactive" || // Avoid storing arguments when running in non-interactive scripts
         arg=="-args" ||            // Do not add to history
         arg=="-h" || arg=="--help" || arg=="-help" ||
         arg=="-rargs") {
        _argumentList.clear();
        return;
      }
      _argumentList.append(arg);
    }
  }

  void CoreApplicationPrivate::updateArgumentLists()
  {
    // Make sure that settings has not been used so far
    // If so, name and organisation are probably not preperly set
    ASSERT(!_settings);
    if(_argumentList.isEmpty()) {
      return;
    }
    QList<QStringList> list=argumentLists();
    int index=list.indexOf(_argumentList);
    if(index>-1) {
      list.removeAt(index);
    }
    list.prepend(_argumentList);
    while(list.count()>1000) {
      list.removeLast();
    }
    QList<QVariant> tmplist;
    tmplist.reserve(list.count());
    for(QList<QStringList>::const_iterator it=list.begin(); it!=list.end(); it++) {
      if(it->count()>=2) { // Clean history generated by some old releases
        tmplist.append(*it);
      }
    }
    QSettings& reg=CoreApplication::instance()->settings();
    reg.setValue("applicationArguments", tmplist);
  }

  /*!
    Starts \a app application with the argument list at \a index.
  */
  int CoreApplicationPrivate::startArgumentList(int index)
  {
    QTextStream sOut(stderr);
    if(index<0) {
      sOut << tr("Bad argument list index (%1").arg(index) << Qt::endl;
      return 2;
    }
    QList<QStringList> list=argumentLists();
    if(index>=list.count()) {
      sOut << tr("Bad argument list index (%1>=%2)").arg(index).arg(list.count()) << Qt::endl;
      return 2;
    }
    const QStringList& args=list.at(index);
    if(args.contains("-rargs")) {
      sOut << tr("To prevent inifinte looping, '-rargs' option is not allowed in argument list.") << Qt::endl;
      return 2;
    }
    QString appPath=QCoreApplication::instance()->applicationFilePath();
    sOut << tr("Starting detached '%1 %2'...").arg(appPath).arg(args.join(" ")) << Qt::endl;
    return QProcess::startDetached(appPath, args) ? 0 : 2;
  }

  /*!
    Returns the size of physical memory (RAM) in bytes.

    Author:  David Robert Nadeau
    Site:    http://NadeauSoftware.com/
    License: Creative Commons Attribution 3.0 Unported License
             http://creativecommons.org/licenses/by/3.0/deed.en_US
  */
  size_t CoreApplicationPrivate::getMemorySize()
  {
#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__))
    /* Cygwin under Windows. ------------------------------------ */
    /* New 64-bit MEMORYSTATUSEX isn't available.  Use old 32.bit */
    MEMORYSTATUS status;
    status.dwLength = sizeof(status);
    GlobalMemoryStatus( &status );
    return (size_t)status.dwTotalPhys;

#elif defined(_WIN32)
    /* Windows. ------------------------------------------------- */
    /* Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS */
    MEMORYSTATUSEX status;
    status.dwLength = sizeof(status);
    GlobalMemoryStatusEx( &status );
    return (size_t)status.ullTotalPhys;

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
    /* UNIX variants. ------------------------------------------- */
    /* Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM */

#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
    int mib[2];
    mib[0] = CTL_HW;
#if defined(HW_MEMSIZE)
    mib[1] = HW_MEMSIZE;            /* OSX. --------------------- */
#elif defined(HW_PHYSMEM64)
    mib[1] = HW_PHYSMEM64;          /* NetBSD, OpenBSD. --------- */
#endif
    int64_t size = 0;               /* 64-bit */
    size_t len = sizeof( size );
    if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
      return (size_t)size;
    return 0L;			/* Failed? */

#elif defined(_SC_AIX_REALMEM)
    /* AIX. ----------------------------------------------------- */
    return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L;

#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
    /* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */
    return (size_t)sysconf( _SC_PHYS_PAGES ) *
      (size_t)sysconf( _SC_PAGESIZE );

#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE)
    /* Legacy. -------------------------------------------------- */
    return (size_t)sysconf( _SC_PHYS_PAGES ) *
      (size_t)sysconf( _SC_PAGE_SIZE );

#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
    /* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */
    int mib[2];
    mib[0] = CTL_HW;
#if defined(HW_REALMEM)
    mib[1] = HW_REALMEM;		/* FreeBSD. ----------------- */
#elif defined(HW_PYSMEM)
    mib[1] = HW_PHYSMEM;		/* Others. ------------------ */
#endif
    unsigned int size = 0;		/* 32-bit */
    size_t len = sizeof( size );
    if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
      return (size_t)size;
    return 0L;			/* Failed? */
#endif /* sysctl and sysconf variants */

#else
    return 0L;			/* Unknown OS. */
#endif
  }

  /*!
    For Qt 5, this is particularly important for the platform plugin.
    Avoid also conflicts with other Qt directories.
  */
  void CoreApplicationPrivate::setLibraryPath()
  {
#ifdef Q_OS_MAC
    QString p("/Library/Geopsy.org/%1/plugins");
    QStringList paths;
    paths << p.arg(QGPCORETOOLS_VERSION_TYPE);
    QCoreApplication::setLibraryPaths(paths);
#endif // Q_OS_MAC
  }

  /*!
    Returned setting is always pointing to the root.

    endGroup() are not necessary at the end of usage.
  */
  QSettings& CoreApplicationPrivate::settings()
  {
    if(!_settings) {
      _settings=new QSettings;
    } else {
      while(!_settings->group().isEmpty()) {
        _settings->endGroup();
      }
    }
    return *_settings;
  }

  /*!
    Returned setting is always pointing to the root.

    endGroup() are not necessary at the end of usage.
  */
  QSettings& CoreApplicationPrivate::globalSettings()
  {
    if(!_globalSettings) {
#ifdef Q_OS_MAC
      _globalSettings=new QSettings(qApp->organizationDomain());
#else
      _globalSettings=new QSettings(qApp->organizationName());
#endif
    } else {
      while(!_globalSettings->group().isEmpty()) {
        _globalSettings->endGroup();
      }
    }
    return *_globalSettings;
  }

  void CoreApplicationPrivate::signature(QTextStream& s)
  {
    s << "#\n"
         "# BEGIN COMMAND LINE ARGUMENTS\n"
         "#\n";
    s << CoreApplication::applicationFilePath() << " ";
    s << argumentList().join(' ') << "\n";
    s << "#\n"
         "# END COMMAND LINE ARGUMENTS\n"
         "#\n"
         "#\n"
         "# BEGIN SOFTWARE VERSION\n"
         "#\n";
    s << CoreApplication::instance()->version();
    s << "#\n"
         "# END SOFTWARE VERSION\n"
         "#\n";
  }

} // namespace QGpCoreTools
