/*
    This file is part of the KDE project
    SPDX-FileCopyrightText: 2020 Felix Ernst <felixernst@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#ifndef DOLPHINNAVIGATORSWIDGETACTION_H
#define DOLPHINNAVIGATORSWIDGETACTION_H

#include "dolphinurlnavigator.h"

#include <QPointer>
#include <QSplitter>
#include <QTimer>
#include <QWidgetAction>

#include <memory>

class KXmlGuiWindow;
class QPushButton;

/**
 * @brief QWidgetAction that allows to use DolphinUrlNavigators in a toolbar.
 *
 * This class is mainly a container that manages up to two DolphinUrlNavigator objects so they
 * can be added to a toolbar. It also deals with alignment.
 *
 * The structure of the defaultWidget() of this QWidgetAction is as follows:
 * - A QSplitter manages up to two sides which each correspond to one DolphinViewContainer.
 *      The secondary side only exists for split view and is created by
 *      createSecondaryUrlNavigator() when necessary.
 * - Each side is a QWidget which I call NavigatorWidget with a QHBoxLayout.
 * - Each NavigatorWidget consists an UrlNavigator, an emptyTrashButton, a
 *   networkFolderButton, and spacing.
 * - Only the primary navigatorWidget has leading spacing. Both have trailing spacing.
 *      The spacing is there to align the UrlNavigator with its DolphinViewContainer.
 */
class DolphinNavigatorsWidgetAction : public QWidgetAction
{
    Q_OBJECT

public:
    DolphinNavigatorsWidgetAction(QWidget *parent = nullptr);

    /**
     * Adjusts the width of the spacings used to align the UrlNavigators with ViewContainers.
     * This can only work nicely if up-to-date geometry of ViewContainers is cached so
     * followViewContainersGeometry() has to have been called at least once before.
     */
    void adjustSpacing();

    /**
     * The secondary UrlNavigator is only created on-demand. Such an action is not necessary
     * for the primary UrlNavigator which is created preemptively.
     *
     * This method should preferably only be called when:
     * - Split view is activated in the active tab
     * OR
     * - A switch to a tab that is already in split view mode is occurring
     */
    void createSecondaryUrlNavigator();

    /**
     * Notify this widget of changes in geometry of the ViewContainers it tries to be
     * aligned with.
     */
    void followViewContainersGeometry(QWidget *primaryViewContainer, QWidget *secondaryViewContainer = nullptr);

    bool isInToolbar() const;

    /**
     * @return the primary UrlNavigator.
     */
    DolphinUrlNavigator *primaryUrlNavigator() const;
    /**
     * @return the secondary UrlNavigator and nullptr if it doesn't exist.
     */
    DolphinUrlNavigator *secondaryUrlNavigator() const;

    /**
     * Change the visibility of the secondary UrlNavigator including spacing.
     * @param visible Setting this to false will completely hide the secondary side of this
     *                WidgetAction's QSplitter making the QSplitter effectively disappear.
     */
    void setSecondaryNavigatorVisible(bool visible);

    /**
     * Sets the background cosmetic of the location bar(s) visible or hidden.
     * In frameless designs it's better to hide the background.
     * @param enabled True for showing background cosmetic, false for hiding it.
     */
    void setBackgroundEnabled(bool enabled);

protected:
    /**
     * There should always ever be one navigatorsWidget for this action so
     * this method always returns the same widget and reparents it.
     * You normally don't have to use this method directly because
     * QWidgetAction::requestWidget() is used to obtain the navigatorsWidget
     * and to steal it from wherever it was prior.
     * @param parent the new parent of the navigatorsWidget.
     */
    QWidget *createWidget(QWidget *parent) override;

    /** @see QWidgetAction::deleteWidget() */
    void deleteWidget(QWidget *widget) override;

private:
    /**
     * In Left-to-right languages the Primary side will be the left one.
     */
    enum Side { Primary, Secondary };
    /**
     * Used to create the navigatorWidgets for both sides of the QSplitter.
     */
    QWidget *createNavigatorWidget(Side side) const;

    /**
     * Used to retrieve the emptyTrashButtons for the navigatorWidgets on both sides.
     */
    QPushButton *emptyTrashButton(Side side);

    /**
     * Creates a new empty trash button.
     * @param urlNavigator Only when this UrlNavigator shows the trash directory
     *                     will the button be visible.
     * @param parent       Aside from the usual QObject deletion mechanisms,
     *                     this parameter influences the positioning of dialog windows
     *                     pertaining to this trash button.
     */
    QPushButton *newEmptyTrashButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const;

    /**
     * Used to retrieve the networkFolderButtons for the navigatorWidgets on
     * both sides.
     */
    QPushButton *networkFolderButton(Side side);

    /**
     * Creates a new add "network folder" button.
     * @param urlNavigator Only when this UrlNavigator shows the remote directory
     *                     will the button be visible.
     * @param parent       The object that should be the button's parent.
     */
    QPushButton *newNetworkFolderButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const;

    /**
     * Used to retrieve the kdeConnectButtons for the navigatorWidgets on
     * both sides.
     */
    QPushButton *kdeConnectButton(Side side);

    /**
     * Creates a new add "Open KDE Connect" button.
     * @param urlNavigator Only when this UrlNavigator shows a "kdeconnect" URL
     *                     will the button be visible.
     * @param parent       The object that should be the button's parent.
     */
    QPushButton *newKdeConnectButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const;

    enum Position { Leading, Trailing };
    /**
     * Used to retrieve both the leading and trailing spacing for the navigatorWidgets
     * on both sides. A secondary leading spacing does not exist.
     */
    QWidget *spacing(Side side, Position position) const;

    /**
     * Sets this action's text depending on the amount of visible UrlNavigators.
     */
    void updateText();

    /**
     * The defaultWidget() of this QWidgetAction.
     */
    std::unique_ptr<QSplitter> m_splitter;

    /**
     * adjustSpacing() has to be called slightly later than when urlChanged is emitted.
     * This timer bridges that time.
     */
    std::unique_ptr<QTimer> m_adjustSpacingTimer;

    /**
     * Extracts the geometry information needed by adjustSpacing() from
     * ViewContainers. They are also monitored for size changes which
     * will lead to adjustSpacing() calls.
     */
    class ViewGeometriesHelper : public QObject
    {
    public:
        /**
         * @param navigatorsWidget       The QWidget of the navigatorsWidgetAction.
         * @param navigatorsWidgetAction is only used to call adjustSpacing() whenever that is
         *                               deemed necessary.
         */
        ViewGeometriesHelper(QWidget *navigatorsWidget, DolphinNavigatorsWidgetAction *navigatorsWidgetAction);

        /**
         * Calls m_navigatorsWidgetAction::adjustSpacing() when a watched object is resized.
         */
        bool eventFilter(QObject *watched, QEvent *event) override;

        /**
         * Sets the ViewContainers whose geometry is obtained when viewGeometries() is called.
         */
        void setViewContainers(QWidget *primaryViewContainer, QWidget *secondaryViewContainer = nullptr);

        struct Geometries {
            int globalXOfNavigatorsWidget;
            int globalXOfPrimary;
            int widthOfPrimary;
            int globalXOfSecondary;
            int widthOfSecondary;
        };
        /**
         * @return a Geometries struct that contains values adjustSpacing() requires.
         */
        Geometries viewGeometries();

    private:
        QWidget *m_navigatorsWidget;
        /** Is only used to call adjustSpacing() whenever that is deemed necessary. */
        DolphinNavigatorsWidgetAction *m_navigatorsWidgetAction;

        QPointer<QWidget> m_primaryViewContainer;
        QPointer<QWidget> m_secondaryViewContainer;
    };

    ViewGeometriesHelper m_viewGeometriesHelper;

    /**
     * Used to check if the window has been resized.
     * @see ViewGeometriesHelper::eventFilter() for why this is needed.
     */
    int m_previousWindowWidth = -1;
};

#endif // DOLPHINNAVIGATORSWIDGETACTION_H
