//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include "qtNewtFileBrowserWidget.h"
#include "ui_qtNewtFileBrowserWidget.h"

#include "smtk/newt/qtNewtInterface.h"

#include <QClipboard>
#include <QCursor>
#include <QDebug>
#include <QGuiApplication>
#include <QListIterator>
#include <QMessageBox>
#include <QNetworkReply>
#include <QPushButton>
#include <QStandardItem>
#include <QStringList>
#include <Qt>
#include <QtGlobal>

#include <algorithm> // sort

namespace
{
// For now, hard code machine to "cori"
const QString MACHINE("cori");

const int NUM_COLUMNS = 4;
const int NAME_COLUMN = 0;
const int SIZE_COLUMN = 1;
const int TYPE_COLUMN = 2;
const int DATE_COLUMN = 3;

// Internal classes used in parsing ls results
class FileSystemItem
{
public:
  enum Type
  {
    FILE = 1,
    FOLDER,
    LINK,
    UNKNOWN,
  };

  QString m_name;
  QString m_size;
  FileSystemItem::Type m_type;
  QString m_date;

  FileSystemItem(const QString& text)
    : m_type(FileSystemItem::UNKNOWN)
  {
    // Parse text from ls command
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
    QStringList split = text.split(' ', QString::SkipEmptyParts);
#else
    QStringList split = text.split(' ', Qt::SkipEmptyParts);
#endif
    QChar typeChar = split[0][0];
    if (typeChar == 'l')
    {
      m_type = FileSystemItem::LINK;
      m_name = QString("%1 -> %2").arg(split[4], split[6]);
      m_size = split[2];
      m_date = split[3];
    }
    else if ((typeChar == '-') || (typeChar == 'd'))
    {
      m_type = typeChar == '-' ? FileSystemItem::FILE : FileSystemItem::FOLDER;
      m_name = split[4];
      m_size = split[2];
      m_date = split[3];
    }
    else
    {
      qWarning() << "Unrecognized file type" << text;
    }
  }

  bool isHidden() const { return m_name[0] == '.'; }

  QString typeString() const
  {
    QString ts("?");
    switch (m_type)
    {
      case FileSystemItem::FILE:
        ts = "File";
        break;

      case FileSystemItem::FOLDER:
        ts = "Folder";
        break;

      case FileSystemItem::LINK:
        ts = "Link";
        break;
    }
    return ts;
  }

  static bool lessThan(const FileSystemItem& left, const FileSystemItem& right)
  {
    QString sLeft = left.m_type == FileSystemItem::FOLDER ? "0." : "1.";
    sLeft.append(left.m_name);

    QString sRight = right.m_type == FileSystemItem::FOLDER ? "0." : "1.";
    sRight.append(right.m_name);

    return sLeft < sRight;
  }
};

} // namespace

namespace newt
{

qtNewtFileBrowserWidget::qtNewtFileBrowserWidget(QWidget* parentWidget)
  : QWidget(parentWidget)
  , ui(new Ui::qtNewtFileBrowserWidget)
  , m_fileIcon(":/icons/newt/file.png")
  , m_folderIcon(":/icons/newt/folder.png")
  , m_model(0, NUM_COLUMNS, this)
  , m_newt(qtNewtInterface::instance())
{
  this->ui->setupUi(this);
  this->ui->m_statusLabel->setText("Click Home or Scratch button");
  this->ui->m_userLabel->setText("?");
  this->ui->m_tableView->setModel(&m_model);

  if (m_newt->isLoggedIn())
  {
    this->ui->m_userLabel->setText(m_newt->userName());
  }
  // Connect to signal from newt interface
  QObject::connect(
    m_newt, &qtNewtInterface::loginComplete, this, &qtNewtFileBrowserWidget::onLogin);

  // Connect to signals from the UI
  QObject::connect(this->ui->m_copyButton, &QPushButton::clicked, [this]() {
    QString path = this->ui->m_folderLine->text();
    auto clipboard = QGuiApplication::clipboard();
    clipboard->setText(path);
    emit this->pathCopied(path);
  });
  QObject::connect(
    this->ui->m_homeButton, &QPushButton::clicked, this, &qtNewtFileBrowserWidget::onHomeClicked);
  QObject::connect(this->ui->m_scratchButton, &QPushButton::clicked, this,
    &qtNewtFileBrowserWidget::onScratchClicked);
  QObject::connect(
    this->ui->m_tableView, &QTableView::activated, this, &qtNewtFileBrowserWidget::onItemActivated);
}

qtNewtFileBrowserWidget::~qtNewtFileBrowserWidget()
{
}

void qtNewtFileBrowserWidget::onItemActivated(const QModelIndex& index)
{
  // Only respond if name clicked
  int col = index.column();
  if (col != NAME_COLUMN)
  {
    return;
  }

  int row = index.row();
  QStandardItem* nameItem = m_model.item(row, NAME_COLUMN);
  QString name = nameItem->text();
  if (name == "..")
  {
    QString path = m_currentPath;
    // Strip off trailing slash
    if (path.back() == QChar('/'))
    {
      path = path.left(path.size() - 1);
    }
    int pos = path.lastIndexOf(QChar('/'));
    QString parentPath = path.left(pos);
    this->gotoPath(parentPath);
    return;
  }

  int intValue = nameItem->data().toInt();
  FileSystemItem::Type fsType = static_cast<FileSystemItem::Type>(intValue);
  QString sep = m_currentPath.back() == QChar('/') ? "" : "/";
  if (fsType == FileSystemItem::FOLDER)
  {
    QString nextPath = QString("%1%2%3").arg(m_currentPath, sep, name);
    this->gotoPath(nextPath);
  }
}

void qtNewtFileBrowserWidget::onListFolderReply()
{
  this->setCursor(Qt::ArrowCursor);
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(this->sender());

  QString text;
  if (!this->getCommandReply(reply, text))
  {
    return;
  }

  // Parse list folder reply to create list of FileSystemItem instances
  QList<FileSystemItem> fsList;
  QStringList lines = text.split("\n");
  QListIterator<QString> lineIter(lines);
  lineIter.next(); // skip first line ("Total size")
  while (lineIter.hasNext())
  {
    QString line = lineIter.next();
    // qDebug() << line;
    FileSystemItem fsItem(line);
    if (!fsItem.isHidden())
    {
      fsList.push_back(fsItem);
    }
  }
  std::sort(fsList.begin(), fsList.end(), FileSystemItem::lessThan);

  // Rebuild model
  m_model.clear();

  QStringList labels = { "Name", "Size", "Type", "Date Modified" };
  m_model.setHorizontalHeaderLabels(labels);

  // Generate QStandardItem instances from FileSystemItem list
  m_model.setRowCount(fsList.size());
  for (int i = 0; i < fsList.size(); ++i)
  {
    FileSystemItem fsItem = fsList[i];

    QStandardItem* nameItem = new QStandardItem(fsItem.m_name);
    if (fsItem.m_type == FileSystemItem::FILE)
    {
      nameItem->setIcon(m_fileIcon);
    }
    else if (fsItem.m_type == FileSystemItem::FOLDER)
    {
      nameItem->setIcon(m_folderIcon);
    }
    m_model.setItem(i, NAME_COLUMN, nameItem);

    m_model.setItem(i, SIZE_COLUMN, new QStandardItem(fsItem.m_size));
    m_model.setItem(i, TYPE_COLUMN, new QStandardItem(fsItem.typeString()));
    m_model.setItem(i, DATE_COLUMN, new QStandardItem(fsItem.m_date));

    // Set flags
    for (int col = 0; col < NUM_COLUMNS; ++col)
    {
      m_model.item(i, col)->setFlags(Qt::ItemIsEnabled);
    }

    // Attach type as data
    m_model.item(i, 0)->setData(fsItem.m_type);
  } // for (i)

  // If not at root path, insert ".." item
  if ((m_requestPath != m_homePath) && (m_requestPath != m_scratchPath))
  {
    m_model.insertRow(0, new QStandardItem(".."));
    m_model.item(0, 0)->setFlags(Qt::ItemIsEnabled);
  }

  this->ui->m_tableView->resizeColumnsToContents();
  this->ui->m_tableView->horizontalHeader()->setVisible(true);
  this->ui->m_tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);

  m_currentPath = m_requestPath;
  this->ui->m_folderLine->setText(m_currentPath);

  QString status = QString("%1 items").arg(m_model.rowCount());
  this->ui->m_statusLabel->setText(status);
  m_requestPath.clear();

  reply->deleteLater();
  emit this->endGotoPath(m_currentPath);
}

void qtNewtFileBrowserWidget::onLogin(const QString& username)
{
  this->ui->m_userLabel->setText(username);
}

void qtNewtFileBrowserWidget::onNewtError(QString text, QNetworkReply* reply)
{
  this->setCursor(Qt::ArrowCursor);
  qWarning() << text;

  QMessageBox msgBox(this->parentWidget());
  msgBox.setText("Network Error");
  msgBox.setDetailedText(text);
  msgBox.setStandardButtons(QMessageBox::Close);
  msgBox.setStyleSheet("QTextEdit{min-width:600 px; min-height: 400px;}");
  msgBox.exec();

  reply->deleteLater();
}

void qtNewtFileBrowserWidget::onHomeClicked()
{
  if (!m_homePath.isEmpty())
  {
    this->gotoPath(m_homePath);
    return;
  }

  this->setCursor(Qt::BusyCursor);
  QNetworkReply* reply = m_newt->requestHomePath(MACHINE);
  QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
    QString text;
    if (!this->getCommandReply(reply, text))
    {
      return;
    }

    qDebug() << "Home path is" << text;
    this->m_homePath = text;
    this->gotoPath(text);
    reply->deleteLater();
  });
}

void qtNewtFileBrowserWidget::onScratchClicked()
{
  if (!m_scratchPath.isEmpty())
  {
    this->gotoPath(m_scratchPath);
    return;
  }

  this->setCursor(Qt::BusyCursor);
  QNetworkReply* reply = m_newt->requestScratchPath(MACHINE);
  QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
    QString text;
    if (!this->getCommandReply(reply, text))
    {
      return;
    }

#ifndef NDEBUG
    qDebug() << "Scratch path is" << text;
#endif
    this->m_scratchPath = text;
    this->gotoPath(text);
    reply->deleteLater();
  });
}

bool qtNewtFileBrowserWidget::getCommandReply(QNetworkReply* reply, QString& text)
{
  QString content;
  QString error;
  this->m_newt->getCommandReply(reply, content, error);

  if (error.isEmpty())
  {
    text = content;
    reply->deleteLater();
    return true;
  }

  this->onNewtError(error, reply);
  return false;
}

void qtNewtFileBrowserWidget::gotoPath(const QString& path)
{
#ifndef NDEBUG
  qDebug() << "filebrowser::gotoPath" << path;
#endif

  emit this->beginGotoPath(path);
  m_requestPath = path;
  this->setCursor(Qt::BusyCursor);
  // Make sure path ends with forward slash
  QString sep = path.endsWith('/') ? "" : "/";

  // Use ls command to get files
  QString command = QString("ls -Gghpt --time-style=\"+%Y-%m-%d\" %1%2").arg(path, sep);
  // Substitutes "%2B" for "+" to avoid encoding problems
  command.replace('+', "%2B");

  QNetworkReply* reply = m_newt->sendCommand(command, MACHINE);
  QObject::connect(
    reply, &QNetworkReply::finished, this, &qtNewtFileBrowserWidget::onListFolderReply);
}

} // end namespace
