/***************************************************************************
**
**  This file is part of GeopsyLand.
**
**  GeopsyLand is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  GeopsyLand 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 General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2018-12-03
**  Copyright: 2018-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Terminal.h"
#include "MiniBash.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
Terminal::Terminal(QWidget * parent)
  : QPlainTextEdit(parent)
{
  TRACE;
  document()->setMaximumBlockCount(1000);
  QPalette p=palette();
  p.setColor(QPalette::Base, Qt::black);
  p.setColor(QPalette::Text, Qt::green);
  setPalette(p);
#ifdef Q_OS_WIN
  setFont(QFont("Courier New", 10));
#else
  setFont(QFont("Monospace", 9));
#endif
  setWordWrapMode(QTextOption::WrapAnywhere);

  _interpreter=new MiniBash(this);
  connect(_interpreter, SIGNAL(processFinished()), this, SLOT(processFinished()));

  _promptLength=0;
  putData(tr("                  Welcome to Mini Shell\n"
             "\n"
             "This terminal offers basic shell features for educational purpose.\n"
             "To get a full featured terminal, switch to Linux OS.\n"
             "\n"
             "Type 'help' to get the list of built-in commands\n"
             "\n"));
  addPrompt();
}

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

void Terminal::addPath(const QString& p)
{
  TRACE;
  _interpreter->addPath(p);
}

void Terminal::putData(const QString& data)
{
  if(data.startsWith(QChar(0x1B))) {
    if(data.mid(1, 3)=="[1A") {
      QTextCursor c=textCursor();
      c.movePosition(QTextCursor::PreviousBlock, QTextCursor::KeepAnchor);
      setTextCursor(c);
      insertPlainText(data.mid(4));
    }
  } else {
    insertPlainText(data);
  }

  QScrollBar *bar=verticalScrollBar();
  bar->setValue(bar->maximum());
}

void Terminal::putData(const QStringList& data)
{
  // Get number of character columns
  QFontMetricsF fm(font());
  int columns=qRound(static_cast<double>(width())/fm.width('m'))-2;
  // Find the length of longest entry
  int n=0;
  for(QStringList::const_iterator it=data.begin(); it!=data.end(); it++) {
    if(it->count()>n) {
      n=it->count();
    }
  }
  // Minimum 2 space for separation
  n+=2;
  columns/=n;
  int rows=data.count()/columns;
  if(rows*columns<data.count()) {
    rows++;
  }
  // Build the lines and print
  QString blanks, item, line;
  int index;
  for(int row=0; row<rows; row++) {
    line.clear();
    for(int col=0; col<columns; col++) {
      index=col*rows+row;
      if(index<data.count()) {
        item=data.at(col*rows+row);
      } else {
        item.clear();
      }
      blanks.fill(' ', n-item.count());
      line+=item+blanks;
    }
    putData(line+"\n");
  }
}

void Terminal::keyPressEvent(QKeyEvent *e)
{
  QTextCursor c=textCursor();
  switch (e->key()) {
  case Qt::Key_Backspace:
  case Qt::Key_Left:
    if(c.positionInBlock()>_promptLength &&
       e->modifiers()==Qt::NoModifier) {
      break;
    } else {
      return;
    }
  case Qt::Key_Home:
    c.movePosition(QTextCursor::StartOfBlock);
    c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, _promptLength);
    setTextCursor(c);
    return;
  case Qt::Key_End:
    c.movePosition(QTextCursor::End);
    setTextCursor(c);
    return;
  case Qt::Key_Enter:
  case Qt::Key_Return:
    if(_interpreter->status()==MiniBash::Processing) {
      break;
    } else {
      c.movePosition(QTextCursor::End);
      setTextCursor(c);
      QString data=document()->lastBlock().text().mid(_promptLength);
       QPlainTextEdit::keyPressEvent(e);
       _interpreter->parse(data);
      switch(_interpreter->status()) {
      case MiniBash::WaitMoreData:
        addLineContinued();
        break;
      case MiniBash::Idle:
        addPrompt();
        break;
      case MiniBash::Processing:
        break;
      }
    }
    return;
  case Qt::Key_Tab:
    completion();
    return;
  case Qt::Key_Up:
    clearLine();
    putData(_interpreter->previousHistory());
    return;
  case Qt::Key_Down:
    clearLine();
    putData(_interpreter->nextHistory());
    return;
  case Qt::Key_PageUp:
  case Qt::Key_PageDown:
    return;
  case Qt::Key_C:
    if(e->modifiers()==Qt::CTRL) {
      if(!_interpreter->killCurrentProcess()) {
        addPrompt();
      }
      return;
    }
    break;
  case Qt::Key_D:
    if(e->modifiers()==Qt::CTRL) {
      exit();
      return;
    }
    break;
  default:
    break;
  }
  QPlainTextEdit::keyPressEvent(e);
}

void Terminal::processFinished()
{
  addPrompt();
}

void Terminal::addPrompt()
{
  TRACE;
  QDir d;
  QString p=MiniBash::fromNativePath(d.absolutePath());
  QFileInfo fi(p);
  p=fi.fileName();
  if(p.isEmpty()) {
    p="/";
  }
  p+="$ ";
  _promptLength=p.length();
  insertPlainText(p);
}

void Terminal::clearLine()
{
  QTextCursor c=textCursor();
  c.movePosition(QTextCursor::StartOfBlock);
  c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, _promptLength);
  c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
  c.removeSelectedText();
}
void Terminal::addLineContinued()
{
  TRACE;
  _promptLength=2;
  insertPlainText("> ");
}

void Terminal::completion()
{
  QTextBlock b=document()->lastBlock();
  int posInLine=textCursor().position()-b.position();
  QString currentText=b.text();
  QString data=currentText.left(textCursor().position()-b.position()).mid(_promptLength);
  QString completion;
  QStringList list=_interpreter->complete(data, completion);
  putData(completion);
  if(list.count()>1) {
    if(_completionWord!=data) {
      _completionWord=data;
    } else {
      QTextCursor c=textCursor();
      c.movePosition(QTextCursor::EndOfLine);
      setTextCursor(c);
      putData("\n");
      putData(list);
      putData(currentText);
      c=textCursor();
      c.movePosition(QTextCursor::StartOfLine);
      c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, posInLine);
      setTextCursor(c);
    }
  }
}

void Terminal::mousePressEvent(QMouseEvent *e)
{
  _selection=cursorForPosition(e->pos());
}

void Terminal::mouseReleaseEvent(QMouseEvent *e)
{
  if(e->button()==Qt::MidButton) {
    QClipboard * clipboard=QGuiApplication::clipboard();
    QString t=clipboard->text(QClipboard::Selection);
    if(t.isEmpty()) {
      t=clipboard->text(QClipboard::Clipboard);
    }
    insertPlainText(clipboard->text(QClipboard::Selection));
  } else if(e->button()==Qt::LeftButton) {
    QList<QTextEdit::ExtraSelection> selList=extraSelections();
    if(!selList.isEmpty()) {
      QClipboard * clipboard=QGuiApplication::clipboard();
      QString t=selList.first().cursor.selectedText();
      clipboard->setText(t, QClipboard::Clipboard);
      clipboard->setText(t, QClipboard::Selection);
    }
  }
}

void Terminal::mouseMoveEvent(QMouseEvent *e)
{
  QList<QTextEdit::ExtraSelection> selList;
  QTextEdit::ExtraSelection sel;
  int i=cursorForPosition(e->pos()).position();
  sel.format.setBackground(Qt::green);
  sel.format.setForeground(Qt::black);
  sel.cursor=_selection;
  sel.cursor.setPosition(i, QTextCursor::KeepAnchor);
  selList.append(sel);
  setExtraSelections(selList);
}

void Terminal::mouseDoubleClickEvent(QMouseEvent *e)
{
  QList<QTextEdit::ExtraSelection> selList;
  QTextEdit::ExtraSelection sel;
  sel.format.setBackground(Qt::green);
  sel.format.setForeground(Qt::black);
  sel.cursor=cursorForPosition(e->pos());
  sel.cursor.select(QTextCursor::WordUnderCursor);
  selList.append(sel);
  setExtraSelections(selList);
}

void Terminal::contextMenuEvent(QContextMenuEvent *e)
{
  Q_UNUSED(e)
}

void Terminal::exit()
{
  QWidget * w=parentWidget();
  while(w->parentWidget()) {
    w=w->parentWidget();
  }
  w->close();
}
