// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause

#include "pqFileDialogModel.h"

#include <algorithm>

#include <QApplication>
#include <QDateTime>
#include <QDir>
#include <QLocale>
#include <QMessageBox>
#include <QStyle>

#include <pqApplicationCore.h>
#include <pqServer.h>
#include <vtkClientServerStream.h>
#include <vtkCollection.h>
#include <vtkCollectionIterator.h>
#include <vtkDirectory.h>
#include <vtkPVFileInformation.h>
#include <vtkPVFileInformationHelper.h>
#include <vtkPVSession.h>
#include <vtkSMDirectoryProxy.h>
#include <vtkSMIntVectorProperty.h>
#include <vtkSMPropertyHelper.h>
#include <vtkSMProxy.h>
#include <vtkSMProxyManager.h>
#include <vtkSMSessionProxyManager.h>
#include <vtkSMSettings.h>
#include <vtkSMStringVectorProperty.h>
#include <vtkSmartPointer.h>
#include <vtkStringList.h>

#include "pqObjectBuilder.h"
#include "pqSMAdaptor.h"

//////////////////////////////////////////////////////////////////////
// pqFileDialogModelFileInfo

class pqFileDialogModelFileInfo
{
public:
  pqFileDialogModelFileInfo()
    : Type(vtkPVFileInformation::INVALID)
    , Hidden(false)
  {
  }
  pqFileDialogModelFileInfo(const QString& l, const QString& filepath,
    vtkPVFileInformation::FileTypes t, const bool& h, const QString& extension,
    const long long& sizeVal, const time_t& modificationTimeVal,
    const QList<pqFileDialogModelFileInfo>& g = QList<pqFileDialogModelFileInfo>())
    : Label(l)
    , FilePath(filepath)
    , Type(t)
    , Hidden(h)
    , Group(g)
    , Extension(extension)
    , ModificationTime(modificationTimeVal)
    , Size(sizeVal)
  {
  }

  const QString& label() const { return this->Label; }

  const QString& filePath() const { return this->FilePath; }

  vtkPVFileInformation::FileTypes type() const { return this->Type; }

  bool isGroup() const { return !this->Group.empty(); }

  bool isHidden() const { return this->Hidden; }

  const QList<pqFileDialogModelFileInfo>& group() const { return this->Group; }

  QString extensionTypeString() const
  {
    if (this->Type == vtkPVFileInformation::DIRECTORY)
    {
      return QCoreApplication::translate("pqFileDialogModel", "Folder");
    }
    else
    {
      return QString("%1%2%3")
        .arg(this->Extension)
        .arg(this->Extension.isEmpty() ? "" : " ")
        .arg(QCoreApplication::translate("pqFileDialogModel", "File"));
    }
  }

  QString sizeString() const
  {
    if (this->Type == vtkPVFileInformation::DIRECTORY)
    {
      return QString();
    }
    const long long kb = 1024;
    const long long mb = 1024 * kb;
    const long long gb = 1024 * mb;
    const long long tb = 1024 * gb;

    if (this->Size >= tb)
    {
      return QCoreApplication::translate("pqFileDialogModel", "%1 TB")
        .arg(QLocale().toString(qreal(this->Size) / tb, 'f', 3));
    }
    if (this->Size >= gb)
    {
      return QCoreApplication::translate("pqFileDialogModel", "%1 GB")
        .arg(QLocale().toString(qreal(this->Size) / gb, 'f', 2));
    }
    if (this->Size >= mb)
    {
      return QCoreApplication::translate("pqFileDialogModel", "%1 MB")
        .arg(QLocale().toString(qreal(this->Size) / mb, 'f', 1));
    }
    if (this->Size >= kb)
    {
      return QCoreApplication::translate("pqFileDialogModel", "%1 KB")
        .arg(QLocale().toString(qreal(this->Size) / kb, 'f', 1));
    }
    return QCoreApplication::translate("pqFileDialogModel", "%1 bytes")
      .arg(QLocale().toString(static_cast<qulonglong>(this->Size)));
  }

  QString modificationTimeString() const
  {
    return QLocale::system().toString(
      QDateTime::fromSecsSinceEpoch(this->ModificationTime), QLocale::ShortFormat);
  }

  qulonglong size() const { return static_cast<qulonglong>(this->Size); }

  QDateTime modificationTime() const
  {
    return QDateTime::fromSecsSinceEpoch(this->ModificationTime);
  }

private:
  QString Label;
  QString FilePath;
  vtkPVFileInformation::FileTypes Type;
  bool Hidden;
  QList<pqFileDialogModelFileInfo> Group;
  QString Extension;
  time_t ModificationTime;
  long long Size;
};

/////////////////////////////////////////////////////////////////////
// Icons

pqFileDialogModelIconProvider::pqFileDialogModelIconProvider()
{
  QStyle* style = QApplication::style();
  this->InvalidIcon = style->standardIcon(QStyle::SP_MessageBoxWarning);
  this->FolderLinkIcon = style->standardIcon(QStyle::SP_DirLinkIcon);
  this->FileLinkIcon = style->standardIcon(QStyle::SP_FileLinkIcon);
  this->DomainIcon.addPixmap(QPixmap(":/pqCore/Icons/pqDomain16.png"));
  this->NetworkIcon.addPixmap(QPixmap(":/pqCore/Icons/pqNetwork16.png"));
}

QIcon pqFileDialogModelIconProvider::icon(IconType t) const
{
  switch (t)
  {
    case Invalid:
      return this->InvalidIcon;
    case Computer:
      return QFileIconProvider::icon(QFileIconProvider::Computer);
    case Drive:
      return QFileIconProvider::icon(QFileIconProvider::Drive);
    case Folder:
      return QFileIconProvider::icon(QFileIconProvider::Folder);
    case File:
      return QFileIconProvider::icon(QFileIconProvider::File);
    case FolderLink:
      return this->FolderLinkIcon;
    case FileLink:
      return this->FileLinkIcon;
    case NetworkFolder:
      return QFileIconProvider::icon(QFileIconProvider::Network);
    case NetworkRoot:
      return this->NetworkIcon;
    case NetworkDomain:
      return this->DomainIcon;
  }
  return QIcon();
}

QIcon pqFileDialogModelIconProvider::icon(vtkPVFileInformation::FileTypes f) const
{
  if (f == vtkPVFileInformation::INVALID)
  {
    return icon(pqFileDialogModelIconProvider::Invalid);
  }
  if (f == vtkPVFileInformation::DIRECTORY_LINK)
  {
    return icon(pqFileDialogModelIconProvider::FolderLink);
  }
  else if (f == vtkPVFileInformation::SINGLE_FILE_LINK)
  {
    return icon(pqFileDialogModelIconProvider::FileLink);
  }
  else if (f == vtkPVFileInformation::NETWORK_SHARE)
  {
    return icon(pqFileDialogModelIconProvider::NetworkFolder);
  }
  else if (f == vtkPVFileInformation::NETWORK_SERVER)
  {
    return icon(pqFileDialogModelIconProvider::Computer);
  }
  else if (f == vtkPVFileInformation::NETWORK_DOMAIN)
  {
    return icon(pqFileDialogModelIconProvider::NetworkDomain);
  }
  else if (f == vtkPVFileInformation::NETWORK_ROOT)
  {
    return icon(pqFileDialogModelIconProvider::NetworkRoot);
  }
  else if (vtkPVFileInformation::IsDirectory(f))
  {
    return icon(pqFileDialogModelIconProvider::Folder);
  }
  return icon(pqFileDialogModelIconProvider::File);
}
QIcon pqFileDialogModelIconProvider::icon(const QFileInfo& info) const
{
  return QFileIconProvider::icon(info);
}
QIcon pqFileDialogModelIconProvider::icon(QFileIconProvider::IconType ico) const
{
  return QFileIconProvider::icon(ico);
}

// NOLINTNEXTLINE(readability-redundant-member-init)
Q_GLOBAL_STATIC(pqFileDialogModelIconProvider, Icons);

namespace
{

///////////////////////////////////////////////////////////////////////
// CaseInsensitiveSort

bool CaseInsensitiveSort(const pqFileDialogModelFileInfo& A, const pqFileDialogModelFileInfo& B)
{
  // Sort alphabetically (but case-insensitively)
  return A.label().toLower() < B.label().toLower();
}

bool getShowDetailedInformationSetting()
{
  vtkSMSettings* settings = vtkSMSettings::GetInstance();
  pqServer* server = pqApplicationCore::instance()->getActiveServer();
  vtkSMSessionProxyManager* pxm = server->proxyManager();
  vtkSMProxy* helperProxy = pxm->NewProxy("misc", "FileInformationHelper");
  settings->GetProxySettings(helperProxy);
  bool result = vtkSMPropertyHelper(helperProxy, "ReadDetailedFileInformation").GetAsInt() == 1;
  helperProxy->Delete();
  return result;
}

void setShowDetailedInformationSetting(pqServer* server, bool show)
{
  if (!server)
  {
    server = pqApplicationCore::instance()->getActiveServer();
  }
  vtkSMSessionProxyManager* pxm = server->proxyManager();
  vtkSMProxy* helperProxy = pxm->NewProxy("misc", "FileInformationHelper");
  vtkSMPropertyHelper(helperProxy, "ReadDetailedFileInformation").Set(show ? 1 : 0);
  helperProxy->UpdateVTKObjects();
  vtkSMSettings* settings = vtkSMSettings::GetInstance();
  settings->SetProxySettings(helperProxy);
  helperProxy->Delete();
}

} // namespace

/////////////////////////////////////////////////////////////////////////
// pqFileDialogModel::Implementation

class pqFileDialogModel::pqImplementation
{
public:
  Qt::ItemFlags FileItemFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
  Qt::ItemFlags DirItemFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;

  pqImplementation(pqServer* server)
    : Separator(0)
    , Server(server)
  {
    // Always create a client side helper as it may be needed
    this->FileInformationHelper = vtkSmartPointer<vtkPVFileInformationHelper>::New();
    this->FileInformationHelper->SetReadDetailedFileInformation(
      getShowDetailedInformationSetting());

    // if we are doing remote browsing
    if (server)
    {
      vtkSMSessionProxyManager* pxm = server->proxyManager();

      vtkSMProxy* helper = pxm->NewProxy("misc", "FileInformationHelper");
      this->FileInformationHelperProxy = helper;
      helper->Delete();
      helper->UpdateVTKObjects();
      helper->UpdatePropertyInformation();
      QString separator =
        pqSMAdaptor::getElementProperty(helper->GetProperty("PathSeparator")).toString();
      this->Separator = separator.toUtf8().data()[0];
      // Since this isn't going through the proxy widget we have to manually restore the setting
      vtkSMPropertyHelper(helper, "ReadDetailedFileInformation")
        .Set(getShowDetailedInformationSetting());
    }
    else
    {
      this->Separator = this->FileInformationHelper->GetPathSeparator()[0];
    }

    this->FileInformation = vtkSmartPointer<vtkPVFileInformation>::New();

    // Recover client side current path
    vtkPVFileInformation* info = this->GetData(false, "", ".", false, true);
    if (server)
    {
      // When browsing remote, try to use the client current path as default path
      info = this->GetData(false, "", info->GetFullPath(), false);
      if (info->GetType() == vtkPVFileInformation::INVALID)
      {
        // Client current path does not exit server-side
        // Use server current path instead
        info = this->GetData(false, "", ".", false);
      }
    }
    this->CurrentPath = info->GetFullPath();
  }

  ~pqImplementation() = default;

  /// Removes multiple-slashes, ".", and ".." from the given path string,
  /// and points slashes in the correct direction for the server
  QString cleanPath(const QString& path)
  {
    QString result = QDir::cleanPath(QDir::fromNativeSeparators(path));
    return result.trimmed();
  }

  /// query the file system for information
  vtkPVFileInformation* GetData(bool dirListing, const QString& path, bool specialDirs)
  {
    return this->GetData(dirListing, this->CurrentPath, path, specialDirs);
  }

  /// query the file system for information
  vtkPVFileInformation* GetData(bool dirListing, const QString& workingDir, const QString& path,
    bool specialDirs, bool forceClient = false)
  {
    if (this->FileInformationHelperProxy && !forceClient)
    {
      // send data to server
      vtkSMProxy* helper = this->FileInformationHelperProxy;
      pqSMAdaptor::setElementProperty(helper->GetProperty("WorkingDirectory"), workingDir.toUtf8());
      pqSMAdaptor::setElementProperty(helper->GetProperty("DirectoryListing"), dirListing);
      pqSMAdaptor::setElementProperty(helper->GetProperty("Path"), path.toUtf8());
      pqSMAdaptor::setElementProperty(helper->GetProperty("SpecialDirectories"), specialDirs);
      pqSMAdaptor::setElementProperty(helper->GetProperty("GroupFileSequences"), this->GroupFiles);
      helper->UpdateVTKObjects();

      // get data from server
      this->FileInformation->Initialize();
      this->FileInformationHelperProxy->GatherInformation(this->FileInformation);
    }
    else
    {
      vtkPVFileInformationHelper* helper = this->FileInformationHelper;
      helper->SetDirectoryListing(dirListing);
      helper->SetPath(path.toUtf8().data());
      helper->SetSpecialDirectories(specialDirs);
      helper->SetWorkingDirectory(workingDir.toUtf8().data());
      helper->SetGroupFileSequences(this->GroupFiles);
      this->FileInformation->CopyFromObject(helper);
    }
    return this->FileInformation;
  }

  /// put queried information into our model
  void Update(const QString& path, vtkPVFileInformation* dir)
  {
    this->CurrentPath = path;
    this->FileList.clear();

    QList<pqFileDialogModelFileInfo> dirs;
    QList<pqFileDialogModelFileInfo> files;

    vtkSmartPointer<vtkCollectionIterator> iter;
    iter.TakeReference(dir->GetContents()->NewIterator());

    for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
    {
      vtkPVFileInformation* info = vtkPVFileInformation::SafeDownCast(iter->GetCurrentObject());
      if (!info)
      {
        continue;
      }
      if (vtkPVFileInformation::IsDirectory(info->GetType()))
      {
        dirs.push_back(pqFileDialogModelFileInfo(QString::fromUtf8(info->GetName()),
          QString::fromUtf8(info->GetFullPath()),
          static_cast<vtkPVFileInformation::FileTypes>(info->GetType()), info->GetHidden(),
          info->GetExtension(), info->GetSize(), info->GetModificationTime()));
      }
      else if (info->GetType() != vtkPVFileInformation::FILE_GROUP &&
        info->GetType() != vtkPVFileInformation::DIRECTORY_GROUP)
      {
        files.push_back(pqFileDialogModelFileInfo(QString::fromUtf8(info->GetName()),
          QString::fromUtf8(info->GetFullPath()),
          static_cast<vtkPVFileInformation::FileTypes>(info->GetType()), info->GetHidden(),
          info->GetExtension(), info->GetSize(), info->GetModificationTime()));
      }
      else if (info->GetType() == vtkPVFileInformation::FILE_GROUP ||
        info->GetType() == vtkPVFileInformation::DIRECTORY_GROUP)
      {
        QList<pqFileDialogModelFileInfo> groupFiles;
        vtkSmartPointer<vtkCollectionIterator> childIter;
        childIter.TakeReference(info->GetContents()->NewIterator());
        for (childIter->InitTraversal(); !childIter->IsDoneWithTraversal();
             childIter->GoToNextItem())
        {
          vtkPVFileInformation* child =
            vtkPVFileInformation::SafeDownCast(childIter->GetCurrentObject());
          groupFiles.push_back(pqFileDialogModelFileInfo(/*QString::fromUtf8*/ (child->GetName()),
            /*QString::fromUtf8*/ (child->GetFullPath()),
            static_cast<vtkPVFileInformation::FileTypes>(child->GetType()), child->GetHidden(),
            child->GetExtension(), child->GetSize(), child->GetModificationTime()));
        }

        const bool as_files = (info->GetType() == vtkPVFileInformation::FILE_GROUP);
        auto& container = as_files ? files : dirs;
        container.push_back(pqFileDialogModelFileInfo(/*QString::fromUtf8*/ (info->GetName()),
          groupFiles[0].filePath(),
          (as_files ? vtkPVFileInformation::SINGLE_FILE : vtkPVFileInformation::DIRECTORY),
          info->GetHidden(), info->GetExtension(), info->GetSize(), info->GetModificationTime(),
          groupFiles));
      }
    }

    std::sort(dirs.begin(), dirs.end(), CaseInsensitiveSort);
    std::sort(files.begin(), files.end(), CaseInsensitiveSort);

    for (int i = 0; i != dirs.size(); ++i)
    {
      this->FileList.push_back(dirs[i]);
    }
    for (int i = 0; i != files.size(); ++i)
    {
      this->FileList.push_back(files[i]);
    }
  }

  QStringList getFilePaths(const QModelIndex& index)
  {
    QStringList results;

    QModelIndex p = index.parent();
    if (p.isValid())
    {
      if (p.row() < this->FileList.size())
      {
        pqFileDialogModelFileInfo& file = this->FileList[p.row()];
        const QList<pqFileDialogModelFileInfo>& grp = file.group();
        if (index.row() < grp.size())
        {
          results.push_back(grp[index.row()].filePath());
        }
      }
    }
    else if (index.row() < this->FileList.size())
    {
      pqFileDialogModelFileInfo& file = this->FileList[index.row()];
      if (file.isGroup() && file.group().count() > 0)
      {
        for (int i = 0; i < file.group().count(); i++)
        {
          results.push_back(file.group().at(i).filePath());
        }
      }
      else
      {
        results.push_back(file.filePath());
      }
    }

    return results;
  }

  bool isHidden(const QModelIndex& index)
  {
    const pqFileDialogModelFileInfo* info = this->infoForIndex(index);
    return info ? info->isHidden() : false;
  }

  bool isDir(const QModelIndex& index)
  {
    const pqFileDialogModelFileInfo* info = this->infoForIndex(index);
    return info ? vtkPVFileInformation::IsDirectory(info->type()) : false;
  }

  bool isRemote() { return this->Server; }

  pqServer* getServer() { return this->Server; }

  /// Path separator for the connected server's filesystem.
  char Separator;

  /// Current path being displayed (server's filesystem).
  QString CurrentPath;
  /// Caches information about the set of files within the current path.
  QVector<pqFileDialogModelFileInfo> FileList; // adjacent memory occupation for QModelIndex

  const pqFileDialogModelFileInfo* infoForIndex(const QModelIndex& idx) const
  {
    if (idx.isValid() && nullptr == idx.internalPointer() && idx.row() >= 0 &&
      idx.row() < this->FileList.size())
    {
      return &this->FileList[idx.row()];
    }
    else if (idx.isValid() && idx.internalPointer())
    {
      pqFileDialogModelFileInfo* ptr =
        reinterpret_cast<pqFileDialogModelFileInfo*>(idx.internalPointer());
      const QList<pqFileDialogModelFileInfo>& grp = ptr->group();
      if (idx.row() >= 0 && idx.row() < grp.size())
      {
        return &grp[idx.row()];
      }
    }
    return nullptr;
  }

  void setShowDetailedInformation(bool show)
  {
    if (this->FileInformationHelperProxy)
    {
      vtkSMPropertyHelper(this->FileInformationHelperProxy, "ReadDetailedFileInformation")
        .Set(show);
    }
    else if (this->FileInformationHelper)
    {
      this->FileInformationHelper->SetReadDetailedFileInformation(show);
    }
    setShowDetailedInformationSetting(this->Server, show);
  }

  bool isShowingDetailedInformation() const
  {
    bool currentValue = false;
    if (this->FileInformationHelperProxy)
    {
      currentValue =
        vtkSMPropertyHelper(this->FileInformationHelperProxy, "ReadDetailedFileInformation")
          .GetAsInt() == 1;
    }
    else if (this->FileInformationHelper)
    {
      currentValue = this->FileInformationHelper->GetReadDetailedFileInformation();
    }
    return currentValue;
  }

  void setGroupFiles(bool group) { this->GroupFiles = group; }

  bool isGroupingFiles() const { return this->GroupFiles; }

private:
  // server vs. local implementation private
  pqServer* Server;

  vtkSmartPointer<vtkPVFileInformationHelper> FileInformationHelper;
  vtkSmartPointer<vtkSMProxy> FileInformationHelperProxy;
  vtkSmartPointer<vtkPVFileInformation> FileInformation;
  bool GroupFiles = true;
};

//////////////////////////////////////////////////////////////////////////
// pqFileDialogModel
pqFileDialogModel::pqFileDialogModel(pqServer* _server, QObject* Parent)
  : base(Parent)
  , Implementation(new pqImplementation(_server))
{
}

pqFileDialogModel::~pqFileDialogModel()
{
  delete this->Implementation;
}

void pqFileDialogModel::setFileItemFlags(const Qt::ItemFlags& flags)
{
  this->Implementation->FileItemFlags = flags;
}

void pqFileDialogModel::setDirectoryItemFlags(const Qt::ItemFlags& flags)
{
  this->Implementation->DirItemFlags = flags;
}

bool pqFileDialogModel::isShowingDetailedInfo()
{
  return this->Implementation->isShowingDetailedInformation();
}

void pqFileDialogModel::setShowDetailedInfo(bool show)
{
  this->Implementation->setShowDetailedInformation(show);
  this->setCurrentPath(this->getCurrentPath());
}

bool pqFileDialogModel::isGroupingFiles()
{
  return this->Implementation->isGroupingFiles();
}

void pqFileDialogModel::setGroupFiles(bool group)
{
  this->Implementation->setGroupFiles(group);
  this->setCurrentPath(this->getCurrentPath());
}

pqServer* pqFileDialogModel::server() const
{
  return this->Implementation->getServer();
}

void pqFileDialogModel::setCurrentPath(const QString& path)
{
  this->beginResetModel();
  QString cPath = this->Implementation->cleanPath(path);
  vtkPVFileInformation* info;
  info = this->Implementation->GetData(true, cPath, false);
  this->Implementation->Update(cPath, info);
  this->endResetModel();
}

QString pqFileDialogModel::getCurrentPath()
{
  return this->Implementation->CurrentPath;
}

QString pqFileDialogModel::absoluteFilePath(const QString& path)
{
  if (path.isEmpty())
  {
    return QString();
  }

  vtkPVFileInformation* info;
  info = this->Implementation->GetData(false, path, false);
  return this->Implementation->cleanPath(QString::fromUtf8(info->GetFullPath()));
}

QStringList pqFileDialogModel::getFilePaths(const QModelIndex& localIndex)
{
  if (localIndex.model() == this)
  {
    return this->Implementation->getFilePaths(localIndex);
  }
  return QStringList();
}

bool pqFileDialogModel::isHidden(const QModelIndex& localIndex)
{
  if (localIndex.model() == this)
  {
    return this->Implementation->isHidden(localIndex);
  }
  return false;
}

bool pqFileDialogModel::isDir(const QModelIndex& localIndex) const
{
  if (localIndex.model() == this)
  {
    return this->Implementation->isDir(localIndex);
  }

  return false;
}

bool pqFileDialogModel::fileExists(const QString& file, QString& fullpath)
{
  QString filePath = this->Implementation->cleanPath(file);
  vtkPVFileInformation* info;
  info = this->Implementation->GetData(false, filePath, false);

  // try again for shortcut
  if (info->GetType() != vtkPVFileInformation::SINGLE_FILE)
  {
    info = this->Implementation->GetData(false, filePath + ".lnk", false);
  }

  if (info->GetType() == vtkPVFileInformation::SINGLE_FILE)
  {
    fullpath = info->GetFullPath();
    return true;
  }
  return false;
}

int pqFileDialogModel::fileType(const QString& file)
{
  QString filePath = this->Implementation->cleanPath(file);
  vtkPVFileInformation* info;
  info = this->Implementation->GetData(false, filePath, false);
  return info->GetType();
}

bool pqFileDialogModel::mkdir(const QString& dirName)
{
  QString path;
  QString dirPath = this->absoluteFilePath(dirName);
  if (this->dirExists(dirPath, path))
  {
    return false;
  }

  bool ret = false;

  if (this->Implementation->isRemote())
  {
    vtkSMDirectoryProxy* dirProxy = vtkSMDirectoryProxy::SafeDownCast(
      this->Implementation->getServer()->proxyManager()->NewProxy("misc", "Directory"));
    dirProxy->SetLocation(vtkPVSession::SERVERS);
    ret = dirProxy->MakeDirectory(dirPath.toUtf8().data());
    dirProxy->Delete();
  }
  else
  {
    // File system is local.
    ret = (vtkDirectory::MakeDirectory(dirPath.toUtf8().data()) != 0);
  }

  this->beginResetModel();
  QString cPath = this->Implementation->cleanPath(this->getCurrentPath());
  vtkPVFileInformation* info;
  info = this->Implementation->GetData(true, cPath, false);
  this->Implementation->Update(cPath, info);
  this->endResetModel();

  return ret;
}

bool pqFileDialogModel::rmdir(const QString& dirName)
{
  QString path;
  QString dirPath = this->absoluteFilePath(dirName);
  if (!this->dirExists(dirPath, path))
  {
    return false;
  }

  bool ret = false;

  if (this->Implementation->isRemote())
  {
    vtkSMDirectoryProxy* dirProxy = vtkSMDirectoryProxy::SafeDownCast(
      this->Implementation->getServer()->proxyManager()->NewProxy("misc", "Directory"));
    dirProxy->SetLocation(vtkPVSession::SERVERS);
    ret = dirProxy->DeleteDirectory(dirPath.toUtf8().data());
    dirProxy->Delete();
  }
  else
  {
    // File system is local.
    ret = (vtkDirectory::DeleteDirectory(dirPath.toUtf8().data()) != 0);
  }

  this->beginResetModel();
  QString cPath = this->Implementation->cleanPath(this->getCurrentPath());
  vtkPVFileInformation* info;
  info = this->Implementation->GetData(true, cPath, false);
  this->Implementation->Update(cPath, info);
  this->endResetModel();

  return ret;
}

bool pqFileDialogModel::rename(const QString& oldname, const QString& newname)
{
  QString oldPath = this->absoluteFilePath(oldname);
  QString newPath = this->absoluteFilePath(newname);

  if (oldPath == newPath)
  {
    return true;
  }

  vtkPVFileInformation* info;
  info = this->Implementation->GetData(false, oldPath, false);

  int oldType = info->GetType();

  if (oldType != vtkPVFileInformation::SINGLE_FILE && !vtkPVFileInformation::IsDirectory(oldType))
  {
    return false;
  }

  // don't replace file/dir
  info = this->Implementation->GetData(false, newPath, false);
  if (info->GetType() == oldType)
  {
    QString message = tr("Cannot rename to %1, which already exists");
    message = message.arg(newname);
    QMessageBox::warning(nullptr, tr("Error renaming"), message);
    return false;
  }

  bool ret = false;

  if (this->Implementation->isRemote())
  {
    vtkSMDirectoryProxy* dirProxy = vtkSMDirectoryProxy::SafeDownCast(
      this->Implementation->getServer()->proxyManager()->NewProxy("misc", "Directory"));
    dirProxy->SetLocation(vtkPVSession::SERVERS);
    ret = dirProxy->Rename(oldPath.toUtf8().data(), newPath.toUtf8().data());
    dirProxy->Delete();
  }
  else
  {
    ret = (vtkDirectory::Rename(oldPath.toUtf8().data(), newPath.toUtf8().data()) != 0);
  }

  this->beginResetModel();
  QString cPath = this->Implementation->cleanPath(this->getCurrentPath());
  info = this->Implementation->GetData(true, cPath, false);
  this->Implementation->Update(cPath, info);
  this->endResetModel();

  return ret;
}

bool pqFileDialogModel::dirExists(const QString& path, QString& fullpath)
{
  QString dir = this->Implementation->cleanPath(path);
  vtkPVFileInformation* info;
  info = this->Implementation->GetData(false, dir, false);

  // try again for shortcuts
  if (!vtkPVFileInformation::IsDirectory(info->GetType()))
  {
    info = this->Implementation->GetData(false, dir + ".lnk", false);
  }

  if (vtkPVFileInformation::IsDirectory(info->GetType()))
  {
    fullpath = info->GetFullPath();
    return true;
  }
  return false;
}

bool pqFileDialogModel::dirIsEmpty(const QString& path, QString& fullpath)
{
  if (!this->dirExists(path, fullpath))
  {
    return false;
  }

  vtkPVFileInformation* info;
  info = this->Implementation->GetData(false, fullpath, false);
  info->FetchDirectoryListing();
  if (info->GetContents()->GetNumberOfItems() == 0)
  {
    return true;
  }
  return false;
}

QChar pqFileDialogModel::separator() const
{
  return this->Implementation->Separator;
}

int pqFileDialogModel::columnCount(const QModelIndex& idx) const
{
  const pqFileDialogModelFileInfo* file = this->Implementation->infoForIndex(idx);
  if (!file)
  {
    // should never get here anyway
    return 4;
  }
  return file->group().size() + 4;
}

QVariant pqFileDialogModel::data(const QModelIndex& idx, int role) const
{

  const pqFileDialogModelFileInfo* file;

  if (idx.column() <= 3)
  {
    file = this->Implementation->infoForIndex(idx);
  }
  else
  {
    file = this->Implementation->infoForIndex(idx.parent());
  }

  if (!file)
  {
    // should never get here anyway
    return QVariant();
  }

  int grSize = file->group().size();
  if (role == Qt::DisplayRole || role == Qt::EditRole)
  {
    if (grSize == 0)
    {
      switch (idx.column())
      {
        case 0:
          return file->label();
          break;
        case 1:
          return file->extensionTypeString();
          break;
        case 2:
          return file->sizeString();
          break;
        case 3:
          return file->modificationTimeString();
          break;
        default:
          return QVariant();
          break;
      }
    }
    else if (grSize > 0)
    {
      switch (idx.column())
      {
        case 0:
          return file->label();
          break;
        case 1:
          return tr("Group");
          break;
        case 2:
          return QVariant();
          break;
        case 3:
          return QVariant();
          break;
        default:
          return file->group().at(idx.column() - 4).label();
          break;
      }
    }
  }
  else if (role == Qt::UserRole)
  {
    if (grSize == 0)
    {
      switch (idx.column())
      {
        case 0:
          return file->filePath();
          break;
        case 1:
          return static_cast<int>(file->type());
          break;
        case 2:
          return file->size();
          break;
        case 3:
          return file->modificationTime();
          break;
        default:
          return QVariant();
          break;
      }
    }
    else if (grSize > 0)
    {
      switch (idx.column())
      {
        case 0:
          return file->filePath();
          break;
        case 1:
          return static_cast<int>(file->type());
          break;
        case 2:
          return QVariant();
          break;
        case 3:
          return QVariant();
          break;
        default:
          return file->group().at(idx.column() - 4).filePath();
          break;
      }
    }
  }
  else if (role == Qt::DecorationRole && idx.column() == 0)
  {
    return Icons()->icon(file->type());
  }

  return QVariant();
}

QModelIndex pqFileDialogModel::index(int row, int column, const QModelIndex& p) const
{
  if (!p.isValid())
  {
    return this->createIndex(row, column);
  }
  if (p.row() >= 0 && p.row() < this->Implementation->FileList.size() &&
    nullptr == p.internalPointer())
  {
    pqFileDialogModelFileInfo* fi = &this->Implementation->FileList[p.row()];
    return this->createIndex(row, column, fi);
  }

  return QModelIndex();
}

QModelIndex pqFileDialogModel::parent(const QModelIndex& idx) const
{
  if (!idx.isValid() || !idx.internalPointer())
  {
    return QModelIndex();
  }

  const pqFileDialogModelFileInfo* ptr =
    reinterpret_cast<pqFileDialogModelFileInfo*>(idx.internalPointer());
  int row = ptr - &this->Implementation->FileList.first();
  return this->createIndex(row, idx.column());
}

int pqFileDialogModel::rowCount(const QModelIndex& idx) const
{
  if (!idx.isValid())
  {
    return this->Implementation->FileList.size();
  }

  if (nullptr == idx.internalPointer() && idx.row() >= 0 &&
    idx.row() < this->Implementation->FileList.size())
  {
    return this->Implementation->FileList[idx.row()].group().size();
  }

  return 0;
}

bool pqFileDialogModel::hasChildren(const QModelIndex& idx) const
{
  if (!idx.isValid())
    return true;

  if (nullptr == idx.internalPointer() && idx.row() >= 0 &&
    idx.row() < this->Implementation->FileList.size())
  {
    return this->Implementation->FileList[idx.row()].isGroup();
  }

  return false;
}

QVariant pqFileDialogModel::headerData(int section, Qt::Orientation, int role) const
{
  switch (role)
  {
    case Qt::DisplayRole:
      switch (section)
      {
        case 0:
          return tr("Filename");
          break;
        case 1:
          return tr("Type");
          break;
        case 2:
          return tr("Size");
          break;
        case 3:
          return tr("Date Modified");
          break;
      }
  }

  return QVariant();
}

bool pqFileDialogModel::setData(const QModelIndex& idx, const QVariant& value, int role)
{
  if (role != Qt::DisplayRole && role != Qt::EditRole)
  {
    return false;
  }

  const pqFileDialogModelFileInfo* file = this->Implementation->infoForIndex(idx);

  if (!file)
  {
    return false;
  }

  QString name = value.toString();
  return this->rename(file->filePath(), name);
}

Qt::ItemFlags pqFileDialogModel::flags(const QModelIndex& idx) const
{
  Qt::ItemFlags ret =
    this->isDir(idx) ? this->Implementation->DirItemFlags : this->Implementation->FileItemFlags;
  const pqFileDialogModelFileInfo* file = this->Implementation->infoForIndex(idx);
  if (file && !file->isGroup())
  {
    ret |= Qt::ItemIsEditable;
  }
  return ret;
}
