/***************************************************************************
**
**  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: 2005-10-04
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "Message.h"
#include "File.h"
#include "Trace.h"
#include "CoreApplication.h"

namespace QGpCoreTools {

  /*!
    \class Message Message.h
    \brief Show messages to user
    If not graphical user interface is running this class send messages to and get information from the user through
    the console. In graphical user interface, a GuiMessage class is also available (in library qtbguitools), which
    transparently (for the developer) send the message to dialog boxes. Those two classes have eaxctly the same API.
    So always use this class for send messages and automatically the messages will be displayed in the appropriate way.

    The console is considered is its largest sense here. In fact this is a log stream which can be redirected anywhere:
    true unix terminal, inside a general log view, to a specific log (attached to a thread), or simply to trash.
  */

  /*!
    Init base context for answers to messages
  */
  Message::Message()
  {
    TRACE;
    beginContext(false);
  }

  /*!
    Define a new context for automatic answers, stack this context

    \sa endContext(), MessageContext
  */
  void Message::beginContext(bool quiet)
  {
    TRACE;
    QMutexLocker ml(&_contextMutex);
    _contexts.push(Context(quiet));
  }

  /*!
    Delete current context for automatic answers, and restore old one

    \sa beginContext(), MessageContext
  */
  void Message::endContext()
  {
    TRACE;
    QMutexLocker ml(&_contextMutex);
    _contexts.pop();
  }

  void Message::setAutoAnswer(uint msgId, Answer a)
  {
    TRACE;
    QMutexLocker ml(&_contextMutex);
    _contexts.top().answers.insert(msgId, a);
  }

  /*!
    Print auto answer to log console and return the automatic answer for message with id \a msgId
  */
  Message::Answer Message::autoAnswer(uint msgId,
                                      Severity sev,
                                      QString caption,
                                      const QString &text,
                                      QString answer0,
                                      QString answer1,
                                      QString answer2)
  {
    App::log(severityString(sev)+QTime::currentTime().toString("hh:mm:ss")+" -- "+caption+"----\n");
    App::log(text+"\n ==> ");
    _contextMutex.MUTEX_LOCK;
    Answer a=_contexts.top().answers[msgId];
    _contextMutex.unlock();
    switch (a) {
    case Answer0:
      App::log(answer0+"\n");
      break;
    case Answer1:
      App::log(answer1+"\n");
      break;
    case Answer2:
      App::log(answer2+"\n");
      break;
    }
    return a;
  }

  /*!
    Generate a unique message id from position in source file.

    Use C macros __FILE__ and __LINE__ as argument of this function.
  */
  uint Message::messageId(const char * fileName, int lineNumber)
  {
    TRACE;
    return qHash(QByteArray(fileName))-lineNumber;
  }

  /*!
    Translate severity \a sev into a localized string.
  */
  QString Message::severityString(Severity sev)
  {
    TRACE;
    switch (sev) {
    case Information:
      return tr("-----INFO----- ");
    case Warning:
      return tr("----WARNING--- ");
    case Critical:
      return tr("----ERROR----- ");
    case Fatal:
      return tr("--FATAL ERROR- ");
    case Question:
      return tr("---QUESTION--- ");
    default:
      return tr("-------------- ");
    }
  }

  /*!
    General message function with interactions with console
  */
  Message::Answer Message::message(uint msgId,
                                   Severity sev,
                                   QString caption,
                                   const QString &text,
                                   QString answer0,
                                   QString answer1,
                                   QString answer2,
                                   bool againOption)
  {
    if(againOption && hasAutoAnswer(msgId)) {
      return autoAnswer(msgId, sev, caption, text, answer0, answer1, answer2);
    }

    // For instancem, during restore of make-up files, the output is redirected
    // to a QString, dialog message is expected to be printed in the terminal instead
    // of the log string.
    QTextStream s(stdout);
    s << severityString(sev) << caption << "----\n" << text << Qt::endl;
    if(answer1.isEmpty() && answer2.isEmpty()) {
      return Answer0;
    }
    s << "  1. " << answer0 << tr( " <-- default" ) << Qt::endl;
    s << "  2. " << answer1 << Qt::endl;
    if(!answer2.isEmpty()) {
      s << "  3. " << answer2 << Qt::endl;
    }
    s << tr("? ") << Qt::flush;
    QString rep=File::readLine(true);
    rep=rep.toLower();
    Answer a;
    if(rep=="1") {
      a=Answer0;
    } else if(rep=="2") {
      a=Answer1;
    } else if(rep=="3") {
      a=Answer2;
    } else if(answer0.toLower().startsWith(rep)) {
      a=Answer0;
    } else if(answer1.toLower().startsWith(rep)) {
      a=Answer1;
    } else if(answer2.toLower().startsWith(rep)) {
      a=Answer2;
    } else {
      a=Answer0;
    }

    if(againOption) {
      s << tr("Show this message again? [y]/n ") << Qt::flush;
      QString rep=File::readLine(true);
      if(rep.toLower().startsWith("n")) {
        setAutoAnswer(msgId, a);
      }
    }
    return a;
  }

  /*!
    Show an information message.
    \a msgId has to be the macro MSG_ID.
  */
  Message::Answer Message::information(uint msgId, const QString &caption,
                                       const QString &text,
                                       QString answer0,
                                       QString answer1,
                                       QString answer2,
                                       bool againOption)
  {
    return CoreApplication::instance()->messageHandler()
        ->message(msgId, Information, caption, text,
                  answer0, answer1, answer2, againOption);
  }

  /*!
    Show a warning message.
    \a msgId has to be the macro MSG_ID.
  */
  Message::Answer Message::warning(uint msgId, const QString &caption,
                                   const QString &text,
                                   QString answer0,
                                   QString answer1,
                                   QString answer2,
                                   bool againOption)
  {
    return CoreApplication::instance()->messageHandler()
        ->message(msgId, Warning, caption, text,
                  answer0, answer1, answer2, againOption);
  }

  /*!
    Show a critical message.
    \a msgId has to be the macro MSG_ID.
  */
  Message::Answer Message::critical(uint msgId, const QString &caption,
                                    const QString &text,
                                    QString answer0,
                                    QString answer1,
                                    QString answer2,
                                    bool againOption)
  {
    return CoreApplication::instance()->messageHandler()
        ->message(msgId, Critical, caption, text,
                  answer0, answer1, answer2, againOption);
  }

  /*!
    Ask a question.
    \a msgId has to be the macro MSG_ID.
  */
  Message::Answer Message::question(uint msgId, const QString &caption,
                                    const QString &text,
                                    QString answer0,
                                    QString answer1,
                                    QString answer2,
                                    bool againOption)
  {
    return CoreApplication::instance()->messageHandler()
        ->message(msgId, Question, caption, text,
                  answer0, answer1, answer2, againOption);
  }

  /*!
    get an existing file name
  */
  QString Message::getOpenFileName(const QString &caption,
                                   const QString &filter,
                                   const QString &directory,
                                   const QString &bottomMessage)
  {
    TRACE;
    return CoreApplication::instance()->messageHandler()
        ->getOpenFileNameInternal(caption, filter, directory, bottomMessage);
  }

  /*!
    get a new file name
  */
  QString Message::getSaveFileName(const QString &caption,
                                   const QString &filter,
                                   const QString &selection,
                                   const QString &bottomMessage)
  {
    TRACE;
    return CoreApplication::instance()->messageHandler()
        ->getSaveFileNameInternal(caption, filter, selection, bottomMessage);
  }

  /*!
    get existing file names
  */
  QStringList Message::getOpenFileNames(const QString &caption,
                                        const QString &filter,
                                        const QString &directory,
                                        const QString &bottomMessage)
  {
    TRACE;
    return CoreApplication::instance()->messageHandler()
        ->getOpenFileNamesInternal(caption, filter, directory, bottomMessage);
  }

  /*!
    get an existing directory
  */
  QString Message::getExistingDirectory(const QString &caption,
                                        const QString &dir,
                                        const QString &bottomMessage)
  {
    TRACE;
    return CoreApplication::instance()->messageHandler()
        ->getExistingDirectoryInternal(caption, dir, bottomMessage);
  }

  QString Message::directoryPath(const QString &directory)
  {
    TRACE;
    QDir d(directory);
    return d.absolutePath();
  }

  /*!
    get an existing file name
  */
  QString Message::getOpenFileNameInternal(const QString &caption,
                                           const QString &filter,
                                           const QString &directory,
                                           const QString &bottomMessage)
  {
    TRACE;
    QTextStream s(stdout);
    s << "---- " << caption << " ----" << Qt::endl;
    s << tr("  Filter: ") << filter << Qt::endl;
    s << tr("  Current directory: ") << directoryPath(directory) << Qt::endl;
    if(!bottomMessage.isNull()) {
      QString msg=bottomMessage;
      msg.replace("\n", "  \n");
      s << "  " << msg << Qt::endl;
    }
    s << tr("File to open: ") << Qt::flush;
    QString fn=File::readLine(true);
    if(!fn.isEmpty()) {
      QFileInfo fi(fn);
      return fi.absoluteFilePath();
    } else {
      return QString();
    }
  }

  /*!
    get a new file name
  */
  QString Message::getSaveFileNameInternal(const QString &caption,
                                           const QString &filter,
                                           const QString &selection,
                                           const QString &bottomMessage)
  {
    TRACE;
    QTextStream s(stdout);
    s << "---- " << caption << " ----" << Qt::endl;
    s << tr("  Filter: ") << filter << Qt::endl;
    s << tr("  Selection: ") << selection << Qt::endl;
    if(!bottomMessage.isNull()) {
      QString msg=bottomMessage;
      msg.replace("\n", "  \n");
      s << "  " << msg << Qt::endl;
    }
    s << tr("File to save: ") << Qt::flush;
    QString fn=File::readLine(true);
    if(!fn.isEmpty()) {
      QFileInfo fi(fn);
      return fi.absoluteFilePath();
    } else {
      return QString();
    }
  }

  /*!
    get existing file names
  */
  QStringList Message::getOpenFileNamesInternal(const QString &caption,
                                                const QString &filter,
                                                const QString &directory,
                                                const QString &bottomMessage)
  {
    TRACE;
    QTextStream s(stdout);
    s << "---- " << caption << " ----" << Qt::endl;
    s << tr("  Filter: ") << filter << Qt::endl;
    s << tr("  Current directory: ") << directoryPath(directory) << Qt::endl;
    if(!bottomMessage.isNull()) {
      QString msg=bottomMessage;
      msg.replace("\n", "  \n");
      s << "  " << msg << Qt::endl;
    }
    QStringList fileNames;
    while(true) {
      s << tr("File %1 to open: ").arg(fileNames.count()+1) << Qt::flush;
      QString fn=File::readLine(true);
      if(fn.isEmpty()) break;
      QFileInfo fi(fn);
      fileNames.append(fi.absoluteFilePath());
    }
    return fileNames;
  }

  /*!
    get an existing directory
  */
  QString Message::getExistingDirectoryInternal(const QString &caption,
                                                const QString &directory,
                                                const QString &bottomMessage)
  {
    TRACE;
    QTextStream s(stdout);
    s << "---- " << caption << " ----" << Qt::endl;
    s << tr("  Dir: ") << directoryPath(directory) << Qt::endl;
    if(!bottomMessage.isNull()) {
      QString msg=bottomMessage;
      msg.replace("\n", "  \n");
      s << "  " << msg << Qt::endl;
    }
    s << tr("Directory to choose: ") << Qt::flush;
    QString fn=File::readLine(true);
    if(!fn.isEmpty()) {
      QFileInfo fi(fn);
      return fi.absoluteFilePath();
    } else {
      return QString();
    }
  }

  /*!
    Call this function to print a user friendly message in case of error while parsing a formated text
    from a QTextStream. If graphical user interface is available, it will be printed in a dialog box, if not to the terminal
  */
  void Message::wrongTextFormat(QTextStream&sErr, const QString&caption)
  {
    TRACE;
    QFile * fErr=static_cast<QFile *>(sErr.device());
    QString str;
    if(fErr) {
      str=tr("Error parsing text file %1\n").arg(fErr->fileName());
    } else {
      str=tr("Error parsing text\n");
    }
    qint64 errorLocation=sErr.pos ();
    qint64 errorShowStart;
    if(errorLocation<200) errorShowStart=0; else errorShowStart=errorLocation-200;
    if(sErr.seek(errorShowStart)) {
      str+=tr("Offset from beginning of file: %1 bytes.\n").arg(errorShowStart);
      QString strLine=sErr.readLine();
      qint64 lastLinePos=errorShowStart;
      qint64 currentPos=sErr.pos();
      while(currentPos<errorLocation) {
        str+=tr("    " )+strLine+"\n";
        strLine=sErr.readLine();
        lastLinePos=currentPos;
        currentPos=sErr.pos();
      }
      int errorCharPos=errorLocation-lastLinePos;
      str+=tr("==> %1\n").arg(strLine.left(errorCharPos));
      QString blankStr;
      blankStr.fill(' ',errorCharPos+4);
      if(strLine.length()>=errorCharPos)
        str+=blankStr+strLine.right(strLine.length()-errorCharPos)+"\n";
      str+=blankStr+"^\n";
      while(!sErr.atEnd() && currentPos<errorLocation+200) {
        str+=tr("    " )+sErr.readLine()+"\n";
        currentPos=sErr.pos();
      }
      sErr.seek(errorLocation);
    } else {
      str+=tr("Impossible to print the context of the parsing error\n");
    }
    critical(MSG_ID, caption, str, cancel());
  }

  /*!
    Call this function to print a user friendly message in case of error while reading a text file.
    If graphical user interface is available, it will be printed in a dialog box, if not to the terminal
  */
  void Message::wrongTextFormat(FILE * fErr, const QString&caption, const QString&fileName)
  {
    TRACE;
    QTextStream sOut(stdout);
    QString str;
    if(fileName.isEmpty()) {
      str=tr("Error parsing text file\n");
    } else {
      str=tr("Error parsing text file %1\n").arg(fileName);
    }
    uint errorLocation=ftell(fErr);
    uint errorShowStart;
    if(errorLocation<200) errorShowStart=0; else errorShowStart=errorLocation-200;
    // TODO check if possible to know if it can seek correctly
    fseek(fErr,errorShowStart,SEEK_SET);
    int strLineLen=256;
    char * strLine=new char[strLineLen];
    File::readLine(strLine, strLineLen, fErr);
    uint lastLinePos=errorShowStart;
    uint currentPos=ftell(fErr);
    while(currentPos<errorLocation) {
      str+=tr("    " )+strLine+"\n";
      File::readLine(strLine, strLineLen, fErr);
      lastLinePos=currentPos;
      currentPos=ftell(fErr);
    }
    uint errorCharPos=errorLocation-lastLinePos;
    char c=strLine[errorCharPos];
    strLine[errorCharPos]='\0';
    str+=tr("==> %1\n").arg(strLine);
    strLine[errorCharPos]=c;
    QString blankStr;
    blankStr.fill(' ',errorCharPos+4);
    if(strlen(strLine)>=errorCharPos)
      str+=blankStr+(strLine+errorCharPos)+"\n";
    str+=blankStr+"^\n";
    while(!feof(fErr) && currentPos<errorLocation+200) {
      File::readLine(strLine, strLineLen, fErr);
      str+=tr("    " )+strLine+"\n";
      currentPos=ftell(fErr);
    }
    fseek(fErr,errorLocation,SEEK_SET);
    critical(MSG_ID, caption, str, cancel());
  }

} // namespace QGpCoreTools
