/*
*  Copyright 2019  Michail Vourlakos <mvourlakos@gmail.com>
*
*  This file is part of Latte-Dock
*
*  Latte-Dock is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License as
*  published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*
*  Latte-Dock 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 General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "synchronizer.h"

//! local
#include "importer.h"
#include "manager.h"
#include "../apptypes.h"
#include "../data/layoutdata.h"
#include "../lattecorona.h"
#include "../layout/centrallayout.h"
#include "../layout/genericlayout.h"
#include "../settings/universalsettings.h"
#include "../templates/templatesmanager.h"
#include "../view/view.h"

// Qt
#include <QDir>
#include <QFile>

// Plasma
#include <Plasma/Containment>

// KDE
#include <KActivities/Consumer>
#include <KActivities/Controller>
#include <KWindowSystem>

#define LAYOUTSINITINTERVAL 350

namespace Latte {
namespace Layouts {

Synchronizer::Synchronizer(QObject *parent)
    : QObject(parent),
      m_activitiesController(new KActivities::Controller)
{
    m_manager = qobject_cast<Manager *>(parent);

    connect(this, &Synchronizer::layoutsChanged, this, &Synchronizer::reloadAssignedLayouts);

    //! KWin update Disabled Borders
    connect(this, &Synchronizer::centralLayoutsChanged, this, &Synchronizer::updateKWinDisabledBorders);
    connect(m_manager->corona()->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, &Synchronizer::updateKWinDisabledBorders);


    //! KActivities tracking
    connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::activityRemoved,
            this, &Synchronizer::onActivityRemoved);

    connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged,
            this, &Synchronizer::onCurrentActivityChanged);

    connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged,
            this, [&]() {
        if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
            syncMultipleLayoutsToActivities();
        }
    });
}

Synchronizer::~Synchronizer()
{
    m_activitiesController->deleteLater();
}

KActivities::Controller *Synchronizer::activitiesController() const
{
    return m_activitiesController;
}

bool Synchronizer::latteViewExists(Latte::View *view) const
{
    for (const auto layout : m_centralLayouts) {
        for (const auto &v : layout->latteViews()) {
            if (v == view) {
                return true;
            }
        }
    }

    return false;
}

bool Synchronizer::layoutExists(QString layoutName) const
{
    return m_layouts.containsName(layoutName);
}


bool Synchronizer::isAssigned(QString layoutName) const
{
    for(auto activityid : m_assignedLayouts.keys()) {
        if (m_assignedLayouts[activityid].contains(layoutName)) {
            return true;
        }
    }

    return false;
}

int Synchronizer::centralLayoutPos(QString id) const
{
    for (int i = 0; i < m_centralLayouts.size(); ++i) {
        CentralLayout *layout = m_centralLayouts.at(i);

        if (layout->name() == id) {
            return i;
        }
    }

    return -1;
}

QString Synchronizer::layoutPath(QString layoutName)
{
    QString path = Layouts::Importer::layoutUserFilePath(layoutName);

    if (!QFile(path).exists()) {
        path = "";
    }

    return path;
}

QStringList Synchronizer::activities()
{
    return m_manager->corona()->activitiesConsumer()->activities();
}

QStringList Synchronizer::freeActivities()
{
    QStringList frees = activities();

    for(auto assigned : m_assignedLayouts.keys()) {
        frees.removeAll(assigned);
    }

    return frees;
}

QStringList Synchronizer::runningActivities()
{
    return m_manager->corona()->activitiesConsumer()->runningActivities();
}

QStringList Synchronizer::freeRunningActivities()
{
    QStringList fActivities;

    for (const auto &activity : runningActivities()) {
        if (!m_assignedLayouts.contains(activity)) {
            fActivities.append(activity);
        }
    }

    return fActivities;
}

QStringList Synchronizer::validActivities(const QStringList &layoutActivities)
{
    QStringList valids;
    QStringList allactivities = activities();

    for(auto activity : layoutActivities) {
        if (allactivities.contains(activity)) {
            valids << activity;
        }
    }

    return valids;
}

QStringList Synchronizer::centralLayoutsNames()
{
    QStringList names;

    if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
        names << m_centralLayouts.at(0)->name();
    } else {
        for (int i = 0; i < m_centralLayouts.size(); ++i) {
            CentralLayout *layout = m_centralLayouts.at(i);
            names << layout->name();
        }
    }

    return names;
}

QStringList Synchronizer::currentLayoutsNames() const
{
    QList<CentralLayout *> currents = currentLayouts();
    QStringList currentNames;

    for (int i = 0; i < currents.size(); ++i) {
        CentralLayout *layout = currents.at(i);
        currentNames << layout->name();
    }

    return currentNames;
}

QStringList Synchronizer::layouts() const
{
    return m_layouts.names();
}

QStringList Synchronizer::menuLayouts() const
{
    QStringList menulayouts;

    for (int i=0; i<m_layouts.rowCount(); ++i) {
        if (!m_layouts[i].isShownInMenu) {
            continue;
        }

        menulayouts << m_layouts[i].name;
    }

    for (const auto layout : m_centralLayouts) {
        if (!menulayouts.contains(layout->name())) {
            menulayouts.prepend(layout->name());
        }
    }

    return menulayouts;
}

void Synchronizer::setIsSingleLayoutInDeprecatedRenaming(const bool &enabled)
{
    m_isSingleLayoutInDeprecatedRenaming = enabled;
}

Data::LayoutsTable Synchronizer::layoutsTable() const
{
    return m_layouts;
}

void Synchronizer::setLayoutsTable(const Data::LayoutsTable &table)
{
    if (m_layouts == table) {
        return;
    }

    m_layouts = table;
    emit layoutsChanged();
}

void Synchronizer::updateLayoutsTable()
{
    for (int i = 0; i < m_centralLayouts.size(); ++i) {
        CentralLayout *layout = m_centralLayouts.at(i);

        if (m_layouts.containsId(layout->file())) {
            m_layouts[layout->file()] = layout->data();
        }
    }

    for (int i = 0; i < m_layouts.rowCount(); ++i) {
        if ((m_layouts[i].errors>0 || m_layouts[i].warnings>0) && !m_layouts[i].isActive) {
            CentralLayout central(this, m_layouts[i].id);
            m_layouts[i].errors = central.errors().count();
            m_layouts[i].warnings = central.warnings().count();
        }
    }
}

CentralLayout *Synchronizer::centralLayout(QString layoutname) const
{
    for (int i = 0; i < m_centralLayouts.size(); ++i) {
        CentralLayout *layout = m_centralLayouts.at(i);

        if (layout->name() == layoutname) {
            return layout;
        }
    }

    return nullptr;
}

QList<CentralLayout *> Synchronizer::currentLayouts() const
{
    QList<CentralLayout *> layouts;

    if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
        layouts << m_centralLayouts.at(0);
    } else {
        for (auto layout : m_centralLayouts) {
            if (layout->isOnAllActivities() || layout->appliedActivities().contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
                layouts << layout;
            }
        }
    }

    return layouts;
}

QList<CentralLayout *> Synchronizer::centralLayoutsForActivity(const QString activityid) const
{
    QList<CentralLayout *> layouts;

    if (m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.count() >= 1) {
        layouts << m_centralLayouts.at(0);
    } else {
        for (auto layout : m_centralLayouts) {
            if (layout->isOnAllActivities() || layout->appliedActivities().contains(activityid)) {
                layouts << layout;
            }
        }
    }

    return layouts;
}

QList<Latte::View *> Synchronizer::currentViews() const
{
    QList<Latte::View *> views;

    for(auto layout : currentLayouts()) {
        views << layout->latteViews();
    }

    return views;
}

QList<Latte::View *> Synchronizer::currentViewsWithPlasmaShortcuts() const
{
    QList<Latte::View *> views;

    for(auto layout : currentLayouts()) {
        views << layout->viewsWithPlasmaShortcuts();
    }

    return views;
}

QList<Latte::View *> Synchronizer::sortedCurrentViews() const
{
    QList<Latte::View *> views = currentViews();

    return Layout::GenericLayout::sortedLatteViews(views);
}

QList<Latte::View *> Synchronizer::viewsBasedOnActivityId(const QString &id) const
{
    QList<Latte::View *> views;

    for(auto layout : centralLayoutsForActivity(id)) {
        if (m_centralLayouts.contains(layout)) {
            views << layout->latteViews();
        }
    }

    return views;
}

Layout::GenericLayout *Synchronizer::layout(QString layoutname) const
{
    Layout::GenericLayout *l = centralLayout(layoutname);

    return l;
}

int Synchronizer::screenForContainment(Plasma::Containment *containment)
{
    for (auto layout : m_centralLayouts) {
        if (layout->contains(containment)) {
            return layout->screenForContainment(containment);
        }
    }

    return -1;
}

Latte::View *Synchronizer::viewForContainment(uint id)
{
    for (auto layout : m_centralLayouts) {
        Latte::View *view = layout->viewForContainment(id);

        if (view) {
            return view;
        }
    }

    return nullptr;
}

Latte::View *Synchronizer::viewForContainment(Plasma::Containment *containment)
{
    for (auto layout : m_centralLayouts) {
        Latte::View *view = layout->viewForContainment(containment);

        if (view) {
            return view;
        }
    }

    return nullptr;
}

void Synchronizer::addLayout(CentralLayout *layout)
{
    if (!m_centralLayouts.contains(layout)) {
        m_centralLayouts.append(layout);
        layout->initToCorona(m_manager->corona());
    }
}

void Synchronizer::onActivityRemoved(const QString &activityid)
{
    if (!m_assignedLayouts.contains(activityid)) {
        return;
    }

    //! remove any other explicit set layouts for the current activity
    QStringList explicits = m_assignedLayouts[activityid];

    for(auto explicitlayoutname : explicits) {
        QString explicitlayoutid = m_layouts.idForName(explicitlayoutname);

        m_layouts[explicitlayoutid].activities.removeAll(activityid);
        m_manager->setOnActivities(explicitlayoutname, m_layouts[explicitlayoutid].activities);
        emit layoutActivitiesChanged(m_layouts[explicitlayoutid]);
    }

    QStringList freelayoutnames;

    if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
        freelayoutnames = m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
    }

    reloadAssignedLayouts();

    for(auto freelayoutname : freelayoutnames) {
        //! inform free activities layouts that their activities probably changed
        CentralLayout *central = centralLayout(freelayoutname);

        if (central) {
            emit central->activitiesChanged();
        }
    }
}

void Synchronizer::onCurrentActivityChanged(const QString &activityid)
{
    if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
        updateKWinDisabledBorders();
    }
}

void Synchronizer::hideAllViews()
{
    for (const auto layout : m_centralLayouts) {
        emit currentLayoutIsSwitching(layout->name());
    }
}

void Synchronizer::pauseLayout(QString layoutName)
{
    if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
        CentralLayout *layout = centralLayout(layoutName);

        if (layout->isOnAllActivities()) {
            return;
        }

        QStringList appliedactivities = layout->appliedActivities();

        if (layout && !appliedactivities.isEmpty()) {
            int i = 0;

            for (const auto &activityid : appliedactivities) {
                //! Stopping the activities must be done asynchronous because otherwise
                //! the activity manager cant close multiple activities
                QTimer::singleShot(i * 1000, [this, activityid]() {
                    m_activitiesController->stopActivity(activityid);
                });

                i = i + 1;
            }
        }
    }
}

void Synchronizer::syncActiveLayoutsToOriginalFiles()
{
    if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
        for (const auto layout : m_centralLayouts) {
            layout->syncToLayoutFile();
        }
    }
}

void Synchronizer::syncLatteViewsToScreens()
{
    for (const auto layout : m_centralLayouts) {
        layout->syncLatteViewsToScreens();
    }
}

void Synchronizer::unloadCentralLayout(CentralLayout *layout)
{
    int pos = m_centralLayouts.indexOf(layout);

    if (pos>=0) {
        CentralLayout *central = m_centralLayouts.takeAt(pos);

        if (m_multipleModeInitialized) {
            central->syncToLayoutFile(true);
        }

        central->unloadLatteViews();
        central->unloadContainments();

        if (m_multipleModeInitialized) {
            m_manager->clearUnloadedContainmentsFromLinkedFile(central->unloadedContainmentsIds(), true);
        }

        delete central;
    }
}

void Synchronizer::initLayouts()
{
    m_layouts.clear();

    QDir layoutDir(Layouts::Importer::layoutUserDir());
    QStringList filter;
    filter.append(QString("*.layout.latte"));
    QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);

    for (const auto &layout : files) {
        if (layout.contains(Layout::MULTIPLELAYOUTSHIDDENNAME)) {
            //! IMPORTANT: DON'T ADD MultipleLayouts hidden file in layouts list
            continue;
        }

        QString layoutpath = layoutDir.absolutePath() + "/" + layout;
        onLayoutAdded(layoutpath);
    }

    emit layoutsChanged();

    if (!m_isLoaded) {
        m_isLoaded = true;
        connect(m_manager->corona()->templatesManager(), &Latte::Templates::Manager::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
        connect(m_manager->importer(), &Latte::Layouts::Importer::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
    }
}

void Synchronizer::onLayoutAdded(const QString &layout)
{
    CentralLayout centrallayout(this, layout);
    m_layouts.insertBasedOnName(centrallayout.data());

    if (m_isLoaded) {
        emit layoutsChanged();
    }
}

void Synchronizer::reloadAssignedLayouts()
{
    m_assignedLayouts.clear();

    for (int i=0; i< m_layouts.rowCount(); ++i) {
        for (const auto &activity : m_layouts[i].activities) {
            if (m_assignedLayouts.contains(activity)) {
                m_assignedLayouts[activity] << m_layouts[i].name;
            } else {
                m_assignedLayouts[activity] = QStringList(m_layouts[i].name);
            }
        }
    }
}

void Synchronizer::unloadLayouts()
{
    //! Unload all CentralLayouts
    while (!m_centralLayouts.isEmpty()) {
        CentralLayout *layout = m_centralLayouts.at(0);
        unloadCentralLayout(layout);
    }

    m_multipleModeInitialized = false;
}

bool Synchronizer::memoryInitialized() const
{
    return ((m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.size()>0)
            || (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts && m_multipleModeInitialized));
}

bool Synchronizer::initSingleMode(QString layoutName)
{
    QString layoutpath = layoutName.isEmpty() ? layoutPath(m_manager->corona()->universalSettings()->singleModeLayoutName()) : layoutPath(layoutName);

    if (layoutpath.isEmpty()) {
        qDebug() << "Layout : " << layoutName << " was not found...";
        return false;
    }

    if (m_centralLayouts.size() > 0) {
        emit currentLayoutIsSwitching(m_centralLayouts[0]->name());
    }

    //! this code must be called asynchronously because it can create crashes otherwise.
    //! Tasks plasmoid case that triggers layouts switching through its context menu
    QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutName, layoutpath]() {
        qDebug() << " ... initializing layout in single mode : " << layoutName << " - " << layoutpath;
        unloadLayouts();

        //! load the main layout/corona file
        CentralLayout *newLayout = new CentralLayout(this, layoutpath, layoutName);
        addLayout(newLayout);

        m_manager->loadLatteLayout(layoutpath);

        emit centralLayoutsChanged();

        if (m_isSingleLayoutInDeprecatedRenaming) {
            QString deprecatedlayoutpath = layoutPath(m_manager->corona()->universalSettings()->singleModeLayoutName());

            if (!deprecatedlayoutpath.isEmpty()) {
                qDebug() << "Removing Deprecated single layout after renaming:: " << m_manager->corona()->universalSettings()->singleModeLayoutName();
                QFile(deprecatedlayoutpath).remove();
            }

            m_isSingleLayoutInDeprecatedRenaming = false;
        }

        m_manager->corona()->universalSettings()->setSingleModeLayoutName(layoutName);
    });

    return true;
}

bool Synchronizer::initMultipleMode(QString layoutName)
{
    if (m_multipleModeInitialized) {
        return false;
    }

    for (const auto layout : m_centralLayouts) {
        emit currentLayoutIsSwitching(layout->name());
    }

    //! this code must be called asynchronously because it can create crashes otherwise.
    //! Tasks plasmoid case that triggers layouts switching through its context menu
    QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutName]() {
        qDebug() << " ... initializing layout in multiple mode : " << layoutName ;
        unloadLayouts();

        m_manager->loadLatteLayout(layoutPath(QString(Layout::MULTIPLELAYOUTSHIDDENNAME)));

        m_multipleModeInitialized = true;

        emit centralLayoutsChanged();

        if (!layoutName.isEmpty()) {
            switchToLayoutInMultipleModeBasedOnActivities(layoutName);
        }

        syncMultipleLayoutsToActivities();
    });

    return true;
}

bool Synchronizer::switchToLayoutInSingleMode(QString layoutName)
{
    if (!memoryInitialized() || m_manager->memoryUsage() != MemoryUsage::SingleLayout) {
        return false;
    }

    if (m_centralLayouts.size()>0 && m_centralLayouts[0]->name() == layoutName) {
        return true;
    }

    return initSingleMode(layoutName);
}

bool Synchronizer::switchToLayoutInMultipleModeBasedOnActivities(const QString &layoutName)
{
    Data::Layout layoutdata;
    CentralLayout *central = centralLayout(layoutName);

    if (central) {
        layoutdata = central->data();
    } else if (m_layouts.containsName(layoutName)) {
        QString layoutid = m_layouts.idForName(layoutName);
        CentralLayout storagedlayout(this, layoutid);
        layoutdata = storagedlayout.data();

        m_layouts[layoutid] = layoutdata;
    }

    if (layoutdata.isEmpty()) {
        return false;
    }

    QString switchToActivity;

    //! try to not remove activityids that belong to different machines that are not currently present
    QStringList validlayoutactivities = validActivities(layoutdata.activities);

    if (layoutdata.isOnAllActivities()) {
        //! no reason to switch in any activity;
    } else if (layoutdata.isForFreeActivities()) {
        //! free-activities case
        QStringList freerunningactivities = freeRunningActivities();

        if (freerunningactivities.count() > 0) {
            if (freerunningactivities.contains(layoutdata.lastUsedActivity)) {
                switchToActivity = layoutdata.lastUsedActivity;
            } else {
                switchToActivity = freerunningactivities[0];
            }
        } else {
            QStringList freepausedactivities = freeActivities();

            if (freepausedactivities.count() > 0) {
                switchToActivity = freepausedactivities[0];
            }
        }
    } else if (!validlayoutactivities.isEmpty())  {
        //! set on-explicit activities
        QStringList allactivities = activities();

        if (validlayoutactivities.contains(layoutdata.lastUsedActivity)) {
            switchToActivity = layoutdata.lastUsedActivity;
        } else {
            switchToActivity = validlayoutactivities[0];
        }
    } else if (validlayoutactivities.isEmpty() && m_layouts.containsName(layoutName)) {
        //! no-activities are set
        //! has not been set in any activities but nonetheless it is requested probably by the user
        //! requested layout is assigned explicitly in current activity and any remaining explicit layouts
        //! are removing current activity from their activities list
        QString layoutid = m_layouts.idForName(layoutName);
        QString currentactivityid = m_activitiesController->currentActivity();

        QStringList layoutIdsChanged;

        m_layouts[layoutid].activities.append(currentactivityid);
        m_manager->setOnActivities(layoutName, m_layouts[layoutid].activities);
        emit layoutActivitiesChanged(m_layouts[layoutid]);

        layoutIdsChanged << layoutid;

        if (m_assignedLayouts.contains(currentactivityid)) {
            //! remove any other explicit set layouts for the current activity
            QStringList explicits = m_assignedLayouts[currentactivityid];

            for(auto explicitlayoutname : explicits) {
                QString explicitlayoutid = m_layouts.idForName(explicitlayoutname);

                m_layouts[explicitlayoutid].activities.removeAll(currentactivityid);
                m_manager->setOnActivities(explicitlayoutname, m_layouts[explicitlayoutid].activities);
                emit layoutActivitiesChanged(m_layouts[explicitlayoutid]);
            }
        }

        QStringList freelayoutnames;
        if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
            freelayoutnames = m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
        }

        reloadAssignedLayouts();

        for(auto freelayoutname : freelayoutnames) {
            //! inform free activities layouts that their activities probably changed
            CentralLayout *central = centralLayout(freelayoutname);

            if (central) {
                emit central->activitiesChanged();
            }
        }
    }

    if (!switchToActivity.isEmpty()) {
        if (!m_manager->corona()->activitiesConsumer()->runningActivities().contains(switchToActivity)) {
            m_activitiesController->startActivity(switchToActivity);
        }

        m_activitiesController->setCurrentActivity(switchToActivity);
    }

    return true;
}

bool Synchronizer::switchToLayoutInMultipleMode(QString layoutName)
{
    if (!memoryInitialized() || m_manager->memoryUsage() != MemoryUsage::MultipleLayouts) {
        return false;
    }

    CentralLayout *layout = centralLayout(layoutName);

    if (layout) {
        QStringList appliedActivities = layout->appliedActivities();
        QString nextActivity = !layout->lastUsedActivity().isEmpty() ? layout->lastUsedActivity() : appliedActivities[0];

        if (!appliedActivities.contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
            //! it means we are at a foreign activity and we can switch to correct one
            m_activitiesController->setCurrentActivity(nextActivity);
            return true;
        }
    } else {
        if (!layoutName.isEmpty()) {
            switchToLayoutInMultipleModeBasedOnActivities(layoutName);
        }

        syncMultipleLayoutsToActivities();
    }

    return true;
}


bool Synchronizer::switchToLayout(QString layoutName, MemoryUsage::LayoutsMemory newMemoryUsage)
{
    qDebug() << " >>>>> SWITCHING >> " << layoutName << " __ from memory: " << m_manager->memoryUsage() << " to memory: " << newMemoryUsage;

    if (newMemoryUsage == MemoryUsage::Current) {
        newMemoryUsage = m_manager->memoryUsage();
    }

    if (!memoryInitialized() || newMemoryUsage != m_manager->memoryUsage()) {
        //! Initiate Layouts memory properly
        m_manager->setMemoryUsage(newMemoryUsage);

        return (newMemoryUsage == MemoryUsage::SingleLayout ? initSingleMode(layoutName) : initMultipleMode(layoutName));
    }

    if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
        return switchToLayoutInSingleMode(layoutName);
    } else {
        return switchToLayoutInMultipleMode(layoutName);
    }
}

void Synchronizer::syncMultipleLayoutsToActivities()
{
    qDebug() << "   ----  --------- ------    syncMultipleLayoutsToActivities       -------   ";
    qDebug() << "   ----  --------- ------    -------------------------------       -------   ";

    QStringList layoutNamesToUnload;
    QStringList layoutNamesToLoad;
    QStringList currentNames = centralLayoutsNames();

    //! discover OnAllActivities layouts
    if (m_assignedLayouts.contains(Data::Layout::ALLACTIVITIESID)) {
        layoutNamesToLoad << m_assignedLayouts[Data::Layout::ALLACTIVITIESID];
    }

    //! discover ForFreeActivities layouts
    if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID) && freeRunningActivities().count()>0) {
        layoutNamesToLoad << m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
    }

    //! discover layouts assigned to explicit activities based on running activities
    for (const auto &activity : runningActivities()) {
        if (KWindowSystem::isPlatformWayland() && (m_activitiesController->currentActivity() != activity)){
            //! Wayland Protection: Plasma wayland does not support yet Activities for windows
            //! but we can load the layouts that belong OnAllActivities + (ForFreeActivities OR SpecificActivity)
            continue;
        }

        if (m_assignedLayouts.contains(activity)) {
            layoutNamesToLoad << m_assignedLayouts[activity];
        }
    }

    //! discover layouts that must be unloaded because of running activities changes
    for (const auto layout : m_centralLayouts) {
        if (!layoutNamesToLoad.contains(layout->name())) {
            layoutNamesToUnload << layout->name();
        }
    }

    QString defaultForcedLayout;

    //! Safety
    if (layoutNamesToLoad.isEmpty()) {
        //! If no layout is found then force loading Default Layout
        QString layoutPath = m_manager->corona()->templatesManager()->newLayout("", i18n(Templates::DEFAULTLAYOUTTEMPLATENAME));
        layoutNamesToLoad << Layout::AbstractLayout::layoutName(layoutPath);
        m_manager->setOnAllActivities(layoutNamesToLoad[0]);
        defaultForcedLayout = layoutNamesToLoad[0];
    }

    QStringList newlyActivatedLayouts;

    //! Add needed Layouts based on Activities settings
    for (const auto &layoutname : layoutNamesToLoad) {
        if (!centralLayout(layoutname)) {
            CentralLayout *newLayout = new CentralLayout(this, QString(layoutPath(layoutname)), layoutname);

            if (newLayout) {
                qDebug() << "ACTIVATING LAYOUT ::::: " << layoutname;
                addLayout(newLayout);
                newLayout->importToCorona();

                if (!defaultForcedLayout.isEmpty() && defaultForcedLayout == layoutname) {
                    emit newLayoutAdded(newLayout->data());
                }

                newlyActivatedLayouts << newLayout->name();
            }
        }
    }

    if (newlyActivatedLayouts.count()>0 && m_manager->corona()->universalSettings()->showInfoWindow()) {
        m_manager->showInfoWindow(i18np("Activating layout: <b>%2</b> ...",
                                        "Activating layouts: <b>%2</b> ...",
                                        newlyActivatedLayouts.count(),
                                        newlyActivatedLayouts.join(", ")),
                4000, QStringList(Data::Layout::ALLACTIVITIESID));
    }

    //! Unload no needed Layouts

    //! hide layouts that will be removed in the end
    if (!layoutNamesToUnload.isEmpty()) {
        for (const auto layoutname : layoutNamesToUnload) {
            emit currentLayoutIsSwitching(layoutname);
        }

        QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutNamesToUnload]() {
            unloadLayouts(layoutNamesToUnload);
        });
    }

    qSort(currentNames);
    qSort(layoutNamesToLoad);

    if (currentNames != layoutNamesToLoad) {
        emit centralLayoutsChanged();
    }
}

void Synchronizer::unloadLayouts(const QStringList &layoutNames)
{
    if (layoutNames.isEmpty()) {
        return;
    }

    //! Unload no needed Layouts
    for (const auto &layoutname : layoutNames) {
        CentralLayout *layout = centralLayout(layoutname);
        int posLayout = centralLayoutPos(layoutname);

        if (posLayout >= 0) {
            qDebug() << "REMOVING LAYOUT ::::: " << layoutname;
            m_centralLayouts.removeAt(posLayout);

            layout->syncToLayoutFile(true);
            layout->unloadContainments();
            layout->unloadLatteViews();
            m_manager->clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds());
            delete layout;
        }
    }

    emit centralLayoutsChanged();
}

void Synchronizer::updateKWinDisabledBorders()
{
    if (KWindowSystem::isPlatformWayland()) {
        // BUG: https://bugs.kde.org/show_bug.cgi?id=428202
        // KWin::reconfigure() function blocks/freezes Latte under wayland
        return;
    }

    if (!m_manager->corona()->universalSettings()->canDisableBorders()) {
        m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
    } else {
        if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout) {
            m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(m_centralLayouts.at(0)->disableBordersForMaximizedWindows());
        } else if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
            QList<CentralLayout *> centrals = centralLayoutsForActivity(m_manager->corona()->activitiesConsumer()->currentActivity());

            for (int i = 0; i < centrals.size(); ++i) {
                CentralLayout *layout = centrals.at(i);

                if (layout->disableBordersForMaximizedWindows()) {
                    m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(true);
                    return;
                }
            }

            m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
        }
    }
}

}
} // end of namespace