#include "StalledIssuesDialog.h"

#include "DialogOpener.h"
#include "MegaApplication.h"
#include "ServiceUrls.h"
#include "StalledIssue.h"
#include "StalledIssueDelegate.h"
#include "StalledIssuesModel.h"
#include "StalledIssuesProxyModel.h"
#include "ThemeManager.h"
#include "TokenizableItems/TokenPropertySetter.h"
#include "ui_StalledIssuesDialog.h"
#include "Utilities.h"

const char* MODE_SELECTED = "SELECTED";
const char* SCROLL = "scroll";

StalledIssuesDialog::StalledIssuesDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::StalledIssuesDialog),
    mCurrentTab(StalledIssueFilterCriterion::ALL_ISSUES),
    mProxyModel(nullptr),
    mDelegate(nullptr)
{
    ui->setupUi(this);
#ifndef Q_OS_MACOS
    Qt::WindowFlags flags =  Qt::Window;
    this->setWindowFlags(flags);
#endif

    setAttribute(Qt::WA_DeleteOnClose, true);

    connect(MegaSyncApp->getStalledIssuesModel(),
            &StalledIssuesModel::uiBlocked,
            this,
            &StalledIssuesDialog::onUiBlocked);
    connect(MegaSyncApp->getStalledIssuesModel(),
            &StalledIssuesModel::uiUnblocked,
            this,
            &StalledIssuesDialog::onUiUnblocked);

    connect(MegaSyncApp->getStalledIssuesModel(),
            &StalledIssuesModel::stalledIssuesReceived,
            this,
            &StalledIssuesDialog::onStalledIssuesLoaded);

    // Init all categories
    ui->allIssuesTab->setSelected(true);

    // connect all tabs
    auto tabs = TabSelector::getTabSelectorByParent(ui->header);
    for (auto& tab: tabs)
    {
        connect(tab, &TabSelector::clicked, this, &StalledIssuesDialog::onTabToggled);
    }

    createTabTitles();

    connect(MegaSyncApp->getStalledIssuesModel(),
            &StalledIssuesModel::stalledIssuesCountChanged,
            this,
            &StalledIssuesDialog::createTabTitles);

    mProxyModel = new StalledIssuesProxyModel(this);
    mProxyModel->setSourceModel(MegaSyncApp->getStalledIssuesModel());
    connect(mProxyModel,
            &StalledIssuesProxyModel::modelFiltered,
            this,
            &StalledIssuesDialog::onModelFiltered);

    connect(MegaSyncApp->getStalledIssuesModel(),
            &StalledIssuesModel::updateLoadingMessage,
            ui->stalledIssuesTree->getLoadingMessageHandler(),
            &LoadingSceneMessageHandler::updateMessage,
            Qt::QueuedConnection);
    connect(ui->stalledIssuesTree->getLoadingMessageHandler(),
            &LoadingSceneMessageHandler::buttonPressed,
            this,
            [](MessageInfo::ButtonType buttonType)
            {
                MegaSyncApp->getStalledIssuesModel()->stopSolvingIssues(buttonType);
            });

    mDelegate = new StalledIssueDelegate(mProxyModel, ui->stalledIssuesTree);
    ui->stalledIssuesTree->setItemDelegate(mDelegate);
    connect(mDelegate,
            &StalledIssueDelegate::goToIssue,
            this,
            &StalledIssuesDialog::toggleTabAndScroll);
    connect(&ui->stalledIssuesTree->loadingView(),
            &ViewLoadingSceneBase::sceneVisibilityChange,
            this,
            &StalledIssuesDialog::onLoadingSceneVisibilityChange);

    connect(ui->SettingsButton,
            &QPushButton::clicked,
            this,
            []()
            {
                MegaSyncApp->openSettings(SettingsDialog::SYNCS_TAB);
            });

    connect(ui->HelpButton,
            &QPushButton::clicked,
            this,
            []()
            {
                Utilities::openUrl(ServiceUrls::getSyncHelpUrl());
            });

    connect(SyncInfo::instance(),
            &SyncInfo::syncRemoteRootChanged,
            this,
            &StalledIssuesDialog::onSyncRootChanged);

    // Init value
    setScrollMode(false);

    connect(ui->stalledIssuesTree->verticalScrollBar(),
            &QScrollBar::rangeChanged,
            this,
            &StalledIssuesDialog::onScrollRangeChanged);

    showView();

    if(MegaSyncApp->getStalledIssuesModel()->issuesRequested())
    {
        onUiBlocked();
    }

    BaseTokens iconTokens;
    iconTokens.setNormalOff(QLatin1String("icon-secondary"));
    iconTokens.setNormalOn(QLatin1String("brand-on-container"));
    auto iconTokensSetter = std::make_shared<TokenPropertySetter>(iconTokens);
    TabSelector::applyTokens(ui->categories, iconTokensSetter);
}

StalledIssuesDialog::~StalledIssuesDialog()
{
    delete ui;
}

QModelIndexList StalledIssuesDialog::getSelection(QList<mega::MegaSyncStall::SyncStallReason> reasons) const
{
    auto checkerFunc = [reasons](const std::shared_ptr<const StalledIssue> check) -> bool{
        return reasons.contains(check->getReason());
    };

    return getSelection(checkerFunc);
}

QModelIndexList StalledIssuesDialog::getSelection(std::function<bool (const std::shared_ptr<const StalledIssue>)> checker) const
{
    QModelIndexList list;

    auto selectedIndexes = ui->stalledIssuesTree->selectionModel()->selectedIndexes();
    foreach(auto index, selectedIndexes)
    {
        //Just in case, but children is never selected
        if(!index.parent().isValid())
        {
            QModelIndex sourceIndex = mProxyModel->mapToSource(index);
            if(sourceIndex.isValid())
            {
                auto stalledIssueItem (qvariant_cast<StalledIssueVariant>(sourceIndex.data(Qt::DisplayRole)));
                if(!stalledIssueItem.consultData()->isSolved() && checker(stalledIssueItem.consultData()))
                {
                    list.append(sourceIndex);
                }
            }
        }
    }

    return list;
}

void StalledIssuesDialog::mouseReleaseEvent(QMouseEvent *event)
{
    // User cliked outside the view, but never while it is filtering
    if (!ui->stalledIssuesTree->loadingView().isLoadingViewSet())
    {
        ui->stalledIssuesTree->clearSelection();
    }

    QDialog::mouseReleaseEvent(event);
}

void StalledIssuesDialog::on_doneButton_clicked()
{
    close();
}

void StalledIssuesDialog::on_refreshButton_clicked()
{
    mProxyModel->updateStalledIssues();

    if(auto proxyModel = dynamic_cast<StalledIssuesProxyModel*>(ui->stalledIssuesTree->model()))
    {
        if(proxyModel->filterCriterion() == StalledIssueFilterCriterion::FAILED_CONFLICTS)
        {
            toggleTabAndScroll(StalledIssueFilterCriterion::ALL_ISSUES, QModelIndex());
        }
    }
}

void StalledIssuesDialog::checkIfViewIsEmpty()
{
    if (auto proxyModel = dynamic_cast<StalledIssuesProxyModel*>(ui->stalledIssuesTree->model()))
    {
        if (!ui->stalledIssuesTree->loadingView().isLoadingViewSet())
        {
            auto isEmpty = proxyModel->rowCount(QModelIndex()) == 0;
            ui->TreeViewContainer->setCurrentWidget(isEmpty ? ui->EmptyViewContainerPage :
                                                              ui->TreeViewContainerPage);
        }
    }
}

void StalledIssuesDialog::onSyncRootChanged(std::shared_ptr<SyncSettings> sync)
{
    auto areSyncIssues = [sync](const std::shared_ptr<const StalledIssue> issue)
    {
        return issue->syncIds().contains(sync->backupId());
    };
    auto syncIssues = MegaSyncApp->getStalledIssuesModel()->getIssues(areSyncIssues);

    if (!syncIssues.isEmpty())
    {
        auto refreshLogic = [this]()
        {
            DialogOpener::closeDialogsByParentClass<StalledIssuesDialog>();

            MessageDialogInfo msgInfo;
            msgInfo.textFormat = Qt::RichText;
            msgInfo.buttons = QMessageBox::Ok;
            QMap<QMessageBox::StandardButton, QString> buttonsText;
            buttonsText.insert(QMessageBox::Ok, tr("Refresh"));
            msgInfo.buttonsText = buttonsText;
            msgInfo.descriptionText =
                tr("One of your synced folders has been renamed. Refresh the list of sync issues.");
            msgInfo.finishFunc = [this](QPointer<MessageDialogResult>)
            {
                mProxyModel->updateStalledIssues();
            };

            MegaSyncApp->getStalledIssuesModel()->runMessageBox(std::move(msgInfo));
        };

        if (MegaSyncApp->getStalledIssuesModel()->isSolvingIssues())
        {
            // First stop solving issues as they are solved in another thread
            MegaSyncApp->getStalledIssuesModel()->stopSolvingIssues(MessageInfo::ButtonType::STOP);
            connect(MegaSyncApp->getStalledIssuesModel(),
                    &StalledIssuesModel::stalledIssuesSolvingFinished,
                    this,
                    refreshLogic);
        }
        else
        {
            refreshLogic();
        }
    }
}

void StalledIssuesDialog::onTabToggled()
{
    if (sender())
    {
        if (auto proxyModel =
                dynamic_cast<StalledIssuesProxyModel*>(ui->stalledIssuesTree->model()))
        {
            // Show the view to show the loading view
            ui->TreeViewContainer->setCurrentWidget(ui->TreeViewContainerPage);

            auto filterCriterion = static_cast<StalledIssueFilterCriterion>(
                sender()->property("filterCriterion").toInt());
            proxyModel->filter(filterCriterion);
        }
    }
}

bool StalledIssuesDialog::toggleTabAndScroll(
    StalledIssueFilterCriterion filterCriterion, const QModelIndex& sourceIndex)
{
    if(auto proxyModel = dynamic_cast<StalledIssuesProxyModel*>(ui->stalledIssuesTree->model()))
    {
        if(proxyModel->filterCriterion() != filterCriterion)
        {
            //Show the view to show the loading view
            ui->TreeViewContainer->setCurrentWidget(ui->TreeViewContainerPage);
            proxyModel->filter(filterCriterion);

            TabSelector::selectTabIf(ui->header,
                                     "filterCriterion",
                                     static_cast<int>(filterCriterion));

            if(sourceIndex.isValid())
            {
                QObject* tempObject(new QObject());
                connect(proxyModel,
                    &StalledIssuesProxyModel::modelFiltered,
                    tempObject,
                    [this, proxyModel, tempObject, sourceIndex]()
                    {
                        auto proxyIndex(proxyModel->mapFromSource(sourceIndex));
                        if(proxyIndex.isValid())
                        {
                            ui->stalledIssuesTree->scrollTo(sourceIndex);
                            mDelegate->expandIssue(proxyIndex);
                        }
                        tempObject->deleteLater();
                    });
            }

            return true;
        }
    }

    return false;
}

void StalledIssuesDialog::onUiBlocked()
{
    if (!ui->stalledIssuesTree->loadingView().isLoadingViewSet())
    {
        ui->TreeViewContainer->setCurrentWidget(ui->TreeViewContainerPage);
    }

    ui->stalledIssuesTree->loadingView().toggleLoadingScene(true);
}

void StalledIssuesDialog::onUiUnblocked()
{
    ui->stalledIssuesTree->loadingView().toggleLoadingScene(false);
}

void StalledIssuesDialog::onStalledIssuesLoaded()
{
    mDelegate->resetCache();
    mProxyModel->updateFilter();
}

void StalledIssuesDialog::onModelFiltered()
{
    // Only the first time, in order to avoid setting the model before it is sorted.
    // NOTE: view->model() being null no longer means "first time": the loading scene detaches
    // the model while loading, so we must track the one-time init with an explicit flag and let
    // the loading scene own the detach/reattach (restoreLoadingViewState).
    if (!mViewModelInitialized)
    {
        mViewModelInitialized = true;
        ui->stalledIssuesTree->setModel(mProxyModel);
        mViewHoverManager.setView(ui->stalledIssuesTree);

        connect(ui->stalledIssuesTree->selectionModel(),
                &QItemSelectionModel::selectionChanged,
                mDelegate,
                &StalledIssueDelegate::onSelectionChanged);
    }

    createTabTitles();
}

void StalledIssuesDialog::onLoadingSceneVisibilityChange(bool state)
{
    ui->footer->setDisabled(state);
    ui->header->setDisabled(state);

    if (!state)
    {
        checkIfViewIsEmpty();
    }
}

void StalledIssuesDialog::onScrollRangeChanged(int, int max)
{
    auto currentScrollMode(ui->footer->property(SCROLL).toBool());
    auto newScrollMode(max > 0);

    if (newScrollMode != currentScrollMode)
    {
        setScrollMode(newScrollMode);
    }
}

void StalledIssuesDialog::showView()
{
    on_refreshButton_clicked();
}

void StalledIssuesDialog::setScrollMode(bool state)
{
    ui->footer->setProperty(SCROLL, state);
    ui->footer->setStyleSheet(ui->footer->styleSheet());
}

int StalledIssuesDialog::getFilterCriterionFromChip(QWidget* tab)
{
    return MegaSyncApp->getStalledIssuesModel()->getCountByFilterCriterion(
        static_cast<StalledIssueFilterCriterion>(tab->property("filterCriterion").toInt()));
}

void StalledIssuesDialog::createTabTitles()
{
    ui->allIssuesTab->setTitle(
        QApplication::translate("StalledIssueTab", "All issues: %1")
            .arg(MegaSyncApp->getStalledIssuesModel()->getCountByFilterCriterion(
                static_cast<StalledIssueFilterCriterion>(
                    ui->allIssuesTab->property("filterCriterion").toInt()))));

    int nameConflictsCount = getFilterCriterionFromChip(ui->nameConflictsTab);
    ui->nameConflictsTab->setTitle(
        QApplication::translate("StalledIssueTab", "Name conflict: %n", "", nameConflictsCount));

    int itemTypeCount = getFilterCriterionFromChip(ui->itemTypeTab);
    ui->itemTypeTab->setTitle(
        QApplication::translate("StalledIssueTab", "Item type conflict: %n", "", itemTypeCount));

    int otherTypeCount = getFilterCriterionFromChip(ui->otherTab);
    ui->otherTab->setTitle(
        QApplication::translate("StalledIssueTab", "Other: %n", "", otherTypeCount));

    int failedCount = getFilterCriterionFromChip(ui->failedTab);
    ui->failedTab->setTitle(
        QApplication::translate("StalledIssueTab", "Failed: %n", "", failedCount));

    int solveCount = getFilterCriterionFromChip(ui->solvedIssuesTab);
    ui->solvedIssuesTab->setTitle(
        QApplication::translate("StalledIssueTab", "Resolved: %n", "", solveCount));
}

void StalledIssuesDialog::onGlobalSyncStateChanged(bool)
{
    //For the future, detect if the stalled issues have been removed remotely to close the dialog
}

bool StalledIssuesDialog::event(QEvent* event)
{
    if (event->type() == QEvent::LanguageChange)
    {
        ui->retranslateUi(this);
        MegaSyncApp->getStalledIssuesModel()->languageChanged();
        ui->stalledIssuesTree->update();
        createTabTitles();
    }
    else if (event->type() == ThemeManager::ThemeChanged)
    {
        ui->stalledIssuesTree->viewport()->update();
    }

    return QDialog::event(event);
}
