

#include "filebrowsermodel.h"
#include "tutil.h"
#include "tsystem.h"
#include "tconvert.h"
#include "tenv.h"
#include "toonz/tproject.h"
#include "toonz/toonzscene.h"
#include "toonzqt/gutil.h"

#include "filebrowser.h"
#include "history.h"
#include "iocommand.h"

#include <QFileInfo>
#include <QDir>

#ifdef _WIN32
#include <shlobj.h>
#include <winnetwk.h>
#endif
#ifdef MACOSX
#include <Cocoa/Cocoa.h>
#endif

#ifdef LINETEST
#include "tnzcamera.h"
#endif

namespace {
TFilePath getMyDocumentsPath() {
#ifdef _WIN32
  WCHAR szPath[MAX_PATH];
  if (SHGetSpecialFolderPath(NULL, szPath, CSIDL_PERSONAL, 0)) {
    return TFilePath(szPath);
  }
  return TFilePath();
#elif defined MACOSX
  NSArray *foundref = NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask, YES);
  if (!foundref) return TFilePath();
  int c = [foundref count];
  assert(c == 1);
  NSString *documentsDirectory = [foundref objectAtIndex:0];
  return TFilePath((const char *)[documentsDirectory
      cStringUsingEncoding:NSASCIIStringEncoding]);
#else
  return TFilePath();
#endif
}

// Desktop Path
TFilePath getDesktopPath() {
#ifdef _WIN32
  WCHAR szPath[MAX_PATH];
  if (SHGetSpecialFolderPath(NULL, szPath, CSIDL_DESKTOP, 0)) {
    return TFilePath(szPath);
  }
  return TFilePath();
#elif defined MACOSX
  NSArray *foundref = NSSearchPathForDirectoriesInDomains(
      NSDesktopDirectory, NSUserDomainMask, YES);
  if (!foundref) return TFilePath();
  int c = [foundref count];
  assert(c == 1);
  NSString *desktopDirectory = [foundref objectAtIndex:0];
  return TFilePath((const char *)[desktopDirectory
      cStringUsingEncoding:NSASCIIStringEncoding]);
#else
  return TFilePath();
#endif
}
}

//=============================================================================
//
// DvDirModelNode
//
//-----------------------------------------------------------------------------

DvDirModelNode::DvDirModelNode(DvDirModelNode *parent, std::wstring name)
    : m_parent(parent)
    , m_name(name)
    , m_oldName()
    , m_row(-1)
    , m_childrenValid(false)
    , m_renameEnabled(false)
    , m_nodeType("") {}

//-----------------------------------------------------------------------------

DvDirModelNode::~DvDirModelNode() { clearPointerContainer(m_children); }

//-----------------------------------------------------------------------------

void DvDirModelNode::removeChildren(int row, int count) {
  assert(0 <= row && row + count <= (int)m_children.size());
  int i;
  for (i = 0; i < count; i++) delete m_children[row + i];
  m_children.erase(m_children.begin() + row, m_children.begin() + row + count);
  for (i = 0; i < (int)m_children.size(); i++) m_children[i]->setRow(i);
}

//-----------------------------------------------------------------------------

void DvDirModelNode::addChild(DvDirModelNode *child) {
  child->setRow(m_children.size());
  m_children.push_back(child);
}

//-----------------------------------------------------------------------------

int DvDirModelNode::getChildCount() {
  if (!m_childrenValid) refreshChildren();
  return (int)m_children.size();
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModelNode::getChild(int index) {
  if (!m_childrenValid) refreshChildren();
  assert(0 <= index && index < getChildCount());
  return m_children[index];
}

//-----------------------------------------------------------------------------

bool DvDirModelNode::hasChildren() {
  if (m_childrenValid)
    return getChildCount() > 0;
  else
    return true;
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelNode::getPixmap(bool isOpen) const { return QPixmap(); }

//-----------------------------------------------------------------------------

void DvDirModelNode::setTemporaryName(const std::wstring &newName) {
  m_oldName = getName();
  m_name    = newName;
}

//-----------------------------------------------------------------------------

void DvDirModelNode::restoreName() {
  if (m_oldName != L"") {
    m_name    = m_oldName;
    m_oldName = L"";
  }
}

//=============================================================================
//
// DvDirModelFileFolderNode [File folder]
//
//-----------------------------------------------------------------------------

DvDirModelFileFolderNode::DvDirModelFileFolderNode(DvDirModelNode *parent,
                                                   std::wstring name,
                                                   const TFilePath &path)
    : DvDirModelNode(parent, name)
    , m_path(path)
    , m_isProjectFolder(false)
    , m_existsChecked(false)
    , m_exists(true)
    , m_hasChildren(false)
    , m_peeks(true) {
  m_nodeType = "FileFolder";
}

//-----------------------------------------------------------------------------

DvDirModelFileFolderNode::DvDirModelFileFolderNode(DvDirModelNode *parent,
                                                   const TFilePath &path)
    : DvDirModelNode(parent, path.withoutParentDir().getWideString())
    , m_path(path)
    , m_isProjectFolder(false)
    , m_existsChecked(false)
    , m_exists(true)
    , m_hasChildren(false)
    , m_peeks(true) {
  m_nodeType = "FileFolder";
}

//-----------------------------------------------------------------------------

bool DvDirModelFileFolderNode::exists() {
  return m_existsChecked ? m_exists : m_peeks
             ? m_existsChecked = true,
               m_exists        = TFileStatus(m_path).doesExist() : true;
}

//-----------------------------------------------------------------------------

void DvDirModelFileFolderNode::visualizeContent(FileBrowser *browser) {
  browser->setFolder(m_path);
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModelFileFolderNode::makeChild(std::wstring name) {
  return createNode(this, m_path + name);
}

//-----------------------------------------------------------------------------

DvDirModelFileFolderNode *DvDirModelFileFolderNode::createNode(
    DvDirModelNode *parent, const TFilePath &path) {
  DvDirModelFileFolderNode *node;
  // check the project nodes under the Poject Root Node
  if (QString::fromStdWString(parent->getName()).startsWith("Project root") &&
      TProjectManager::instance()->isProject(path))
    node = new DvDirModelProjectNode(parent, path);
  else {
    node = new DvDirModelFileFolderNode(parent, path);
    if (path.getName().find("_files") == std::string::npos)
      node->enableRename(true);
  }
  return node;
}

//-----------------------------------------------------------------------------

bool DvDirModelFileFolderNode::setName(std::wstring newName) {
  if (isSpaceString(QString::fromStdWString(newName))) return true;

  TFilePath srcPath = getPath();
  TFilePath dstPath = getPath().getParentDir() + newName;
  if (srcPath == dstPath) return false;
  try {
    TSystem::renameFile(dstPath, srcPath);
  } catch (...) {
    return false;
  }
  m_path = dstPath;
  m_name = newName;
  return true;
}

//-----------------------------------------------------------------------------

bool DvDirModelFileFolderNode::hasChildren() {
  if (m_childrenValid) return m_hasChildren;

  if (m_peeks) {
    // Using QDir directly rather than
    // DvDirModelFileFolderNode::refreshChildren() due to
    // performance issues
    QDir dir(QString::fromStdWString(m_path.getWideString()));
    dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);

    return (dir.count() > 0);
  } else
    return true;  // Not peeking nodes allow users to actively scan for
                  // sub-folders
}

//------------------------------------------------------------------------------

void DvDirModelFileFolderNode::refreshChildren() {
  m_childrenValid = true;

  std::vector<std::wstring> names;
  getChildrenNames(names);

  std::vector<DvDirModelNode *> oldChildren;
  oldChildren.swap(m_children);

  std::vector<DvDirModelNode *>::iterator j;
  int i;
  for (i = 0; i < (int)names.size(); i++) {
    std::wstring name = names[i];
    for (j = oldChildren.begin();
         j != oldChildren.end() && (*j)->getName() != name; ++j) {
    }
    DvDirModelNode *child = 0;
    if (j != oldChildren.end()) {
      child = *j;
      oldChildren.erase(j);
    } else {
      child = makeChild(name);
      if (DvDirModelFileFolderNode *folderNode =
              dynamic_cast<DvDirModelFileFolderNode *>(child))
        folderNode->setPeeking(m_peeks);
    }

    if (!child) continue;

    addChild(child);
  }
  for (j = oldChildren.begin(); j != oldChildren.end(); ++j) {
    DvDirModelNode *child = *j;
    if (!!child && child->hasChildren())
      child->removeChildren(0, child->getChildCount());

    delete *j;
  }

  m_hasChildren = (m_children.size() > 0);
}

//-----------------------------------------------------------------------------

void DvDirModelFileFolderNode::getChildrenNames(
    std::vector<std::wstring> &names) const {
  TFileStatus folderPathStatus(m_path);
  if (folderPathStatus.isLink()) return;

  QStringList entries;
  if (folderPathStatus.isDirectory()) {
    QDir dir(toQString(m_path));

    entries = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot,
                            QDir::Name | QDir::LocaleAware);
  }

  int e, eCount = entries.size();
  for (e = 0; e != eCount; ++e) names.push_back(entries[e].toStdWString());
}

//-----------------------------------------------------------------------------

int DvDirModelFileFolderNode::rowByName(const std::wstring &name) const {
  std::vector<std::wstring> names;
  getChildrenNames(names);
  std::vector<std::wstring>::iterator it =
      std::find(names.begin(), names.end(), name);
  if (it == names.end())
    return -1;
  else
    return std::distance(names.begin(), it);
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModelFileFolderNode::getNodeByPath(const TFilePath &path) {
  if (path == m_path) return this;
  if (!m_path.isAncestorOf(path)) return 0;
  int i, n = getChildCount();
  for (i = 0; i < n; i++) {
    DvDirModelNode *node = getChild(i)->getNodeByPath(path);
    if (node) return node;
  }
  return 0;
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelFileFolderNode::getPixmap(bool isOpen) const {
  static QPixmap openFolderPixmap(":Resources/browser_folder_open.png");
  static QPixmap closeFolderPixmap(":Resources/browser_folder_close.png");
  return isOpen ? openFolderPixmap : closeFolderPixmap;
}

//=============================================================================
//
// DvDirModelSceneFolderNode
//
//-----------------------------------------------------------------------------

DvDirModelSceneFolderNode::DvDirModelSceneFolderNode(DvDirModelNode *parent,
                                                     std::wstring name,
                                                     const TFilePath &scenePath)
    : DvDirModelFileFolderNode(parent, name, scenePath) {}

//-----------------------------------------------------------------------------

DvDirModelSceneFolderNode::DvDirModelSceneFolderNode(DvDirModelNode *parent,
                                                     const TFilePath &path)
    : DvDirModelFileFolderNode(parent, path.withoutParentDir().getWideString(),
                               path) {
  try {
    ToonzScene scene;
    scene.setScenePath(path);
    TProjectP project = TProjectManager::instance()->loadSceneProject(path);
    if (!project) return;
    scene.setProject(project.getPointer());
    for (int i = 0; i < project->getFolderCount(); i++) {
      std::string folderName = project->getFolderName(i);
      TFilePath folderPath   = project->getFolder(i);
      if (folderPath.isAbsolute() && !project->isConstantFolder(i)) {
        std::wstring alias         = L"+" + ::to_wstring(folderName);
        TFilePath folderActualPath = scene.decodeFilePath(TFilePath(alias));
        m_folders[alias]           = folderActualPath;
      }
    }
  } catch (...) {
  }
}

//-----------------------------------------------------------------------------

DvDirModelSceneFolderNode::~DvDirModelSceneFolderNode() {}

//-----------------------------------------------------------------------------

bool DvDirModelSceneFolderNode::setName(std::wstring newName) {
  m_name = newName;
  return true;
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelSceneFolderNode::getPixmap(bool isOpen) const {
  static QPixmap openFolderPixmap(":Resources/browser_scene_open.png");
  static QPixmap closeFolderPixmap(":Resources/browser_scene_close.png");
  return isOpen ? openFolderPixmap : closeFolderPixmap;
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModelSceneFolderNode::makeChild(std::wstring name) {
  TFilePath actualFolderPath = m_folders[name];
  DvDirModelFileFolderNode *node =
      new DvDirModelFileFolderNode(this, name, actualFolderPath);
  node->enableRename(false);
  return node;
}

//-----------------------------------------------------------------------------

void DvDirModelSceneFolderNode::getChildrenNames(
    std::vector<std::wstring> &names) const {
  std::map<std::wstring, TFilePath>::const_iterator it;
  for (it = m_folders.begin(); it != m_folders.end(); ++it)
    names.push_back(it->first);
}

//-----------------------------------------------------------------------------

void DvDirModelSceneFolderNode::refreshChildren() {
  m_childrenValid = true;
  std::vector<std::wstring> names;
  getChildrenNames(names);

  std::vector<DvDirModelNode *> oldChildren;
  oldChildren.swap(m_children);

  std::vector<DvDirModelNode *>::iterator j;
  int i;
  for (i = 0; i < (int)names.size(); i++) {
    std::wstring name = names[i];
    for (j = oldChildren.begin();
         j != oldChildren.end() && (*j)->getName() != name; ++j) {
    }
    DvDirModelNode *child = 0;
    if (j != oldChildren.end()) {
      child = *j;
      oldChildren.erase(j);
    } else {
      child = makeChild(name);
    }
    addChild(child);
  }
  for (j = oldChildren.begin(); j != oldChildren.end(); ++j) {
    DvDirModelNode *child = *j;
    if (!!child && child->hasChildren())
      child->removeChildren(0, child->getChildCount());
    delete *j;
  }
}

//-----------------------------------------------------------------------------

DvDirModelFileFolderNode *DvDirModelSceneFolderNode::createNode(
    DvDirModelNode *parent, const TFilePath &path) {
  DvDirModelFileFolderNode *node = new DvDirModelFileFolderNode(parent, path);
  if (path.getName().find("_files") == std::string::npos)
    node->enableRename(true);
  return node;
}

//-----------------------------------------------------------------------------

int DvDirModelSceneFolderNode::rowByName(const std::wstring &name) {
  std::vector<std::wstring> names;
  getChildrenNames(names);
  std::vector<std::wstring>::iterator it =
      std::find(names.begin(), names.end(), name);
  if (it == names.end())
    return -1;
  else
    return std::distance(names.begin(), it);
}

//=============================================================================
//
// DvDirModelSpecialFileFolderNode [Special File Folder]
//
//-----------------------------------------------------------------------------

DvDirModelSpecialFileFolderNode::DvDirModelSpecialFileFolderNode(
    DvDirModelNode *parent, std::wstring name, const TFilePath &path)
    : DvDirModelFileFolderNode(parent, name, path) {}

//-----------------------------------------------------------------------------

QPixmap DvDirModelSpecialFileFolderNode::getPixmap(bool isOpen) const {
  return m_pixmap;
}

//-----------------------------------------------------------------------------

void DvDirModelSpecialFileFolderNode::setPixmap(const QPixmap &pixmap) {
  m_pixmap = pixmap;
}

//=============================================================================
//
// DvDirVersionControlNode [Special File Folder]
//
//-----------------------------------------------------------------------------

DvDirVersionControlNode::DvDirVersionControlNode(DvDirModelNode *parent,
                                                 std::wstring name,
                                                 const TFilePath &path)
    : DvDirModelFileFolderNode(parent, name, path)
    , m_isSynched(false)
    , m_isUnversioned(false)
    , m_oldName() {
  this->setExists(TFileStatus(path).doesExist());
  setIsUnderVersionControl(
      VersionControl::instance()->isFolderUnderVersionControl(toQString(path)));
}

//-----------------------------------------------------------------------------

QList<TFilePath> DvDirVersionControlNode::getMissingFiles() const {
  QList<TFilePath> list;
  QMap<QString, SVNStatus>::const_iterator i = m_statusMap.constBegin();
  while (i != m_statusMap.constEnd()) {
    SVNStatus s = i.value();
    if (s.m_item == "missing" ||
        s.m_item == "none" && s.m_repoStatus == "added") {
      TFilePath path(getPath() + TFilePath(s.m_path.toStdWString()));
      std::string dots = path.getDots();
      if (dots != "") {
        if (dots == "..") path = path.getParentDir() + path.getLevelNameW();
        if (!list.contains(path)) list.append(path);
      }
    }
    i++;
  }
  return list;
}

//-----------------------------------------------------------------------------

QStringList DvDirVersionControlNode::getMissingFiles(
    const QRegExp &filter) const {
  QStringList list;
  QMap<QString, SVNStatus>::const_iterator i = m_statusMap.constBegin();
  for (; i != m_statusMap.constEnd(); i++) {
    SVNStatus s = i.value();
    if (s.m_item == "missing" ||
        s.m_item == "none" && s.m_repoStatus == "added") {
      TFilePath path(s.m_path.toStdWString());
      if (!filter.exactMatch(
              QString::fromStdWString(path.withoutParentDir().getWideString())))
        continue;
      std::string dots = path.getDots();
      if (dots != "") list.append(toQString(path));
    }
  }
  return list;
}

//-----------------------------------------------------------------------------

QList<TFilePath> DvDirVersionControlNode::getMissingFolders() const {
  QList<TFilePath> list;
  QMap<QString, SVNStatus>::const_iterator i = m_statusMap.constBegin();
  while (i != m_statusMap.constEnd()) {
    SVNStatus s = i.value();
    if (s.m_item == "missing" ||
        (s.m_item == "none" && s.m_repoStatus == "added")) {
      TFilePath path(getPath() + TFilePath(s.m_path.toStdWString()));
      if (path.getDots() == "" || path.getType() == "tnz") {
        QString currentFolderName =
            QString::fromStdWString(getPath().getWideName());
        if (currentFolderName != s.m_path) {
          if (!s.m_path.contains("\\"))
            list.append(path);
          else {
            QString newPath = s.m_path.replace(currentFolderName + "\\", "");
            if (!newPath.contains("\\"))
              list.append(getPath() + TFilePath(newPath.toStdWString()));
          }
        }
      }
    }
    i++;
  }
  return list;
}

//-----------------------------------------------------------------------------

void DvDirVersionControlNode::getChildrenNames(
    std::vector<std::wstring> &names) const {
  DvDirModelFileFolderNode::getChildrenNames(names);

  QList<TFilePath> missingFolders = getMissingFolders();
  int count                       = missingFolders.size();

  for (int i = 0; i < count; i++) {
    std::wstring name = missingFolders.at(i).getWideName();
    if (find(names.begin(), names.end(), name) == names.end())
      names.push_back(name);
  }
}

//-----------------------------------------------------------------------------

void DvDirVersionControlNode::setIsUnderVersionControl(bool value) {
  // Set the flags and enable/disable renaming accordling
  m_isUnderVersionControl = value;
  enableRename(!value);
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirVersionControlNode::makeChild(std::wstring name) {
  if (TProjectManager::instance()->isProject(getPath() + name))
    return new DvDirVersionControlProjectNode(this, name, getPath() + name);
  return new DvDirVersionControlNode(this, name, getPath() + name);
}

//-----------------------------------------------------------------------------

QPixmap DvDirVersionControlNode::getPixmap(bool isOpen) const {
  static QPixmap openFolderPixmap(":Resources/browser_folder_open.png");
  static QPixmap closeFolderPixmap(":Resources/browser_folder_close.png");
  static QPixmap openMissingPixmap(":Resources/vcfolder_mis_open.png");
  static QPixmap closeMissingPixmap(":Resources/vcfolder_mis_close.png");
  static QPixmap openSceneFolderPixmap(":Resources/browser_scene_open.png");
  static QPixmap closeSceneFolderPixmap(":Resources/browser_scene_close.png");

  if (TFileStatus(getPath()).doesExist()) {
    if (getPath().getType() == "tnz")
      return isOpen ? openSceneFolderPixmap : closeSceneFolderPixmap;
    else
      return isOpen ? openFolderPixmap : closeFolderPixmap;
  } else
    return isOpen ? openMissingPixmap : closeMissingPixmap;
}

//-----------------------------------------------------------------------------

void DvDirVersionControlNode::insertVersionControlStatus(
    const QString &fileName, SVNStatus status) {
  m_statusMap.insert(fileName, status);
}

//-----------------------------------------------------------------------------

SVNStatus DvDirVersionControlNode::getVersionControlStatus(
    const QString &fileName) {
  if (m_statusMap.contains(fileName)) return m_statusMap.value(fileName);

  SVNStatus s         = SVNStatus();
  s.m_isLocked        = false;
  s.m_isPartialEdited = false;
  s.m_isPartialLocked = false;
  return s;
}

//-----------------------------------------------------------------------------

QStringList DvDirVersionControlNode::refreshVersionControl(
    const QList<SVNStatus> &status) {
  TFilePath nodePath = getPath();
  if (nodePath.isEmpty()) return QStringList();

  int listSize   = status.size();
  bool isSynched = true;

  QStringList checkPartialLockList;

  for (int i = 0; i < listSize; i++) {
    SVNStatus s = status.at(i);

    // It is not needed to check and cache SVNStatus for files that are
    // "outside" the node "scope"
    if (s.m_path == "." || s.m_path == ".." || s.m_path.split("\\").size() > 2)
      continue;

    QString fileName = s.m_path;
    if (nodePath.getType() == "") insertVersionControlStatus(fileName, s);

    // Update also the status of the "scene" child folders
    TFilePath path(fileName.toStdWString());
    if (path.getType() == "tnz") {
      DvDirVersionControlNode *childVcNode =
          dynamic_cast<DvDirVersionControlNode *>(
              getNodeByPath(nodePath + path));
      if (childVcNode) {
        childVcNode->setIsUnderVersionControl(true);
        if (s.m_repoStatus == "modified" || s.m_item == "modified" ||
            s.m_item == "missing" || s.m_item == "none")
          childVcNode->setIsSynched(false);
        else if (s.m_item == "unversioned")
          childVcNode->setIsUnversioned(true);
        else
          childVcNode->setIsSynched(true);
      }
    }
    // If a folder is unversioned, I set its status and even the unversioned
    // status for its children
    else if (path.getType() == "" && s.m_item == "unversioned") {
      DvDirVersionControlNode *childVcNode =
          dynamic_cast<DvDirVersionControlNode *>(
              getNodeByPath(nodePath + path));
      if (childVcNode) {
        childVcNode->setIsUnversioned(true);
        int childCount = childVcNode->getChildCount();
        for (int i = 0; i < childCount; i++) {
          DvDirVersionControlNode *subChildNode =
              dynamic_cast<DvDirVersionControlNode *>(childVcNode->getChild(i));
          if (subChildNode) subChildNode->setIsUnversioned(true);
        }
      }
    }

    // Check also the 'partial-lock' property
    std::string type = TFilePath(s.m_path.toStdWString()).getType();
    if (type == "tlv" || type == "pli") {
      if (s.m_item != "unversioned" && s.m_item != "missing" &&
          s.m_repoStatus != "added")
        checkPartialLockList.append(s.m_path);
    }

    if (s.m_repoStatus == "modified" || s.m_item == "modified" ||
        s.m_item == "unversioned" || s.m_item == "missing" ||
        s.m_item == "none")
      isSynched = false;
  }

  setIsSynched(isSynched);
  return checkPartialLockList;
}

//-----------------------------------------------------------------------------

DvDirVersionControlRootNode *
DvDirVersionControlNode::getVersionControlRootNode() {
  DvDirModelNode *parent = getParent();
  if (parent) return parent->getVersionControlRootNode();
  return 0;
}

//=============================================================================
//
// DvDirVersionControlRootNode [Special File Folder]
//
//-----------------------------------------------------------------------------

DvDirVersionControlRootNode::DvDirVersionControlRootNode(DvDirModelNode *parent,
                                                         std::wstring name,
                                                         const TFilePath &path)
    : DvDirVersionControlNode(parent, name, path) {
  setPixmap(QPixmap(":Resources/vcroot.png"));
  setIsUnderVersionControl(true);
}

//-----------------------------------------------------------------------------

void DvDirVersionControlRootNode::refreshChildren() {
  DvDirModelFileFolderNode::refreshChildren();
}

//=============================================================================
// DvDirVersionControlProjectNode
//-----------------------------------------------------------------------------

DvDirVersionControlProjectNode::DvDirVersionControlProjectNode(
    DvDirModelNode *parent, std::wstring name, const TFilePath &path)
    : DvDirVersionControlNode(parent, name, path) {
  m_nodeType = "Project";
  setIsUnderVersionControl(true);
}

//-----------------------------------------------------------------------------

TFilePath DvDirVersionControlProjectNode::getProjectPath() const {
  return TProjectManager::instance()->projectFolderToProjectPath(getPath());
}

//-----------------------------------------------------------------------------

bool DvDirVersionControlProjectNode::isCurrent() const {
  TProjectManager *pm = TProjectManager::instance();
  return pm->getCurrentProjectPath().getParentDir() == getPath();
}

//-----------------------------------------------------------------------------

void DvDirVersionControlProjectNode::makeCurrent() {
  TProjectManager *pm   = TProjectManager::instance();
  TFilePath projectPath = getProjectPath();
  if (!IoCmd::saveSceneIfNeeded(QObject::tr("Change project"))) return;
  pm->setCurrentProjectPath(projectPath);
  IoCmd::newScene();
}

//-----------------------------------------------------------------------------

QPixmap DvDirVersionControlProjectNode::getPixmap(bool isOpen) const {
  static QPixmap openPixmap(":Resources/browser_vcproject_open.png");
  static QPixmap closePixmap(":Resources/browser_vcproject_close.png");
  static QPixmap openMissingPixmap(":Resources/vcfolder_mis_open.png");
  static QPixmap closeMissingPixmap(":Resources/vcfolder_mis_close.png");

  if (TFileStatus(getPath()).doesExist())
    return isOpen ? openPixmap : closePixmap;
  else
    return isOpen ? openMissingPixmap : closeMissingPixmap;
}

//-----------------------------------------------------------------------------

void DvDirVersionControlProjectNode::refreshChildren() {
  DvDirModelFileFolderNode::refreshChildren();
  int i;
  TProjectManager *pm = TProjectManager::instance();
  TProject *project   = new TProject();
  project->load(getProjectPath());
  for (i = 0; i < getChildCount(); i++) {
    DvDirModelFileFolderNode *node =
        dynamic_cast<DvDirModelFileFolderNode *>(getChild(i));
    if (node) {
      int k = project->getFolderIndexFromPath(node->getPath());
      node->setIsProjectFolder(k >= 0);
    }
  }
  delete project;
}

//-----------------------------------------------------------------------------

void DvDirVersionControlProjectNode::getChildrenNames(
    std::vector<std::wstring> &names) const {
  DvDirVersionControlNode::getChildrenNames(names);
  TProjectManager *pm = TProjectManager::instance();
  TProject *project   = new TProject();
  project->load(getProjectPath());
  int i;
  for (i = 0; i < project->getFolderCount(); i++) {
    std::string folderName = project->getFolderName(i);
    TFilePath folderPath   = project->getFolder(i);
    // if(folderPath.isAbsolute() || folderPath.getParentDir() != TFilePath())
    if (folderPath.isAbsolute() && project->isConstantFolder(i)) {
      names.push_back(L"+" + ::to_wstring(folderName));
    }
  }
  delete project;
}

//=============================================================================
//
// DvDirModelProjectNode [Project Folder]
//
//-----------------------------------------------------------------------------

DvDirModelProjectNode::DvDirModelProjectNode(DvDirModelNode *parent,
                                             const TFilePath &path)
    : DvDirModelFileFolderNode(parent, path) {
  m_nodeType = "Project";
}

//-----------------------------------------------------------------------------

TFilePath DvDirModelProjectNode::getProjectPath() const {
  return TProjectManager::instance()->projectFolderToProjectPath(getPath());
}

//-----------------------------------------------------------------------------

bool DvDirModelProjectNode::isCurrent() const {
  TProjectManager *pm = TProjectManager::instance();
  return pm->getCurrentProjectPath().getParentDir() == getPath();
}

//-----------------------------------------------------------------------------

void DvDirModelProjectNode::makeCurrent() {
  TProjectManager *pm   = TProjectManager::instance();
  TFilePath projectPath = getProjectPath();
  if (!IoCmd::saveSceneIfNeeded(QObject::tr("Change project"))) return;

#ifdef LINETEST
  TnzCamera *camera = TnzCamera::instance();
  if (camera->isCameraConnected()) camera->cameraDisconnect();
#endif

  pm->setCurrentProjectPath(projectPath);
  IoCmd::newScene();
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelProjectNode::getPixmap(bool isOpen) const {
  static QPixmap openProjectPixmap(":Resources/browser_project_open.png");
  static QPixmap closeProjectPixmap(":Resources/browser_project_close.png");
  return isOpen ? openProjectPixmap : closeProjectPixmap;
}

//-----------------------------------------------------------------------------

void DvDirModelProjectNode::refreshChildren() {
  DvDirModelFileFolderNode::refreshChildren();
  int i;
  TProjectManager *pm = TProjectManager::instance();
  TProject *project   = new TProject();
  project->load(getProjectPath());
  for (i = 0; i < getChildCount(); i++) {
    DvDirModelFileFolderNode *node =
        dynamic_cast<DvDirModelFileFolderNode *>(getChild(i));
    if (node) {
      int k = project->getFolderIndexFromPath(node->getPath());
      node->setIsProjectFolder(k >= 0);
    }
  }
  delete project;
}

//-----------------------------------------------------------------------------

void DvDirModelProjectNode::getChildrenNames(
    std::vector<std::wstring> &names) const {
  DvDirModelFileFolderNode::getChildrenNames(names);
  TProjectManager *pm = TProjectManager::instance();
  TProject *project   = new TProject();
  project->load(getProjectPath());
  int i;
  for (i = 0; i < project->getFolderCount(); i++) {
    std::string folderName = project->getFolderName(i);
    TFilePath folderPath   = project->getFolder(i);
    // if(folderPath.isAbsolute() || folderPath.getParentDir() != TFilePath())
    if (folderPath.isAbsolute() && project->isConstantFolder(i)) {
      names.push_back(L"+" + ::to_wstring(folderName));
    }
  }
  delete project;
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModelProjectNode::makeChild(std::wstring name) {
  if (name != L"" && name[0] == L'+') {
    TProjectManager *pm = TProjectManager::instance();
    TProject *project   = new TProject();
    project->load(getProjectPath());
    std::string folderName = ::to_string(name.substr(1));
    TFilePath folderPath   = project->getFolder(folderName);
    DvDirModelNode *node = new DvDirModelFileFolderNode(this, name, folderPath);
    delete project;
    return node;
  } else
    return DvDirModelFileFolderNode::makeChild(name);
}

//=============================================================================
//
// DvDirModelDayNode [Day folder]
//
//-----------------------------------------------------------------------------

DvDirModelDayNode::DvDirModelDayNode(DvDirModelNode *parent,
                                     std::wstring dayDateString)
    : DvDirModelNode(parent, dayDateString)
    , m_dayDateString(::to_string(dayDateString)) {
  m_nodeType = "Day";
}

//-----------------------------------------------------------------------------

void DvDirModelDayNode::visualizeContent(FileBrowser *browser) {
  browser->setHistoryDay(m_dayDateString);
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelDayNode::getPixmap(bool isOpen) const {
  static QPixmap openFolderPixmap(":Resources/browser_folder_open.png");
  static QPixmap closeFolderPixmap(":Resources/browser_folder_close.png");
  return isOpen ? openFolderPixmap : closeFolderPixmap;
}

//=============================================================================
//
// DvDirModelHistoryNode [History]
//
//-----------------------------------------------------------------------------

DvDirModelHistoryNode::DvDirModelHistoryNode(DvDirModelNode *parent)
    : DvDirModelNode(parent, L"History") {
  m_nodeType = "History";
}

//-----------------------------------------------------------------------------

void DvDirModelHistoryNode::refreshChildren() {
  m_children.clear();
  m_childrenValid = true;
  History *h      = History::instance();
  for (int i = 0; i < h->getDayCount(); i++) {
    const History::Day *day = h->getDay(i);
    DvDirModelNode *child =
        new DvDirModelDayNode(this, ::to_wstring(day->getDate()));
    addChild(child);
  }
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelHistoryNode::getPixmap(bool isOpen) const {
  static QPixmap pixmap(":Resources/history.png");
  return pixmap;
}

//=============================================================================
//
// DvDirModelMyComputerNode [My Computer]
//
//-----------------------------------------------------------------------------

DvDirModelMyComputerNode::DvDirModelMyComputerNode(DvDirModelNode *parent)
    : DvDirModelNode(parent, L"My Computer") {
  // setIcon(QFileIconProvider().icon(QFileIconProvider::Computer));
  m_nodeType = "MyComputer";
}

//-----------------------------------------------------------------------------

void DvDirModelMyComputerNode::refreshChildren() {
  m_childrenValid = true;
  if (!m_children.empty()) clearPointerContainer(m_children);

  TFilePathSet fps = TSystem::getDisks();

#ifdef MACOSX
  fps.push_back(TFilePath("/Volumes/"));
#endif

  TFilePathSet::iterator it;
  for (it = fps.begin(); it != fps.end(); ++it) {
    DvDirModelNode *child =
        new DvDirModelFileFolderNode(this, it->getWideString(), *it);
    addChild(child);
  }
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelMyComputerNode::getPixmap(bool isOpen) const {
  static QPixmap pixmap(":Resources/my_computer.png");
  return pixmap;
}

//=============================================================================
//
// DvDirModelNetworkNode [Network]
//
//-----------------------------------------------------------------------------

DvDirModelNetworkNode::DvDirModelNetworkNode(DvDirModelNode *parent)
    : DvDirModelNode(parent, L"Network") {
  m_nodeType = "Network";
}

//-----------------------------------------------------------------------------

void DvDirModelNetworkNode::refreshChildren() {
  m_childrenValid = true;

  if (!m_children.empty()) clearPointerContainer(m_children);

#ifdef _WIN32

  // Enumerate network nodes
  HANDLE enumHandle;
  DWORD err =
      WNetOpenEnum(RESOURCE_CONTEXT, RESOURCETYPE_DISK, 0, NULL, &enumHandle);
  assert(err == NO_ERROR);

  DWORD count = -1,
        bufferSize =
            16 << 10;  // Use 16 kB as recommended on Windows references
  LPNETRESOURCE buffer = (LPNETRESOURCE)malloc(bufferSize);

  do {
    // Get the next bunch of network devices
    memset(buffer, 0, bufferSize);
    err = WNetEnumResource(enumHandle, &count, buffer, &bufferSize);

    if (err == NO_ERROR) {
      for (int i = 0; i < count; ++i) {
        // Only list disk-type devices - in any case, the remote (UNC) name
        // should exist
        if (buffer[i].dwType == RESOURCETYPE_DISK && buffer[i].lpRemoteName) {
          std::wstring wstr(buffer[i].lpRemoteName);

          // Build a NOT PEEKING folder node. This is important since network
          // access is SLOW.
          DvDirModelFileFolderNode *child =
              new DvDirModelFileFolderNode(this, wstr, TFilePath(wstr));
          child->setPeeking(false);

          addChild(child);
        }
      }
    } else if (err != ERROR_NO_MORE_ITEMS)
      break;  // Excluding NO_ERROR and ERROR_NO_MORE_ITEMS, quit

  } while (err != ERROR_NO_MORE_ITEMS);

  err = WNetCloseEnum(enumHandle);
  assert(err == NO_ERROR);

  free(buffer);

#endif
}

//-----------------------------------------------------------------------------

QPixmap DvDirModelNetworkNode::getPixmap(bool isOpen) const {
  static QPixmap pixmap(":Resources/network.png");
  return pixmap;
}

//=============================================================================
//
// DvDirModelRootNode [Root]
//
//-----------------------------------------------------------------------------

DvDirModelRootNode::DvDirModelRootNode()
    : DvDirModelNode(0, L"Root")
    , m_myComputerNode(0)
    , m_networkNode(0)
    , m_sandboxProjectNode(0) {
  m_nodeType = "Root";
}

//-----------------------------------------------------------------------------

void DvDirModelRootNode::add(std::wstring name, const TFilePath &path) {
  DvDirModelNode *child = new DvDirModelFileFolderNode(this, name, path);
  child->setRow((int)m_children.size());
  m_children.push_back(child);
}

//-----------------------------------------------------------------------------

void DvDirModelRootNode::refreshChildren() {
  m_childrenValid = true;
  if (m_children.empty()) {
    addChild(m_myComputerNode = new DvDirModelMyComputerNode(this));

#ifdef _WIN32
    addChild(m_networkNode = new DvDirModelNetworkNode(this));
#endif

    DvDirModelSpecialFileFolderNode *child;
    child = new DvDirModelSpecialFileFolderNode(this, L"My Documents",
                                                getMyDocumentsPath());
    child->setPixmap(QPixmap(":Resources/my_documents.png"));
    addChild(child);

    child =
        new DvDirModelSpecialFileFolderNode(this, L"Desktop", getDesktopPath());
    child->setPixmap(QPixmap(":Resources/desktop.png"));
    addChild(child);

    child = new DvDirModelSpecialFileFolderNode(
        this, L"Library", ToonzFolder::getLibraryFolder());
    child->setPixmap(QPixmap(":Resources/library.png"));
    addChild(child);

    addChild(new DvDirModelHistoryNode(this));

    TProjectManager *pm = TProjectManager::instance();
    std::vector<TFilePath> projectRoots;
    pm->getProjectRoots(projectRoots);

    int i;
    for (i = 0; i < (int)projectRoots.size(); i++) {
      TFilePath projectRoot = projectRoots[i];
      std::wstring roothDir = projectRoot.getWideString();
      DvDirModelSpecialFileFolderNode *projectRootNode =
          new DvDirModelSpecialFileFolderNode(
              this, L"Project root (" + roothDir + L")", projectRoot);
      projectRootNode->setPixmap(QPixmap(":Resources/projects.png"));
      m_projectRootNodes.push_back(projectRootNode);
      addChild(projectRootNode);
    }

    TFilePath sandboxProjectPath = pm->getSandboxProjectFolder();
    m_sandboxProjectNode = new DvDirModelProjectNode(this, sandboxProjectPath);
    addChild(m_sandboxProjectNode);

    // SVN Repositories
    QList<SVNRepository> repositories =
        VersionControl::instance()->getRepositories();
    int count = repositories.size();
    for (int i = 0; i < count; i++) {
      SVNRepository repo                = repositories.at(i);
      DvDirVersionControlRootNode *node = new DvDirVersionControlRootNode(
          this, repo.m_name.toStdWString(),
          TFilePath(repo.m_localPath.toStdWString()));
      node->setRepositoryPath(repo.m_repoPath.toStdWString());
      node->setLocalPath(repo.m_localPath.toStdWString());
      node->setUserName(repo.m_username.toStdWString());
      node->setPassword(repo.m_password.toStdWString());
      m_versionControlNodes.push_back(node);
      addChild(node);
    }
  }
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModelRootNode::getNodeByPath(const TFilePath &path) {
  // Check first for version control nodes
  DvDirModelNode *node = 0;
  int i;

  // search in #1 the project folders, #2 sandbox, #3 other folders in file
  // system

  // path could be a project, under some project root
  for (i = 0; i < (int)m_projectRootNodes.size(); i++) {
    node = m_projectRootNodes[i]->getNodeByPath(path);
    if (node) return node;
    // search in the project folders
    for (int j = 0; j < m_projectRootNodes[i]->getChildCount(); j++) {
      DvDirModelProjectNode *projectNode =
          dynamic_cast<DvDirModelProjectNode *>(
              m_projectRootNodes[i]->getChild(j));
      if (projectNode) {
        // for the normal folder in the project folder
        node = projectNode->getNodeByPath(path);
        if (node) return node;

        if (projectNode->isCurrent()) {
          // for the aliases in the project folder ("+drawings" etc.)
          for (int k = 0; k < projectNode->getChildCount(); k++) {
            node = projectNode->getChild(k)->getNodeByPath(path);
            if (node) return node;
          }
        }
      } else  // for the normal folder in the project root
      {
        node = m_projectRootNodes[i]->getChild(j)->getNodeByPath(path);
        if (node) return node;
      }
    }
  }

  // it could be on a repository, under version control
  for (i = 0; i < (int)m_versionControlNodes.size(); i++) {
    node = m_versionControlNodes[i]->getNodeByPath(path);
    if (node) return node;
  }

  // or it could be the sandbox project or in the sandbox project
  if (m_sandboxProjectNode && m_sandboxProjectNode->getPath() == path)
    return m_sandboxProjectNode;
  if (m_sandboxProjectNode) {
    for (i = 0; i < m_sandboxProjectNode->getChildCount(); i++) {
      DvDirModelNode *node =
          m_sandboxProjectNode->getChild(i)->getNodeByPath(path);
      if (node) return node;
    }
  }

  // it could be a regular folder, somewhere in the file system
  if (m_myComputerNode) {
    for (i = 0; i < m_myComputerNode->getChildCount(); i++) {
      DvDirModelNode *node = m_myComputerNode->getChild(i)->getNodeByPath(path);
      if (node) return node;
    }
  }

  // it could be a network folder
  if (m_networkNode) {
    for (i = 0; i < m_networkNode->getChildCount(); i++) {
      DvDirModelNode *node = m_networkNode->getChild(i)->getNodeByPath(path);
      if (node) return node;
    }
  }

  return 0;
}

//=============================================================================
//
// DvDirModel
//
//-----------------------------------------------------------------------------

DvDirModel::DvDirModel() {
  m_root = new DvDirModelRootNode();
  m_root->refreshChildren();
}

//-----------------------------------------------------------------------------

DvDirModel::~DvDirModel() { delete m_root; }

//-----------------------------------------------------------------------------

void DvDirModel::onFolderChanged(const TFilePath &path) { refreshFolder(path); }

//-----------------------------------------------------------------------------

void DvDirModel::refresh(const QModelIndex &index) {
  if (!index.isValid()) return;
  DvDirModelNode *node = getNode(index);
  if (!node) return;
  emit layoutAboutToBeChanged();
  emit beginRemoveRows(index, 0, node->getChildCount());
  node->refreshChildren();
  emit endRemoveRows();
  emit layoutChanged();
}

//-----------------------------------------------------------------------------

void DvDirModel::refreshFolder(const TFilePath &folderPath,
                               const QModelIndex &i) {
  DvDirModelNode *node = getNode(i);
  if (!node) return;
  if (node->isFolder(folderPath))
    refresh(i);
  else if (node->areChildrenValid()) {
    int count = rowCount(i);
    int r;
    for (r = 0; r < count; r++) refreshFolder(folderPath, index(r, 0, i));
  }
}

//-----------------------------------------------------------------------------

void DvDirModel::refreshFolderChild(const QModelIndex &i) {
  DvDirModelNode *node = getNode(i);
  if (!node || !node->areChildrenValid()) return;

  if (node->isFolder() || dynamic_cast<DvDirModelMyComputerNode *>(node))
    refresh(i);
  int count = rowCount(i);
  int r;
  for (r = 0; r < count; r++) refreshFolderChild(index(r, 0, i));
}

//-----------------------------------------------------------------------------

DvDirModelNode *DvDirModel::getNode(const QModelIndex &index) const {
  if (index.isValid())
    return static_cast<DvDirModelNode *>(index.internalPointer());
  else
    return m_root;
}

//-----------------------------------------------------------------------------

QModelIndex DvDirModel::index(int row, int column,
                              const QModelIndex &parent) const {
  if (column != 0) return QModelIndex();
  DvDirModelNode *parentNode       = m_root;
  if (parent.isValid()) parentNode = getNode(parent);
  if (row < 0 || row >= parentNode->getChildCount()) return QModelIndex();
  DvDirModelNode *node = parentNode->getChild(row);
  return createIndex(row, column, node);
}

//-----------------------------------------------------------------------------

QModelIndex DvDirModel::parent(const QModelIndex &index) const {
  if (!index.isValid()) return QModelIndex();
  DvDirModelNode *node       = getNode(index);
  DvDirModelNode *parentNode = node->getParent();
  if (!parentNode || parentNode == m_root)
    return QModelIndex();
  else
    return createIndex(parentNode->getRow(), 0, parentNode);
}

//-----------------------------------------------------------------------------

QModelIndex DvDirModel::childByName(const QModelIndex &parent,
                                    const std::wstring &name) const {
  if (!parent.isValid()) return QModelIndex();
  DvDirModelNode *parentNode = getNode(parent);
  if (!parentNode) return QModelIndex();
  int row = parentNode->rowByName(name);
  if (row < 0 || row >= parentNode->getChildCount()) return QModelIndex();
  DvDirModelNode *childNode = parentNode->getChild(row);
  return createIndex(row, 0, childNode);
}

//-----------------------------------------------------------------------------

int DvDirModel::rowCount(const QModelIndex &parent) const {
  DvDirModelNode *node = getNode(parent);
  return node->getChildCount();
}

//-----------------------------------------------------------------------------

bool DvDirModel::hasChildren(const QModelIndex &parent) const {
  DvDirModelNode *node = getNode(parent);
  return node->hasChildren();
}

//-----------------------------------------------------------------------------

QVariant DvDirModel::data(const QModelIndex &index, int role) const {
  if (!index.isValid()) return QVariant();
  DvDirModelNode *node = getNode(index);
  if (role == Qt::DisplayRole || role == Qt::EditRole)
    return QString::fromStdWString(node->getName());
  else if (role == Qt::DecorationRole) {
    return QVariant();
  } else if (role == Qt::ForegroundRole) {
    if (!node || !node->isRenameEnabled())
      return QBrush(Qt::blue);
    else
      return QVariant();
  } else
    return QVariant();
}

//-----------------------------------------------------------------------------

Qt::ItemFlags DvDirModel::flags(const QModelIndex &index) const {
  Qt::ItemFlags flags = QAbstractItemModel::flags(index);
  if (index.isValid()) {
    DvDirModelNode *node = getNode(index);
    if (node && node->isRenameEnabled()) flags |= Qt::ItemIsEditable;
  }
  return flags;
}

//-----------------------------------------------------------------------------
/*! used only for name / rename of items
*/
bool DvDirModel::setData(const QModelIndex &index, const QVariant &value,
                         int role) {
  if (!index.isValid()) return false;
  DvDirModelNode *node = getNode(index);
  if (!node || !node->isRenameEnabled()) return false;
  QString newName = value.toString();
  if (newName == "") return false;
  if (!node->setName(newName.toStdWString())) return false;
  emit dataChanged(index, index);
  return true;
}

//-----------------------------------------------------------------------------

bool DvDirModel::removeRows(int row, int count,
                            const QModelIndex &parentIndex) {
  if (!parentIndex.isValid()) return false;
  DvDirModelNode *node = getNode(parentIndex);
  if (!node) return false;
  emit beginRemoveRows(parentIndex, row, row + count - 1);
  node->removeChildren(row, count);
  emit endRemoveRows();
  return true;
}

//-----------------------------------------------------------------------------

QModelIndex DvDirModel::getIndexByPath(const TFilePath &path) const {
  return getIndexByNode(m_root->getNodeByPath(path));
}

//-----------------------------------------------------------------------------

QModelIndex DvDirModel::getIndexByNode(DvDirModelNode *node) const {
  if (!node) return QModelIndex();
  return createIndex(node->getRow(), 0, node);
}

//-----------------------------------------------------------------------------

QModelIndex DvDirModel::getCurrentProjectIndex() const {
  return getIndexByPath(
      TProjectManager::instance()->getCurrentProjectPath().getParentDir());
}

//-----------------------------------------------------------------------------

DvDirModel *DvDirModel::instance() {
  static DvDirModel _instance;
  return &_instance;
}
