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

#include "ApplicationHelp.h"
#include "Trace.h"
#include "StandardStream.h"
#include "CoreApplication.h"

namespace QGpCoreTools {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
ApplicationHelp::ApplicationHelp()
{
  TRACE;
}

/*!
  Description of destructor still missing
*/
ApplicationHelp::~ApplicationHelp()
{
  TRACE;
}

void ApplicationHelp::addGroup(QString title, QByteArray section, int level)
{
  TRACE;
  OptionGroup g;
  g.title=title;
  g.section=section;
  if(level==1 && _options.isEmpty()) {
    g.level=0;
  } else {
    g.level=level;
  }
  _options.append(g);
}

void ApplicationHelp::addOption(QString option, QString comments)
{
  TRACE;
  OptionGroup& g=_options.last();
  Option o;
  o.option=option;
  o.comments=comments;
  g.options.append(o);
}

void ApplicationHelp::addExample(QString command, QString comments)
{
  TRACE;
  Example o;
  o.command=command;
  o.comments=comments;
  _examples.append(o);
}

void ApplicationHelp::print(const OptionGroup& g)
{
  TRACE;
  if(g.options.isEmpty()) {
    print("\n"+g.title+" [level="+QString::number(g.level)+"]", "", 0);
  } else {
    if(g.title.isEmpty()) {
      print("\nOptions:\n", nullptr);
    } else {
      print("\n"+g.title+" options: [level="+QString::number(g.level)+"]\n", nullptr);
    }
    QString indentStr;
    indentStr.fill(' ',28);
    for(QList<Option>::const_iterator it=g.options.begin(); it!=g.options.end(); it++ ) {
      const Option& o=*it;
      QString cmt=o.comments;
      QString cmtLine=getLine(cmt, CoreApplication::instance()->terminalCols()-28);
      if(o.option.length()<25) {
        App::log("  "+o.option.leftJustified(25)+" "+cmtLine+"\n");
      } else {
        App::log("  "+o.option+"\n"+indentStr+cmtLine+"\n");
      }
      print(cmt, QString(), 28);
    }
  }
}

/*!
  Returns the list of sections
*/
QList<QByteArray> ApplicationHelp::sections()
{
  QList<QByteArray> l;
  for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
    l << it->section;
  }
  if( !_examples.isEmpty()) {
    l << "examples";
  }
  return l;
}

void ApplicationHelp::exec(const char * group)
{
  TRACE;
  // make sure stdout is used for output
  App::setStream(new StandardStream(stdout));

  if(group) {
    if(strcmp(group,"html")==0) {
      execHtml();
      return;
    }
    if(strcmp(group,"latex")==0) {
      execLatex();
      return;
    }
  }

  print(tr("Usage: %1 %2\n\n").arg(QCoreApplication::applicationName()).arg(_optionSummary), QString(), 0);
  print(_comments, QString(), 2);
  if(group) {
    QByteArray helpOption(group);
    bool isHelpLevel=false;
    int helpLevel=helpOption.toInt(&isHelpLevel);
    if(helpOption=="all") {
      for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
        print(*it);
      }
    } else if(helpOption.startsWith("no-")) {
      helpOption=helpOption.mid(3);
      for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
        if(it->section!=helpOption) {
          print(*it);
        }
      }
    } else if(isHelpLevel) {
      for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
        if(it->level<=helpLevel) {
          print(*it);
        }
      }
    } else {
      for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
        if(it->section==helpOption) {
          print(*it);
        }
      }
    }
  } else if(!_options.isEmpty()) {
    print(_options.first());
    if(_options.count()>1) {
      print("\nMore options are available. Use '-h all' to display all of them.", QString(), 2);
    }
  }
  if( !_examples.isEmpty()) {
    App::log("\nExamples:\n");
    for(QList<Example>::iterator it=_examples.begin(); it!=_examples.end(); it++) {
      Example& o=*it;
      App::log("\n");
      print(o.command, QString(), 9);
      App::log("\n");
      print(o.comments, QString(), 2);
    }
  }
  App::log("\nSee also:\n");
  if( !_seeAlso.isEmpty()) {
    print(_seeAlso, QString(), 2);
  }
  print("More information at http://www.geopsy.org", QString(), 2);
  App::log("\nAuthors:\n");
  print(CoreApplication::authors(), QString(), 2);
}

QString ApplicationHelp::encodeToHtml(QString str)
{
  str.replace("&","&amp;");
  str.replace("'","&apos;");
  str.replace("\"","&quot;");
  str.replace("<","&lt;");
  str.replace(">","&gt;");
  str.replace("\n  ","<li/>");
  str.replace("\n","<br/>");
  return str;
}

QString ApplicationHelp::encodeToLatex(QString str)
{
  str.replace("\\","\\\\");
  str.replace("_","\\_");
  str.replace("$","\\$");
  str.replace("\n","\\\\\n");
  str.replace("<","\\textless ");
  str.replace(">","\\textgreater ");
  str.replace("#","\\#");
  str.replace("^","\\textasciicircum");
  str.replace("|","\\textbar");
  return str;
}

void ApplicationHelp::execHtml()
{
  TRACE;
  App::log("<h2>Name</h2>\n"
           +encodeToHtml(QCoreApplication::applicationName())
           +" - version "+encodeToHtml(CoreApplication::version(QCoreApplication::applicationName()))+"\n"
           +"<h2>Synopsis</h2>\n"
           +encodeToHtml(QCoreApplication::applicationName())+" "
           +encodeToHtml(_optionSummary)+"\n"
           +"<h2>Description</h2>\n"
           +"<p>"+encodeToHtml(_comments)+"</p>\n");
  for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
    const OptionGroup& g=*it;
    if(g.options.isEmpty()) {
      App::log("<h3>"+encodeToHtml(g.title)+"</h3>\n");
    } else {
      if(g.title.isEmpty()) {
        App::log("<h3>Options:</h3>\n");
      } else {
        App::log("<h3>"+encodeToHtml(g.title)+" options:</h3>\n");
      }
      App::log("<dl compact>\n");
      for(QList<Option>::const_iterator it=g.options.begin(); it!=g.options.end(); it++) {
        const Option& o=*it;
        App::log("<dt><b>"+encodeToHtml(o.option)+"</b></dt>\n"
                 +"<dd>"+encodeToHtml(o.comments)+"</dd>\n");
      }
      App::log("</dl>\n");
    }
  }
  if(!_examples.isEmpty()) {
     App::log("<h2>Examples</h2>\n");
     for(QList<Example>::iterator it=_examples.begin(); it!=_examples.end(); it++) {
       Example& o=*it;
       App::log("<pre>"+encodeToHtml(o.command)+"</pre>\n");
       App::log("<p>"+encodeToHtml(o.comments)+"</p>\n");
     }
  }
  if(!_seeAlso.isEmpty()) {
    App::log("<h2>See Also</h2>\n");
    App::log("<p>"+encodeToHtml(_seeAlso)+"</p>\n");
  }
  App::log("<h2>Authors</h2>\n");
  App::log(encodeToHtml(CoreApplication::authors()));
}

void ApplicationHelp::execLatex()
{
  TRACE;
  App::log("\\documentclass[a4paper,10pt]{article}\n"
            "\n"
            "\\usepackage[a4paper, tmargin=2.1cm, bmargin=2.4cm, hmargin=2.5cm]{geometry}\n"
            "\\usepackage[latin1]{inputenc}\n"
            "\n"
            "\\begin{document}\n"
            "\\begin{center}\n"
            "\\noindent\\large ");
  App::log(encodeToLatex(QCoreApplication::applicationName().toUpper()));
  App::log("\n"
            "\\end{center}\n"
            "\\noindent\\textbf{NAME}\\\\\n"
            "\\hspace*{2cm}\\begin{minipage}{14cm}\n");
  App::log(encodeToLatex(QCoreApplication::applicationName()));
  App::log(" - manual page for version ");
  App::log(encodeToLatex(CoreApplication::version(QCoreApplication::applicationName())));
  App::log("\n\\end{minipage}\n"
            "\n"
            "\\vspace{0.5cm}\n"
            "\\noindent\\textbf{SYNOPSIS}\\\\\n"
            "\\hspace*{2cm}\\begin{minipage}{14cm}\n");
  App::log(encodeToLatex(_optionSummary));
  App::log("\n\\end{minipage}\n"
            "\n"
            "\\vspace{0.5cm}\n"
            "\\noindent\\textbf{DESCRIPTION}\\\\\n"
            "\\hspace*{2cm}\\begin{minipage}{14cm}\n");
  App::log(encodeToLatex(_comments));
  App::log("\n\\end{minipage}\n");
  for(QList<OptionGroup>::iterator it=_options.begin(); it!=_options.end(); it++ ) {
    const OptionGroup& g=*it;
    if(g.options.isEmpty()) {
      App::log("\n"
                "\\vspace{0.5cm}\n"
                "\\noindent\\hspace*{1cm}\\textbf{");
      App::log(encodeToLatex(g.title));
      App::log("}\n");
    } else {
      App::log("\n"
                "\\vspace{0.5cm}\n"
                "\\noindent\\hspace*{1cm}\\textbf{");
      App::log(encodeToLatex(g.title));
      App::log(" options:}\\\\\n");
      for(QList<Option>::const_iterator it=g.options.begin(); it!=g.options.end(); it++ ) {
        const Option& o=*it;
        App::log("\n"
                  "\\noindent\\hspace*{2cm}\\textbf{");
        App::log(encodeToLatex(o.option));
        App::log("}\\\\\n"
                  "\\hspace*{4cm}\\begin{minipage}{12cm}\n");
        App::log(encodeToLatex(o.comments));
        App::log("\n\\end{minipage}\n\n");
      }
    }
  }
  if( !_examples.isEmpty()) {
    App::log("\n"
              "\\vspace{0.5cm}\n"
              "\\noindent\\textbf{EXAMPLES}\n\n");
    for(QList<Example>::iterator it=_examples.begin(); it!=_examples.end(); it++ ) {
      Example& o=*it;
      App::log("\\vspace{0.2cm}\n");
      App::log("\\noindent\\hspace*{3cm}\\begin{minipage}{12cm}\n");
      App::log(encodeToLatex(o.command));
      App::log("\n\\end{minipage}\n\n");
      App::log("\\vspace{0.2cm}\n");
      App::log("\\noindent\\hspace*{2cm}\\begin{minipage}{14cm}\n");
      App::log(encodeToLatex(o.comments));
      App::log("\n\\end{minipage}\n\n");
    }
  }
  App::log("\n"
            "\\vspace{0.5cm}\n"
            "\\noindent\\textbf{SEE ALSO}\\\\\n"
            "\\hspace*{2cm}\\begin{minipage}{14cm}\n");
  if(!_seeAlso.isEmpty()) {
    App::log(encodeToLatex(_seeAlso));
  }
  App::log(encodeToLatex("\n\nMore information at http://www.geopsy.org"));
  App::log("\n\\end{minipage}\n");

  App::log("\n"
            "\\vspace{0.5cm}\n"
            "\\noindent\\textbf{AUTHORS}\\\\\n"
            "\\hspace*{2cm}\\begin{minipage}{14cm}\n");
  App::log(encodeToLatex(CoreApplication::authors()));
  App::log("\n\\end{minipage}\n");

  App::log("\\end{document}\n");
}

/*!
  Print string \a p to stdout. Prefix \a linePrefix and indent \a indent are
  inserted before each line. app.terminalCols() is the maximum number of
  characters per line.
*/
void ApplicationHelp::print(QString p, QString linePrefix, int indent)
{
  TRACE;
  QString line;
  QString mainIndentStr, customIndentStr;
  bool newLine=true;
  mainIndentStr.fill(' ',indent);
  while(!p.isEmpty()) {
    bool lastNewLine=newLine;
    line=getLine(p, CoreApplication::instance()->terminalCols()-indent-linePrefix.count()-customIndentStr.count(),
                   &newLine);
    App::log(linePrefix+mainIndentStr+customIndentStr+line+"\n");
    if(lastNewLine && !newLine) {
      // Set indent by the position of the last double space
      customIndentStr=getCustomIndent(line);
      newLine=false;
    } else if(newLine) {
      customIndentStr="";
    }
  }
}

QString ApplicationHelp::getCustomIndent(QString& text)
{
  int i=text.lastIndexOf("  ");
  if(i>-1) {
    QString customIndentStr;
    customIndentStr.fill(' ',i+2);
    return customIndentStr;
  } else {
    return QString();
  }
  /*int nBlanks=0;
  int n=text.count();
  while(nBlanks<n && text[nBlanks]==' ') nBlanks++;
  if(nBlanks>0) {
    QString customIndentStr;
    customIndentStr.fill(' ',nBlanks);
    text=text.mid(nBlanks);
    return customIndentStr;
  } else {
    return QString();
  }*/
}

/*!
  Returns the first line of \a text. The first line is removed from \a text.
  \a newLine is set to true if a '\n' is encountered else it is set to false.
*/
QString ApplicationHelp::getLine(QString& text, int maxLength, bool * newLine)
{
  TRACE;
  if(maxLength<10) {
    QString line=text;
    text="";
    return line;
  }
  QString line=text;
  int index=text.indexOf("\n");
  if(index>-1) {
    if(index<=maxLength) {
      text=text.mid(index+1);
      line.truncate(index);
      if(newLine) *newLine=true;
      return line;
    }
  }
  if(text.length()<=maxLength) {
    text.clear();
    if(newLine) *newLine=false;
    return line;
  }
  line.truncate(maxLength);
  index=line.lastIndexOf(" ");
  if(maxLength>20 && index>maxLength-20) {
    text=text.mid(index+1); // skip the blank
    line.truncate(index);
    if(newLine) *newLine=false;
    return line;
  }
  text=text.mid(maxLength);
  if(newLine) *newLine=false;
  return line;
}

} // namespace QGpCoreTools
