#include "vnotebookselector.h"
#include <QDebug>
#include <QJsonObject>
#include <QListWidget>
#include <QAction>
#include <QMenu>
#include <QGuiApplication>
#include <QScreen>
#include <QLabel>
#include <QDesktopServices>
#include <QUrl>
#include "vnotebook.h"
#include "vconfigmanager.h"
#include "dialog/vnewnotebookdialog.h"
#include "dialog/vnotebookinfodialog.h"
#include "dialog/vdeletenotebookdialog.h"
#include "vnotebook.h"
#include "vdirectory.h"
#include "utils/vutils.h"
#include "vnote.h"
#include "veditarea.h"
#include "vnofocusitemdelegate.h"
#include "vmainwindow.h"
#include "utils/vimnavigationforwidget.h"
#include "utils/viconutils.h"

extern VConfigManager *g_config;

extern VNote *g_vnote;

extern VMainWindow *g_mainWin;

VNotebookSelector::VNotebookSelector(QWidget *p_parent)
    : QComboBox(p_parent),
      VNavigationMode(),
      m_notebooks(g_vnote->getNotebooks()),
      m_lastValidIndex(-1),
      m_muted(false),
      m_naviLabel(NULL)
{
    m_listWidget = new QListWidget(this);
    m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this));
    m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(m_listWidget, &QListWidget::customContextMenuRequested,
            this, &VNotebookSelector::popupListContextMenuRequested);

    setModel(m_listWidget->model());
    setView(m_listWidget);

    m_listWidget->viewport()->installEventFilter(this);
    m_listWidget->installEventFilter(this);

    initActions();

    connect(this, SIGNAL(currentIndexChanged(int)),
            this, SLOT(handleCurIndexChanged(int)));
}

void VNotebookSelector::initActions()
{
    m_deleteNotebookAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_notebook.svg"),
                                      tr("&Delete"), this);
    m_deleteNotebookAct->setToolTip(tr("Delete current notebook"));
    connect(m_deleteNotebookAct, SIGNAL(triggered(bool)),
            this, SLOT(deleteNotebook()));

    m_notebookInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/notebook_info.svg"),
                                    tr("&Info"), this);
    m_notebookInfoAct->setToolTip(tr("View and edit current notebook's information"));
    connect(m_notebookInfoAct, SIGNAL(triggered(bool)),
            this, SLOT(editNotebookInfo()));

    m_openLocationAct = new QAction(tr("&Open Notebook Location"), this);
    m_openLocationAct->setToolTip(tr("Open the root folder of this notebook in operating system"));
    connect(m_openLocationAct, &QAction::triggered,
            this, [this]() {
                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
                if (items.isEmpty()) {
                    return;
                }

                Q_ASSERT(items.size() == 1);
                VNotebook *notebook = getNotebook(items[0]);
                QUrl url = QUrl::fromLocalFile(notebook->getPath());
                QDesktopServices::openUrl(url);
            });

    m_recycleBinAct = new QAction(VIconUtils::menuIcon(":/resources/icons/recycle_bin.svg"),
                                  tr("&Recycle Bin"), this);
    m_recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook"));
    connect(m_recycleBinAct, &QAction::triggered,
            this, [this]() {
                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
                if (items.isEmpty()) {
                    return;
                }

                Q_ASSERT(items.size() == 1);
                VNotebook *notebook = getNotebook(items[0]);
                QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
                QDesktopServices::openUrl(url);
            });

    m_emptyRecycleBinAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/empty_recycle_bin.svg"),
                                       tr("&Empty Recycle Bin"), this);
    m_emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook"));
    connect(m_emptyRecycleBinAct, &QAction::triggered,
            this, [this]() {
                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
                if (items.isEmpty()) {
                    return;
                }

                Q_ASSERT(items.size() == 1);
                VNotebook *notebook = getNotebook(items[0]);
                QString binPath = notebook->getRecycleBinFolderPath();

                int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
                                              tr("Are you sure to empty recycle bin of notebook "
                                                 "<span style=\"%1\">%2</span>?")
                                                .arg(g_config->c_dataTextStyle)
                                                .arg(notebook->getName()),
                                              tr("<span style=\"%1\">WARNING</span>: "
                                                 "VNote will delete all the files in directory "
                                                 "<span style=\"%2\">%3</span>."
                                                 "<br>It may be UNRECOVERABLE!")
                                                .arg(g_config->c_warningTextStyle)
                                                .arg(g_config->c_dataTextStyle)
                                                .arg(binPath),
                                              QMessageBox::Ok | QMessageBox::Cancel,
                                              QMessageBox::Ok,
                                              this,
                                              MessageBoxType::Danger);
                if (ret == QMessageBox::Ok) {
                    QString info;
                    if (VUtils::emptyDirectory(notebook, binPath, true)) {
                        info = tr("Successfully emptied recycle bin of notebook "
                                  "<span style=\"%1\">%2</span>!")
                                 .arg(g_config->c_dataTextStyle)
                                 .arg(notebook->getName());
                    } else {
                        info = tr("Fail to empty recycle bin of notebook "
                                  "<span style=\"%1\">%2</span>!")
                                 .arg(g_config->c_dataTextStyle)
                                 .arg(notebook->getName());
                    }

                    VUtils::showMessage(QMessageBox::Information,
                                        tr("Information"),
                                        info,
                                        "",
                                        QMessageBox::Ok,
                                        QMessageBox::Ok,
                                        this);
                }
            });
}

void VNotebookSelector::updateComboBox()
{
    m_muted = true;

    int index = g_config->getCurNotebookIndex();

    clear();

    m_listWidget->clear();

    insertAddNotebookItem();

    for (int i = 0; i < m_notebooks.size(); ++i) {
        addNotebookItem(m_notebooks[i]);
    }

    setCurrentIndex(-1);

    m_muted = false;

    if (m_notebooks.isEmpty()) {
        g_config->setCurNotebookIndex(-1);
        setCurrentIndex(0);
    } else {
        const VNotebook *nb = NULL;
        if (index >= 0 && index < m_notebooks.size()) {
            nb = m_notebooks[index];
        }

        setCurrentItemToNotebook(nb);
    }

    qDebug() << "notebooks" << m_notebooks.size() << "current index" << index;
}

void VNotebookSelector::setCurrentItemToNotebook(const VNotebook *p_notebook)
{
    setCurrentIndex(itemIndexOfNotebook(p_notebook));
}

int VNotebookSelector::itemIndexOfNotebook(const VNotebook *p_notebook) const
{
    if (!p_notebook) {
        return -1;
    }

    qulonglong ptr = (qulonglong)p_notebook;
    int cnt = m_listWidget->count();
    for (int i = 0; i < cnt; ++i) {
        QListWidgetItem *item = m_listWidget->item(i);
        if (item->data(Qt::UserRole).toULongLong() == ptr) {
            return i;
        }
    }

    return -1;
}

void VNotebookSelector::insertAddNotebookItem()
{
    QListWidgetItem *item = new QListWidgetItem();
    item->setIcon(VIconUtils::comboBoxIcon(":/resources/icons/create_notebook.svg"));
    item->setText(tr("Add Notebook"));
    QFont font;
    font.setItalic(true);
    item->setData(Qt::FontRole, font);
    item->setToolTip(tr("Create or import a notebook"));

    m_listWidget->insertItem(0, item);
}

void VNotebookSelector::handleCurIndexChanged(int p_index)
{
    if (m_muted) {
        return;
    }

    QString tooltip = tr("View and edit notebooks");
    VNotebook *nb = NULL;
    if (p_index > -1) {
        nb = getNotebook(p_index);
        if (!nb) {
            // Add notebook.
            setToolTip(tooltip);

            if (m_lastValidIndex != p_index && m_lastValidIndex > -1) {
                setCurrentIndex(m_lastValidIndex);
            }

            if (!m_notebooks.isEmpty()) {
                newNotebook();
            }

            return;
        }
    }

    m_lastValidIndex = p_index;

    int nbIdx = -1;
    if (nb) {
        tooltip = nb->getName();

        nbIdx = m_notebooks.indexOf(nb);
        Q_ASSERT(nbIdx > -1);
    }

    g_config->setCurNotebookIndex(nbIdx);

    setToolTip(tooltip);

    emit curNotebookChanged(nb);
}

void VNotebookSelector::update()
{
    updateComboBox();
}

bool VNotebookSelector::newNotebook()
{
    QString info(tr("Please type the name of the notebook and "
                    "choose a folder as the Root Folder of the notebook."));
    info += "\n";
    info += tr("* The root folder should be used EXCLUSIVELY by VNote and "
               "it is recommended to be EMPTY.");
    info += "\n";
    info += tr("* A previously created notebook could be imported into VNote "
               "by choosing its root folder.");

    // Use empty default name and path to let the dialog to auto generate a name
    // under the default VNote notebook folder.
    VNewNotebookDialog dialog(tr("Add Notebook"),
                              info,
                              "",
                              "",
                              m_notebooks,
                              this);
    if (dialog.exec() == QDialog::Accepted) {
        createNotebook(dialog.getNameInput(),
                       dialog.getPathInput(),
                       dialog.isImportExistingNotebook(),
                       dialog.getImageFolder(),
                       dialog.getAttachmentFolder());

        emit notebookCreated(dialog.getNameInput(), dialog.isImportExistingNotebook());
        return true;
    }

    return false;
}

void VNotebookSelector::createNotebook(const QString &p_name,
                                       const QString &p_path,
                                       bool p_import,
                                       const QString &p_imageFolder,
                                       const QString &p_attachmentFolder)
{
    VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
                                              p_imageFolder, p_attachmentFolder,
                                              g_vnote);
    if (!nb) {
        VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
                            tr("Fail to create notebook "
                               "<span style=\"%1\">%2</span> in <span style=\"%1\">%3</span>.")
                            .arg(g_config->c_dataTextStyle).arg(p_name).arg(p_path), "",
                            QMessageBox::Ok, QMessageBox::Ok, this);
        return;
    }

    m_notebooks.append(nb);
    g_config->setNotebooks(m_notebooks);

    addNotebookItem(nb);
    setCurrentItemToNotebook(nb);
}

void VNotebookSelector::deleteNotebook()
{
    QList<QListWidgetItem *> items = m_listWidget->selectedItems();
    if (items.isEmpty()) {
        return;
    }

    Q_ASSERT(items.size() == 1);

    VNotebook *notebook = getNotebook(items[0]);
    Q_ASSERT(notebook);

    VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this);
    if (dialog.exec() == QDialog::Accepted) {
        bool deleteFiles = dialog.getDeleteFiles();
        g_mainWin->getEditArea()->closeFile(notebook, true);
        deleteNotebook(notebook, deleteFiles);
    }
}

void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
{
    Q_ASSERT(p_notebook);

    m_notebooks.removeOne(p_notebook);
    g_config->setNotebooks(m_notebooks);

    int idx = itemIndexOfNotebook(p_notebook);
    QListWidgetItem *item = m_listWidget->takeItem(idx);
    Q_ASSERT(item);
    delete item;

    QString name(p_notebook->getName());
    QString path(p_notebook->getPath());
    bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles);
    if (!ret) {
        // Notebook could not be deleted completely.
        int cho = VUtils::showMessage(QMessageBox::Information,
                                      tr("Delete Notebook Folder From Disk"),
                                      tr("Fail to delete the root folder of notebook "
                                         "<span style=\"%1\">%2</span> from disk. You may open "
                                         "the folder and check it manually.")
                                        .arg(g_config->c_dataTextStyle).arg(name),
                                      "",
                                      QMessageBox::Open | QMessageBox::Ok,
                                      QMessageBox::Ok,
                                      this);
        if (cho == QMessageBox::Open) {
            // Open the notebook location.
            QUrl url = QUrl::fromLocalFile(path);
            QDesktopServices::openUrl(url);
        }
    }

    if (m_notebooks.isEmpty()) {
        m_muted = true;
        setCurrentIndex(0);
        m_muted = false;
    }
}

void VNotebookSelector::editNotebookInfo()
{
    QList<QListWidgetItem *> items = m_listWidget->selectedItems();
    if (items.isEmpty()) {
        return;
    }

    Q_ASSERT(items.size() == 1);

    VNotebook *notebook = getNotebook(items[0]);
    VNotebookInfoDialog dialog(tr("Notebook Information"),
                               "",
                               notebook,
                               m_notebooks,
                               this);
    if (dialog.exec() == QDialog::Accepted) {
        bool updated = false;
        bool configUpdated = false;
        QString name = dialog.getName();
        if (name != notebook->getName()) {
            updated = true;
            notebook->rename(name);
            g_config->setNotebooks(m_notebooks);
        }

        QString imageFolder = dialog.getImageFolder();
        if (imageFolder != notebook->getImageFolderConfig()) {
            configUpdated = true;
            notebook->setImageFolder(imageFolder);
        }

        if (configUpdated) {
            updated = true;
            notebook->writeConfigNotebook();
        }

        if (updated) {
            fillItem(items[0], notebook);
            emit notebookUpdated(notebook);
        }
    }
}

void VNotebookSelector::addNotebookItem(const VNotebook *p_notebook)
{
    QListWidgetItem *item = new QListWidgetItem(m_listWidget);
    fillItem(item, p_notebook);
}

void VNotebookSelector::fillItem(QListWidgetItem *p_item,
                                 const VNotebook *p_notebook) const
{
    p_item->setText(p_notebook->getName());
    p_item->setToolTip(p_notebook->getName());
    p_item->setIcon(VIconUtils::comboBoxIcon(":/resources/icons/notebook_item.svg"));
    p_item->setData(Qt::UserRole, (qulonglong)p_notebook);
}


void VNotebookSelector::popupListContextMenuRequested(QPoint p_pos)
{
    QListWidgetItem *item = m_listWidget->itemAt(p_pos);
    if (!item) {
        return;
    }

    const VNotebook *nb = getNotebook(item);
    if (!nb) {
        return;
    }

    m_listWidget->clearSelection();
    item->setSelected(true);

    QMenu menu(this);
    menu.setToolTipsVisible(true);
    menu.addAction(m_deleteNotebookAct);
    if (nb->isValid()) {
        menu.addSeparator();
        menu.addAction(m_recycleBinAct);
        menu.addAction(m_emptyRecycleBinAct);
    }

    menu.addSeparator();
    menu.addAction(m_openLocationAct);

    if (nb->isValid()) {
        menu.addAction(m_notebookInfoAct);
    }

    menu.exec(m_listWidget->mapToGlobal(p_pos));
}

bool VNotebookSelector::eventFilter(QObject *watched, QEvent *event)
{
    QEvent::Type type = event->type();
    if (type == QEvent::KeyPress && watched == m_listWidget) {
        if (handlePopupKeyPress(static_cast<QKeyEvent *>(event))) {
            return true;
        }
    } else if (type == QEvent::MouseButtonRelease) {
        if (static_cast<QMouseEvent *>(event)->button() == Qt::RightButton) {
            return true;
        }
    }

    return QComboBox::eventFilter(watched, event);
}

bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook)
{
    bool ret = false;
    int index = itemIndexOfNotebook(p_notebook);
    if (index > -1) {
        setCurrentIndex(index);
        ret = true;
    }

    return ret;
}

void VNotebookSelector::showPopup()
{
    if (m_notebooks.isEmpty()) {
        // No normal notebook items. Just add notebook.
        newNotebook();
        return;
    }

    resizeListWidgetToContent();
    QComboBox::showPopup();
}

void VNotebookSelector::resizeListWidgetToContent()
{
    static QRect screenRect = QGuiApplication::primaryScreen()->geometry();
    static int maxMinWidth = screenRect.width() < 400 ? screenRect.width() : screenRect.width() / 2;
    static int maxMinHeight = screenRect.height() < 400 ? screenRect.height() : screenRect.height() / 2;

    int minWidth = 0;
    int minHeight = 0;
    if (m_listWidget->count() > 0) {
        // Width
        minWidth = m_listWidget->sizeHintForColumn(0);
        minWidth = qMin(minWidth, maxMinWidth);

        // Height
        minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10;
        minHeight = qMin(minHeight, maxMinHeight);
    }

    m_listWidget->setMinimumSize(minWidth, minHeight);
}

void VNotebookSelector::registerNavigation(QChar p_majorKey)
{
    Q_ASSERT(!m_naviLabel);
    m_majorKey = p_majorKey;
}

void VNotebookSelector::showNavigation()
{
    if (!isVisible()) {
        return;
    }

    V_ASSERT(!m_naviLabel);
    m_naviLabel = new QLabel(m_majorKey, this);
    m_naviLabel->setStyleSheet(g_vnote->getNavigationLabelStyle(m_majorKey));
    m_naviLabel->show();
}

void VNotebookSelector::hideNavigation()
{
    if (m_naviLabel) {
        delete m_naviLabel;
        m_naviLabel = NULL;
    }
}

bool VNotebookSelector::handleKeyNavigation(int p_key, bool &p_succeed)
{
    bool ret = false;
    p_succeed = false;
    QChar keyChar = VUtils::keyToChar(p_key);
    if (keyChar == m_majorKey) {
        // Hit.
        p_succeed = true;
        ret = true;
        if (m_naviLabel) {
            showPopup();
        }
    }
    return ret;
}

bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
{
    if (VimNavigationForWidget::injectKeyPressEventForVim(m_listWidget,
                                                          p_event)) {
        return true;
    }

    return false;
}

VNotebook *VNotebookSelector::getNotebook(int p_itemIdx) const
{
    VNotebook *nb = NULL;
    QListWidgetItem *item = m_listWidget->item(p_itemIdx);
    if (item) {
        nb = (VNotebook *)item->data(Qt::UserRole).toULongLong();
    }

    return nb;
}

VNotebook *VNotebookSelector::getNotebook(const QListWidgetItem *p_item) const
{
    if (p_item) {
        return (VNotebook *)p_item->data(Qt::UserRole).toULongLong();
    }

    return NULL;
}
