/*
 * Copyright (C) 2013 - 2015  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "utilities.h"
#include "utilities_p.h"
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <QUrl>
#include <QList>
#include <QStringBuilder>
#include <QMessageBox>
#include "fileoperation.h"
#include <QEventLoop>

#include <pwd.h>
#include <grp.h>
#include <stdlib.h>
#include <glib.h>

namespace Fm {

FmPathList* pathListFromQUrls(QList<QUrl> urls) {
  QList<QUrl>::const_iterator it;
  FmPathList* pathList = fm_path_list_new();

  for(it = urls.begin(); it != urls.end(); ++it) {
    QUrl url = *it;
    FmPath* path = fm_path_new_for_uri(url.toString().toUtf8());
    fm_path_list_push_tail(pathList, path);
    fm_path_unref(path);
  }

  return pathList;
}

void pasteFilesFromClipboard(FmPath* destPath, QWidget* parent) {
  QClipboard* clipboard = QApplication::clipboard();
  const QMimeData* data = clipboard->mimeData();
  bool isCut = false;
  FmPathList* paths = NULL;

  if(data->hasFormat("x-special/gnome-copied-files")) {
    // Gnome, LXDE, and XFCE
    QByteArray gnomeData = data->data("x-special/gnome-copied-files");
    char* pdata = gnomeData.data();
    char* eol = strchr(pdata, '\n');

    if(eol) {
      *eol = '\0';
      isCut = (strcmp(pdata, "cut") == 0 ? true : false);
      paths = fm_path_list_new_from_uri_list(eol + 1);
    }
  }

  if(!paths && data->hasUrls()) {
    // The KDE way
    paths = Fm::pathListFromQUrls(data->urls());
    QByteArray cut = data->data("x-kde-cut-selection");

    if(!cut.isEmpty() && cut.at(0) == '1')
      isCut = true;
  }

  if(paths) {
    if(isCut)
      FileOperation::moveFiles(paths, destPath, parent);
    else
      FileOperation::copyFiles(paths, destPath, parent);

    fm_path_list_unref(paths);
  }
}

void copyFilesToClipboard(FmPathList* files) {
  QClipboard* clipboard = QApplication::clipboard();
  QMimeData* data = new QMimeData();
  char* urilist = fm_path_list_to_uri_list(files);
  // Gnome, LXDE, and XFCE
  data->setData("x-special/gnome-copied-files", QByteArray("copy\n") + QByteArray(urilist));
  // The KDE way
  data->setData("text/uri-list", urilist);
  // data.setData("x-kde-cut-selection", "0");
  g_free(urilist);
  clipboard->setMimeData(data);
}

void cutFilesToClipboard(FmPathList* files) {
  QClipboard* clipboard = QApplication::clipboard();
  QMimeData* data = new QMimeData();
  char* urilist = fm_path_list_to_uri_list(files);
  // Gnome, LXDE, and XFCE
  data->setData("x-special/gnome-copied-files", QByteArray("cut\n") + QByteArray(urilist));
  // The KDE way
  data->setData("text/uri-list", urilist);
  data->setData("x-kde-cut-selection", "1");
  g_free(urilist);
  clipboard->setMimeData(data);
}

void renameFile(FmFileInfo *file, QWidget *parent) {
  FmPath* path = fm_file_info_get_path(file);
  FilenameDialog dlg(parent);
  dlg.setWindowTitle(QObject::tr("Rename File"));
  dlg.setLabelText(QObject::tr("Please enter a new name:"));
  // FIXME: what's the best way to handle non-UTF8 filename encoding here?
  QString old_name = QString::fromLocal8Bit(fm_path_get_basename(path));
  dlg.setTextValue(old_name);

  if(fm_file_info_is_dir(file)) // select filename extension for directories
    dlg.setSelectExtension(true);

  if(dlg.exec() != QDialog::Accepted)
    return;

  QString new_name = dlg.textValue();

  if(new_name == old_name)
    return;

  GFile* gf = fm_path_to_gfile(path);
  GFile* parent_gf = g_file_get_parent(gf);
  GFile* dest = g_file_get_child(G_FILE(parent_gf), new_name.toLocal8Bit().data());
  g_object_unref(parent_gf);

  GError* err = NULL;
  if(!g_file_move(gf, dest,
                  GFileCopyFlags(G_FILE_COPY_ALL_METADATA |
                                 G_FILE_COPY_NO_FALLBACK_FOR_MOVE |
                                 G_FILE_COPY_NOFOLLOW_SYMLINKS),
                  NULL, /* make this cancellable later. */
                  NULL, NULL, &err)) {
    QMessageBox::critical(parent, QObject::tr("Error"), err->message);
    g_error_free(err);
  }

  g_object_unref(dest);
  g_object_unref(gf);
}

// templateFile is a file path used as a template of the new file.
void createFileOrFolder(CreateFileType type, FmPath* parentDir, FmTemplate* templ, QWidget* parent) {
  QString defaultNewName;
  QString prompt;
  QString dialogTitle = type == CreateNewFolder ? QObject::tr("Create Folder")
                                                : QObject::tr("Create File");

  switch(type) {
  case CreateNewTextFile:
    prompt = QObject::tr("Please enter a new file name:");
    defaultNewName = QObject::tr("New text file");
    break;

  case CreateNewFolder:
    prompt = QObject::tr("Please enter a new folder name:");
    defaultNewName = QObject::tr("New folder");
    break;

  case CreateWithTemplate: {
    FmMimeType* mime = fm_template_get_mime_type(templ);
    prompt = QObject::tr("Enter a name for the new %1:").arg(QString::fromUtf8(fm_mime_type_get_desc(mime)));
    defaultNewName = QString::fromUtf8(fm_template_get_name(templ, NULL));
  }
  break;
  }

_retry:
  // ask the user to input a file name
  bool ok;
  QString new_name = QInputDialog::getText(parent, dialogTitle,
                     prompt,
                     QLineEdit::Normal,
                     defaultNewName,
                     &ok);

  if(!ok)
    return;

  GFile* parent_gf = fm_path_to_gfile(parentDir);
  GFile* dest_gf = g_file_get_child(G_FILE(parent_gf), new_name.toLocal8Bit().data());
  g_object_unref(parent_gf);

  GError* err = NULL;
  switch(type) {
  case CreateNewTextFile: {
    GFileOutputStream* f = g_file_create(dest_gf, G_FILE_CREATE_NONE, NULL, &err);
    if(f) {
      g_output_stream_close(G_OUTPUT_STREAM(f), NULL, NULL);
      g_object_unref(f);
    }
    break;
  }
  case CreateNewFolder:
    g_file_make_directory(dest_gf, NULL, &err);
    break;
  case CreateWithTemplate:
    fm_template_create_file(templ, dest_gf, &err, false);
    break;
  }
  g_object_unref(dest_gf);

  if(err) {
    if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS) {
      g_error_free(err);
      err = NULL;
      goto _retry;
    }

    QMessageBox::critical(parent, QObject::tr("Error"), err->message);
    g_error_free(err);
  }
}

uid_t uidFromName(QString name) {
  uid_t ret;
  if(name.isEmpty())
      return -1;
  if(name.at(0).digitValue() != -1) {
    ret = uid_t(name.toUInt());
  }
  else {
    struct passwd* pw = getpwnam(name.toLatin1());
    // FIXME: use getpwnam_r instead later to make it reentrant
    ret = pw ? pw->pw_uid : -1;
  }

  return ret;
}

QString uidToName(uid_t uid) {
  QString ret;
  struct passwd* pw = getpwuid(uid);

  if(pw)
    ret = pw->pw_name;
  else
    ret = QString::number(uid);

  return ret;
}

gid_t gidFromName(QString name) {
  gid_t ret;
  if(name.isEmpty())
      return -1;
  if(name.at(0).digitValue() != -1) {
    ret = gid_t(name.toUInt());
  }
  else {
    // FIXME: use getgrnam_r instead later to make it reentrant
    struct group* grp = getgrnam(name.toLatin1());
    ret = grp ? grp->gr_gid : -1;
  }

  return ret;
}

QString gidToName(gid_t gid) {
  QString ret;
  struct group* grp = getgrgid(gid);

  if(grp)
    ret = grp->gr_name;
  else
    ret = QString::number(gid);

  return ret;
}

int execModelessDialog(QDialog* dlg) {
  // FIXME: this does much less than QDialog::exec(). Will this work flawlessly?
  QEventLoop loop;
  QObject::connect(dlg, &QDialog::finished, &loop, &QEventLoop::quit);
  // DialogExec does not seem to be documented in the Qt API doc?
  // However, in the source code of QDialog::exec(), it's used so let's use it too.
  dlg->show();
  (void)loop.exec(QEventLoop::DialogExec);
  return dlg->result();
}

// check if GVFS can support this uri scheme (lower case)
// NOTE: this does not work reliably due to some problems in gio/gvfs and causes bug lxde/lxqt#512
// https://github.com/lxde/lxqt/issues/512
// Use uriExists() whenever possible.
bool isUriSchemeSupported(const char* uriScheme) {
  const gchar * const * schemes = g_vfs_get_supported_uri_schemes(g_vfs_get_default());
  if(Q_UNLIKELY(schemes == NULL))
    return false;
  for(const gchar * const * scheme = schemes; *scheme; ++scheme)
    if(strcmp(uriScheme, *scheme) == 0)
      return true;
  return false;
}

// check if the URI exists.
// NOTE: this is a blocking call possibly involving I/O.
// So it's better to use it in limited cases, like checking trash:// or computer://.
// Avoid calling this on a slow filesystem.
// Checking "network:///" is very slow, for example.
bool uriExists(const char* uri) {
  GFile* gf = g_file_new_for_uri(uri);
  bool ret = (bool)g_file_query_exists(gf, NULL);
  g_object_unref(gf);
  return ret;
}


} // namespace Fm
