#include <QtDebug>
#include <QtWidgets>
#include <QUrl>
#include "vfilelist.h"
#include "vconfigmanager.h"
#include "dialog/vnewfiledialog.h"
#include "dialog/vfileinfodialog.h"
#include "vnote.h"
#include "veditarea.h"
#include "utils/vutils.h"
#include "vnotefile.h"
#include "vconfigmanager.h"
#include "vmdeditor.h"
#include "vmdtab.h"
#include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h"
#include "vmainwindow.h"
#include "utils/vimnavigationforwidget.h"
#include "utils/viconutils.h"

extern VConfigManager *g_config;
extern VNote *g_vnote;
extern VMainWindow *g_mainWin;

const QString VFileList::c_infoShortcutSequence = "F2";
const QString VFileList::c_copyShortcutSequence = "Ctrl+C";
const QString VFileList::c_cutShortcutSequence = "Ctrl+X";
const QString VFileList::c_pasteShortcutSequence = "Ctrl+V";

VFileList::VFileList(QWidget *parent)
    : QWidget(parent), VNavigationMode()
{
    setupUI();
    initShortcuts();
    initActions();
}

void VFileList::setupUI()
{
    fileList = new QListWidget(this);
    fileList->setContextMenuPolicy(Qt::CustomContextMenu);
    fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
    fileList->setObjectName("FileList");
    fileList->setAttribute(Qt::WA_MacShowFocusRect, false);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(fileList);
    mainLayout->setContentsMargins(0, 0, 0, 0);

    connect(fileList, &QListWidget::customContextMenuRequested,
            this, &VFileList::contextMenuRequested);
    connect(fileList, &QListWidget::itemClicked,
            this, &VFileList::handleItemClicked);

    setLayout(mainLayout);
}

void VFileList::initShortcuts()
{
    QShortcut *infoShortcut = new QShortcut(QKeySequence(c_infoShortcutSequence), this);
    infoShortcut->setContext(Qt::WidgetWithChildrenShortcut);
    connect(infoShortcut, &QShortcut::activated,
            this, [this](){
                fileInfo();
            });

    QShortcut *copyShortcut = new QShortcut(QKeySequence(c_copyShortcutSequence), this);
    copyShortcut->setContext(Qt::WidgetWithChildrenShortcut);
    connect(copyShortcut, &QShortcut::activated,
            this, [this](){
                copySelectedFiles();
            });

    QShortcut *cutShortcut = new QShortcut(QKeySequence(c_cutShortcutSequence), this);
    cutShortcut->setContext(Qt::WidgetWithChildrenShortcut);
    connect(cutShortcut, &QShortcut::activated,
            this, [this](){
                cutSelectedFiles();
            });

    QShortcut *pasteShortcut = new QShortcut(QKeySequence(c_pasteShortcutSequence), this);
    pasteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
    connect(pasteShortcut, &QShortcut::activated,
            this, [this](){
                pasteFilesFromClipboard();
            });
}

void VFileList::initActions()
{
    newFileAct = new QAction(VIconUtils::menuIcon(":/resources/icons/create_note.svg"),
                             tr("&New Note"), this);
    QString shortcutStr = VUtils::getShortcutText(g_config->getShortcutKeySequence("NewNote"));
    if (!shortcutStr.isEmpty()) {
        newFileAct->setText(tr("&New Note\t%1").arg(shortcutStr));
    }

    newFileAct->setToolTip(tr("Create a note in current folder"));
    connect(newFileAct, SIGNAL(triggered(bool)),
            this, SLOT(newFile()));

    m_openInReadAct = new QAction(VIconUtils::menuIcon(":/resources/icons/reading.svg"),
                                  tr("&Open In Read Mode"), this);
    m_openInReadAct->setToolTip(tr("Open current note in read mode"));
    connect(m_openInReadAct, &QAction::triggered,
            this, [this]() {
                QListWidgetItem *item = fileList->currentItem();
                if (item) {
                    emit fileClicked(getVFile(item), OpenFileMode::Read, true);
                }
            });

    m_openInEditAct = new QAction(VIconUtils::menuIcon(":/resources/icons/editing.svg"),
                                  tr("Open In &Edit Mode"), this);
    m_openInEditAct->setToolTip(tr("Open current note in edit mode"));
    connect(m_openInEditAct, &QAction::triggered,
            this, [this]() {
                QListWidgetItem *item = fileList->currentItem();
                if (item) {
                    emit fileClicked(getVFile(item), OpenFileMode::Edit, true);
                }
            });

    deleteFileAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_note.svg"),
                                tr("&Delete"), this);
    deleteFileAct->setToolTip(tr("Delete selected note"));
    connect(deleteFileAct, SIGNAL(triggered(bool)),
            this, SLOT(deleteSelectedFiles()));

    fileInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/note_info.svg"),
                              tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)), this);
    fileInfoAct->setToolTip(tr("View and edit current note's information"));
    connect(fileInfoAct, SIGNAL(triggered(bool)),
            this, SLOT(fileInfo()));

    copyAct = new QAction(VIconUtils::menuIcon(":/resources/icons/copy.svg"),
                          tr("&Copy\t%1").arg(VUtils::getShortcutText(c_copyShortcutSequence)), this);
    copyAct->setToolTip(tr("Copy selected notes"));
    connect(copyAct, &QAction::triggered,
            this, &VFileList::copySelectedFiles);

    cutAct = new QAction(VIconUtils::menuIcon(":/resources/icons/cut.svg"),
                         tr("C&ut\t%1").arg(VUtils::getShortcutText(c_cutShortcutSequence)), this);
    cutAct->setToolTip(tr("Cut selected notes"));
    connect(cutAct, &QAction::triggered,
            this, &VFileList::cutSelectedFiles);

    pasteAct = new QAction(VIconUtils::menuIcon(":/resources/icons/paste.svg"),
                           tr("&Paste\t%1").arg(VUtils::getShortcutText(c_pasteShortcutSequence)), this);
    pasteAct->setToolTip(tr("Paste notes in current folder"));
    connect(pasteAct, &QAction::triggered,
            this, &VFileList::pasteFilesFromClipboard);

    m_openLocationAct = new QAction(tr("&Open Note Location"), this);
    m_openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
    connect(m_openLocationAct, &QAction::triggered,
            this, &VFileList::openFileLocation);

    m_sortAct = new QAction(VIconUtils::menuIcon(":/resources/icons/sort.svg"),
                            tr("&Sort"),
                            this);
    m_sortAct->setToolTip(tr("Sort notes in this folder manually"));
    connect(m_sortAct, &QAction::triggered,
            this, &VFileList::sortItems);

    initOpenWithMenu();
}

void VFileList::setDirectory(VDirectory *p_directory)
{
    // QPointer will be set to NULL automatically once the directory was deleted.
    // If the last directory is deleted, m_directory and p_directory will both
    // be NULL.
    if (m_directory == p_directory) {
        if (!m_directory) {
            fileList->clear();
        }

        return;
    }

    m_directory = p_directory;
    if (!m_directory) {
        fileList->clear();
        return;
    }

    updateFileList();
}

void VFileList::updateFileList()
{
    fileList->clear();
    if (!m_directory->open()) {
        return;
    }

    const QVector<VNoteFile *> &files = m_directory->getFiles();
    for (int i = 0; i < files.size(); ++i) {
        VNoteFile *file = files[i];
        insertFileListItem(file);
    }
}

void VFileList::fileInfo()
{
    QList<QListWidgetItem *> items = fileList->selectedItems();
    if (items.size() == 1) {
        fileInfo(getVFile(items[0]));
    }
}

void VFileList::openFileLocation() const
{
    QList<QListWidgetItem *> items = fileList->selectedItems();
    if (items.size() == 1) {
        QUrl url = QUrl::fromLocalFile(getVFile(items[0])->fetchBasePath());
        QDesktopServices::openUrl(url);
    }
}

void VFileList::fileInfo(VNoteFile *p_file)
{
    if (!p_file) {
        return;
    }

    VDirectory *dir = p_file->getDirectory();
    QString curName = p_file->getName();
    VFileInfoDialog dialog(tr("Note Information"), "", dir, p_file, this);
    if (dialog.exec() == QDialog::Accepted) {
        QString name = dialog.getNameInput();
        if (name == curName) {
            return;
        }

        if (!p_file->rename(name)) {
            VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
                                tr("Fail to rename note <span style=\"%1\">%2</span>.")
                                  .arg(g_config->c_dataTextStyle).arg(curName), "",
                                QMessageBox::Ok, QMessageBox::Ok, this);
            return;
        }

        QListWidgetItem *item = findItem(p_file);
        if (item) {
            fillItem(item, p_file);
        }

        emit fileUpdated(p_file);
    }
}

void VFileList::fillItem(QListWidgetItem *p_item, const VNoteFile *p_file)
{
    qulonglong ptr = (qulonglong)p_file;
    p_item->setData(Qt::UserRole, ptr);
    p_item->setToolTip(p_file->getName());
    p_item->setText(p_file->getName());

    V_ASSERT(sizeof(p_file) <= sizeof(ptr));
}

QListWidgetItem* VFileList::insertFileListItem(VNoteFile *file, bool atFront)
{
    V_ASSERT(file);
    QListWidgetItem *item = new QListWidgetItem();
    fillItem(item, file);

    if (atFront) {
        fileList->insertItem(0, item);
    } else {
        fileList->addItem(item);
    }

    // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
    fileList->update();
    return item;
}

void VFileList::removeFileListItem(VNoteFile *p_file)
{
    if (!p_file) {
        return;
    }

    QListWidgetItem *item = findItem(p_file);
    if (!item) {
        return;
    }

    int row = fileList->row(item);
    Q_ASSERT(row >= 0);

    fileList->takeItem(row);
    delete item;

    // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
    fileList->update();
}

void VFileList::newFile()
{
    if (!m_directory) {
        return;
    }

    QList<QString> suffixes = g_config->getDocSuffixes()[(int)DocType::Markdown];
    QString defaultSuf;
    QString suffixStr;
    for (auto const & suf : suffixes) {
        suffixStr += (suffixStr.isEmpty() ? suf : "/" + suf);
        if (defaultSuf.isEmpty() || suf == "md") {
            defaultSuf = suf;
        }
    }

    QString info = tr("Create a note in <span style=\"%1\">%2</span>.")
                     .arg(g_config->c_dataTextStyle).arg(m_directory->getName());
    info = info + "<br>" + tr("Note with name ending with \"%1\" will be treated as Markdown type.")
                             .arg(suffixStr);
    QString defaultName = QString("new_note.%1").arg(defaultSuf);
    defaultName = VUtils::getFileNameWithSequence(m_directory->fetchPath(),
                                                  defaultName,
                                                  true);
    VNewFileDialog dialog(tr("Create Note"), info, defaultName, m_directory, this);
    if (dialog.exec() == QDialog::Accepted) {
        VNoteFile *file = m_directory->createFile(dialog.getNameInput());
        if (!file) {
            VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
                                tr("Fail to create note <span style=\"%1\">%2</span>.")
                                  .arg(g_config->c_dataTextStyle).arg(dialog.getNameInput()), "",
                                QMessageBox::Ok, QMessageBox::Ok, this);
            return;
        }

        // Whether need to move the cursor to the end.
        bool moveCursorEnd = false;
        // Content needed to insert into the new file, title/template.
        QString insertContent;
        if (dialog.getInsertTitleInput() && file->getDocType() == DocType::Markdown) {
            // Insert title.
            insertContent = QString("# %1\n").arg(QFileInfo(file->getName()).completeBaseName());
        }

        if (dialog.isTemplateUsed()) {
            Q_ASSERT(insertContent.isEmpty());
            insertContent = dialog.getTemplate();
        }

        if (!insertContent.isEmpty()) {
            if (!file->open()) {
                qWarning() << "fail to open newly-created note" << file->getName();
            } else {
                Q_ASSERT(file->getContent().isEmpty());
                file->setContent(insertContent);
                if (!file->save()) {
                    qWarning() << "fail to write to newly-created note" << file->getName();
                } else {
                    if (dialog.getInsertTitleInput()) {
                        moveCursorEnd = true;
                    }
                }

                file->close();
            }
        }

        QVector<QListWidgetItem *> items = updateFileListAdded();
        Q_ASSERT(items.size() == 1);
        fileList->setCurrentItem(items[0], QItemSelectionModel::ClearAndSelect);
        // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
        fileList->update();

        // Open it in edit mode
        emit fileCreated(file, OpenFileMode::Edit, true);

        // Move cursor down if content has been inserted.
        if (moveCursorEnd) {
            const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->getCurrentTab());
            if (tab) {
                VMdEditor *edit = tab->getEditor();
                if (edit && edit->getFile() == file) {
                    QTextCursor cursor = edit->textCursor();
                    cursor.movePosition(QTextCursor::End);
                    edit->setTextCursor(cursor);
                }
            }
        }
    }
}

QVector<QListWidgetItem *> VFileList::updateFileListAdded()
{
    QVector<QListWidgetItem *> ret;
    const QVector<VNoteFile *> &files = m_directory->getFiles();
    for (int i = 0; i < files.size(); ++i) {
        VNoteFile *file = files[i];
        if (i >= fileList->count()) {
            QListWidgetItem *item = insertFileListItem(file, false);
            ret.append(item);
        } else {
            VNoteFile *itemFile = getVFile(fileList->item(i));
            if (itemFile != file) {
                QListWidgetItem *item = insertFileListItem(file, false);
                ret.append(item);
            }
        }
    }

    return ret;
}

void VFileList::deleteSelectedFiles()
{
    QList<QListWidgetItem *> items = fileList->selectedItems();
    Q_ASSERT(!items.isEmpty());

    QVector<VNoteFile *> files;
    for (auto const & item : items) {
        files.push_back(getVFile(item));
    }

    deleteFiles(files);
}

// @p_file may or may not be listed in VFileList
void VFileList::deleteFile(VNoteFile *p_file)
{
    if (!p_file) {
        return;
    }

    QVector<VNoteFile *> files(1, p_file);
    deleteFiles(files);
}

void VFileList::deleteFiles(const QVector<VNoteFile *> &p_files)
{
    if (p_files.isEmpty()) {
        return;
    }

    QVector<ConfirmItemInfo> items;
    for (auto const & file : p_files) {
        items.push_back(ConfirmItemInfo(file->getName(),
                                        file->fetchPath(),
                                        file->fetchPath(),
                                        (void *)file));
    }

    QString text = tr("Are you sure to delete these notes?");

    QString info = tr("<span style=\"%1\">WARNING</span>: "
                      "VNote will delete notes as well as all "
                      "their images and attachments managed by VNote. "
                      "Deleted files could be found in the recycle "
                      "bin of these notes.<br>"
                      "Click \"Cancel\" to leave them untouched.<br>"
                      "The operation is IRREVERSIBLE!")
                     .arg(g_config->c_warningTextStyle);

    VConfirmDeletionDialog dialog(tr("Confirm Deleting Notes"),
                                  text,
                                  info,
                                  items,
                                  false,
                                  false,
                                  false,
                                  this);
    if (dialog.exec()) {
        items = dialog.getConfirmedItems();
        QVector<VNoteFile *> files;
        for (auto const & item : items) {
            files.push_back((VNoteFile *)item.m_data);
        }

        int nrDeleted = 0;
        for (auto file : files) {
            editArea->closeFile(file, true);

            // Remove the item before deleting it totally, or file will be invalid.
            removeFileListItem(file);

            QString errMsg;
            QString fileName = file->getName();
            QString filePath = file->fetchPath();
            if (!VNoteFile::deleteFile(file, &errMsg)) {
                VUtils::showMessage(QMessageBox::Warning,
                                    tr("Warning"),
                                    tr("Fail to delete note <span style=\"%1\">%2</span>.<br>"
                                       "Please check <span style=\"%1\">%3</span> and manually delete it.")
                                      .arg(g_config->c_dataTextStyle)
                                      .arg(fileName)
                                      .arg(filePath),
                                    errMsg,
                                    QMessageBox::Ok,
                                    QMessageBox::Ok,
                                    this);
            } else {
                Q_ASSERT(errMsg.isEmpty());
                ++nrDeleted;
            }
        }

        if (nrDeleted > 0) {
            g_mainWin->showStatusMessage(tr("%1 %2 deleted")
                                           .arg(nrDeleted)
                                           .arg(nrDeleted > 1 ? tr("notes") : tr("note")));
        }
    }
}

void VFileList::contextMenuRequested(QPoint pos)
{
    QListWidgetItem *item = fileList->itemAt(pos);
    QMenu menu(this);
    menu.setToolTipsVisible(true);

    if (!m_directory) {
        return;
    }

    if (item && fileList->selectedItems().size() == 1) {
        VNoteFile *file = getVFile(item);
        if (file) {
            if (file->getDocType() == DocType::Markdown) {
                menu.addAction(m_openInReadAct);
                menu.addAction(m_openInEditAct);
            }

            menu.addMenu(m_openWithMenu);
            menu.addSeparator();
        }
    }

    menu.addAction(newFileAct);

    if (fileList->count() > 1) {
        menu.addAction(m_sortAct);
    }

    if (item) {
        menu.addSeparator();
        menu.addAction(deleteFileAct);
        menu.addAction(copyAct);
        menu.addAction(cutAct);
    }

    if (pasteAvailable()) {
        if (!item) {
            menu.addSeparator();
        }

        menu.addAction(pasteAct);
    }

    if (item) {
        menu.addSeparator();
        menu.addAction(m_openLocationAct);

        if (fileList->selectedItems().size() == 1) {
            menu.addAction(fileInfoAct);
        }
    }

    menu.exec(fileList->mapToGlobal(pos));
}

QListWidgetItem* VFileList::findItem(const VNoteFile *p_file)
{
    if (!p_file || p_file->getDirectory() != m_directory) {
        return NULL;
    }

    int nrChild = fileList->count();
    for (int i = 0; i < nrChild; ++i) {
        QListWidgetItem *item = fileList->item(i);
        if (p_file == getVFile(item)) {
            return item;
        }
    }

    return NULL;
}

void VFileList::handleItemClicked(QListWidgetItem *currentItem)
{
    Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
    if (modifiers != Qt::NoModifier) {
        return;
    }

    if (!currentItem) {
        emit fileClicked(NULL);
        return;
    }

    // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
    fileList->update();
    emit fileClicked(getVFile(currentItem), g_config->getNoteOpenMode());
}

bool VFileList::importFiles(const QStringList &p_files, QString *p_errMsg)
{
    if (p_files.isEmpty()) {
        return false;
    }

    bool ret = true;
    Q_ASSERT(m_directory && m_directory->isOpened());
    QString dirPath = m_directory->fetchPath();
    QDir dir(dirPath);

    int nrImported = 0;
    for (int i = 0; i < p_files.size(); ++i) {
        const QString &file = p_files[i];

        QFileInfo fi(file);
        if (!fi.exists() || !fi.isFile()) {
            VUtils::addErrMsg(p_errMsg, tr("Skip importing non-exist file %1.")
                                          .arg(file));
            ret = false;
            continue;
        }

        QString name = VUtils::fileNameFromPath(file);
        Q_ASSERT(!name.isEmpty());
        name = VUtils::getFileNameWithSequence(dirPath, name, true);
        QString targetFilePath = dir.filePath(name);
        bool ret = VUtils::copyFile(file, targetFilePath, false);
        if (!ret) {
            VUtils::addErrMsg(p_errMsg, tr("Fail to copy file %1 as %2.")
                                          .arg(file)
                                          .arg(targetFilePath));
            ret = false;
            continue;
        }

        VNoteFile *destFile = m_directory->addFile(name, -1);
        if (destFile) {
            ++nrImported;
            qDebug() << "imported" << file << "as" << targetFilePath;
        } else {
            VUtils::addErrMsg(p_errMsg, tr("Fail to add the note %1 to target folder's configuration.")
                                          .arg(file));
            ret = false;
            continue;
        }
    }

    qDebug() << "imported" << nrImported << "files";

    updateFileList();

    return ret;
}

void VFileList::copySelectedFiles(bool p_isCut)
{
    QList<QListWidgetItem *> items = fileList->selectedItems();
    if (items.isEmpty()) {
        return;
    }

    QJsonArray files;
    for (int i = 0; i < items.size(); ++i) {
        VNoteFile *file = getVFile(items[i]);
        files.append(file->fetchPath());
    }

    QJsonObject clip;
    clip[ClipboardConfig::c_magic] = getNewMagic();
    clip[ClipboardConfig::c_type] = (int)ClipboardOpType::CopyFile;
    clip[ClipboardConfig::c_isCut] = p_isCut;
    clip[ClipboardConfig::c_files] = files;

    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));

    qDebug() << "copied files info" << clipboard->text();

    int cnt = files.size();
    g_mainWin->showStatusMessage(tr("%1 %2 %3")
                                   .arg(cnt)
                                   .arg(cnt > 1 ? tr("notes") : tr("note"))
                                   .arg(p_isCut ? tr("cut") : tr("copied")));
}

void VFileList::cutSelectedFiles()
{
    copySelectedFiles(true);
}

void VFileList::pasteFilesFromClipboard()
{
    if (!pasteAvailable()) {
        return;
    }

    QJsonObject obj = VUtils::clipboardToJson();
    QJsonArray files = obj[ClipboardConfig::c_files].toArray();
    bool isCut = obj[ClipboardConfig::c_isCut].toBool();
    QVector<QString> filesToPaste(files.size());
    for (int i = 0; i < files.size(); ++i) {
        filesToPaste[i] = files[i].toString();
    }

    pasteFiles(m_directory, filesToPaste, isCut);

    QClipboard *clipboard = QApplication::clipboard();
    clipboard->clear();
}

void VFileList::pasteFiles(VDirectory *p_destDir,
                           const QVector<QString> &p_files,
                           bool p_isCut)
{
    if (!p_destDir || p_files.isEmpty()) {
        return;
    }

    int nrPasted = 0;
    for (int i = 0; i < p_files.size(); ++i) {
        VNoteFile *file = g_vnote->getInternalFile(p_files[i]);
        if (!file) {
            qWarning() << "Copied file is not an internal note" << p_files[i];
            VUtils::showMessage(QMessageBox::Warning,
                                tr("Warning"),
                                tr("Fail to paste note <span style=\"%1\">%2</span>.")
                                  .arg(g_config->c_dataTextStyle)
                                  .arg(p_files[i]),
                                tr("VNote could not find this note in any notebook."),
                                QMessageBox::Ok,
                                QMessageBox::Ok,
                                this);

            continue;
        }

        QString fileName = file->getName();
        if (file->getDirectory() == p_destDir) {
            if (p_isCut) {
                qDebug() << "skip one note to cut and paste in the same folder" << fileName;
                continue;
            }

            // Copy and paste in the same folder.
            // We do not allow this if the note contains local images.
            if (file->getDocType() == DocType::Markdown) {
                QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(file,
                                                                                ImageLink::LocalRelativeInternal);
                if (!images.isEmpty()) {
                    qDebug() << "skip one note with internal images to copy and paste in the same folder"
                             << fileName;
                    VUtils::showMessage(QMessageBox::Warning,
                                        tr("Warning"),
                                        tr("Fail to copy note <span style=\"%1\">%2</span>.")
                                          .arg(g_config->c_dataTextStyle)
                                          .arg(p_files[i]),
                                        tr("VNote does not allow copy and paste notes with internal images "
                                           "in the same folder."),
                                        QMessageBox::Ok,
                                        QMessageBox::Ok,
                                        this);
                    continue;
                }
            }

            // Rename it to xxx_copy.md.
            fileName = VUtils::generateCopiedFileName(file->fetchBasePath(),
                                                      fileName,
                                                      true);
        } else {
            // Rename it to xxx_copy.md if needed.
            fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(),
                                                      fileName,
                                                      true);
        }

        QString msg;
        VNoteFile *destFile = NULL;
        bool ret = VNoteFile::copyFile(p_destDir,
                                       fileName,
                                       file,
                                       p_isCut,
                                       &destFile,
                                       &msg);
        if (!ret) {
            VUtils::showMessage(QMessageBox::Warning,
                                tr("Warning"),
                                tr("Fail to copy note <span style=\"%1\">%2</span>.")
                                  .arg(g_config->c_dataTextStyle)
                                  .arg(p_files[i]),
                                msg,
                                QMessageBox::Ok,
                                QMessageBox::Ok,
                                this);
        }

        if (destFile) {
            ++nrPasted;
            emit fileUpdated(destFile);
        }
    }

    qDebug() << "pasted" << nrPasted << "files";
    if (nrPasted > 0) {
        g_mainWin->showStatusMessage(tr("%1 %2 pasted")
                                       .arg(nrPasted)
                                       .arg(nrPasted > 1 ? tr("notes") : tr("note")));
    }

    updateFileList();
    getNewMagic();
}

void VFileList::keyPressEvent(QKeyEvent *p_event)
{
    if (VimNavigationForWidget::injectKeyPressEventForVim(fileList,
                                                          p_event)) {
        return;
    }

    if (p_event->key() == Qt::Key_Return) {
        QListWidgetItem *item = fileList->currentItem();
        if (item) {
            handleItemClicked(item);
        }
    }

    QWidget::keyPressEvent(p_event);
}

void VFileList::focusInEvent(QFocusEvent * /* p_event */)
{
    fileList->setFocus();
}

bool VFileList::locateFile(const VNoteFile *p_file)
{
    if (p_file) {
        if (p_file->getDirectory() != m_directory) {
            return false;
        }

        QListWidgetItem *item = findItem(p_file);
        if (item) {
            fileList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
            return true;
        }
    }

    return false;
}

void VFileList::showNavigation()
{
    VNavigationMode::showNavigation(fileList);
}

bool VFileList::handleKeyNavigation(int p_key, bool &p_succeed)
{
    static bool secondKey = false;
    return VNavigationMode::handleKeyNavigation(fileList, secondKey, p_key, p_succeed);
}

int VFileList::getNewMagic()
{
    m_magicForClipboard = (int)QDateTime::currentDateTime().toTime_t();
    m_magicForClipboard |= qrand();

    return m_magicForClipboard;
}

bool VFileList::checkMagic(int p_magic) const
{
    return m_magicForClipboard == p_magic;
}

bool VFileList::pasteAvailable() const
{
    QJsonObject obj = VUtils::clipboardToJson();
    if (obj.isEmpty()) {
        return false;
    }

    if (!obj.contains(ClipboardConfig::c_type)) {
        return false;
    }

    ClipboardOpType type = (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
    if (type != ClipboardOpType::CopyFile) {
        return false;
    }

    if (!obj.contains(ClipboardConfig::c_magic)
        || !obj.contains(ClipboardConfig::c_isCut)
        || !obj.contains(ClipboardConfig::c_files)) {
        return false;
    }

    int magic = obj[ClipboardConfig::c_magic].toInt();
    if (!checkMagic(magic)) {
        return false;
    }

    QJsonArray files = obj[ClipboardConfig::c_files].toArray();
    return !files.isEmpty();
}

void VFileList::sortItems()
{
    const QVector<VNoteFile *> &files = m_directory->getFiles();
    if (files.size() < 2) {
        return;
    }

    VSortDialog dialog(tr("Sort Notes"),
                       tr("Sort notes in folder <span style=\"%1\">%2</span> "
                          "in the configuration file.")
                         .arg(g_config->c_dataTextStyle)
                         .arg(m_directory->getName()),
                       this);
    QTreeWidget *tree = dialog.getTreeWidget();
    tree->clear();
    tree->setColumnCount(3);
    QStringList headers;
    headers << tr("Name") << tr("Created Time") << tr("Modified Time");
    tree->setHeaderLabels(headers);

    for (int i = 0; i < files.size(); ++i) {
        const VNoteFile *file = files[i];
        QString createdTime = VUtils::displayDateTime(file->getCreatedTimeUtc().toLocalTime());
        QString modifiedTime = VUtils::displayDateTime(file->getModifiedTimeUtc().toLocalTime());
        QStringList cols;
        cols << file->getName() << createdTime << modifiedTime;
        QTreeWidgetItem *item = new QTreeWidgetItem(tree, cols);

        item->setData(0, Qt::UserRole, i);
    }

    dialog.treeUpdated();

    if (dialog.exec()) {
        QVector<QVariant> data = dialog.getSortedData();
        Q_ASSERT(data.size() == files.size());
        QVector<int> sortedIdx(data.size(), -1);
        for (int i = 0; i < data.size(); ++i) {
            sortedIdx[i] = data[i].toInt();
        }

        qDebug() << "sort files" << sortedIdx;
        if (!m_directory->sortFiles(sortedIdx)) {
            VUtils::showMessage(QMessageBox::Warning,
                                tr("Warning"),
                                tr("Fail to sort notes in folder <span style=\"%1\">%2</span>.")
                                  .arg(g_config->c_dataTextStyle)
                                  .arg(m_directory->getName()),
                                "",
                                QMessageBox::Ok,
                                QMessageBox::Ok,
                                this);
        }

        updateFileList();
    }
}

void VFileList::initOpenWithMenu()
{
    m_openWithMenu = new QMenu(tr("Open With"), this);
    m_openWithMenu->setToolTipsVisible(true);

    auto programs = g_config->getExternalEditors();
    for (auto const & pa : programs) {
        QAction *act = new QAction(pa.first, this);
        act->setToolTip(tr("Open current note with %1").arg(pa.first));
        act->setStatusTip(pa.second);
        act->setData(pa.second);
        connect(act, &QAction::triggered,
                this, &VFileList::handleOpenWithActionTriggered);

        m_openWithMenu->addAction(act);
    }

    QAction *defaultAct = new QAction(tr("System's Default Program"), this);
    defaultAct->setToolTip(tr("Open current note with system's default program"));
    connect(defaultAct, &QAction::triggered,
            this, [this]() {
                QListWidgetItem *item = fileList->currentItem();
                if (item) {
                    VNoteFile *file = getVFile(item);
                    if (file
                        && (!editArea->isFileOpened(file) || editArea->closeFile(file, false))) {
                        QUrl url = QUrl::fromLocalFile(file->fetchPath());
                        QDesktopServices::openUrl(url);
                    }
                }
            });

    m_openWithMenu->addAction(defaultAct);
}

void VFileList::handleOpenWithActionTriggered()
{
    QAction *act = static_cast<QAction *>(sender());
    QString cmd = act->data().toString();

    QListWidgetItem *item = fileList->currentItem();
    if (item) {
        VNoteFile *file = getVFile(item);
        if (file
            && (!editArea->isFileOpened(file) || editArea->closeFile(file, false))) {
            cmd.replace("%0", file->fetchPath());
            QProcess *process = new QProcess(this);
            connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
                    process, &QProcess::deleteLater);
            process->start(cmd);
            qDebug() << "open with" << cmd << "process" << process->processId();
        }
    }
}
