/*
    SPDX-FileCopyrightText: 1999 Martin R. Jones <mjones@kde.org>
    SPDX-FileCopyrightText: 2003 Oswald Buddenhagen <ossi@kde.org>
    SPDX-FileCopyrightText: 2008 Chani Armitage <chanika@gmail.com>
    SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>

SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once

#include <QElapsedTimer>
#include <QProcessEnvironment>

#include <KScreenLocker/kscreenlocker_export.h>

// forward declarations
class GlobalAccel;
class LogindIntegration;
class QTimer;
class KSldTest;
class PowerManagementInhibition;

namespace ScreenLocker
{

/**
 * Enum for the different ways to establish a lock.
 */
enum class EstablishLock {
    /**
     * Require password from the start. Use if invoked explicitly by the user.
     */
    Immediate,

    /**
     * Allow the user to log back in without a password for a configured grace time.
     */
    Delayed,

    /**
     * UI should default to showing the "switch user dialog".
     */
    DefaultToSwitchUser,
};

/**
 * @returns a string representation of the given EstablishLock value.
 **/
QString establishLockToString(EstablishLock establishLock);

class AbstractLocker;
class WaylandServer;

/**
 * @class KSldApp
 * @brief The KSldApp class represents the application responsible for screen locking.
 *
 * KSldApp provides functionality for acquiring and releasing screen locks, managing lock states,
 * handling user activity, and interacting with the lock window. It also includes methods for
 * configuring the lock screen behavior and managing power management inhibition.
 *
 * KSldApp implements the D-Bus interface "org.kde.ksld.App".
 *
 * @note KSldApp is primarily used internally by the KDE screen locker component.
 */
class KSCREENLOCKER_EXPORT KSldApp : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.kde.ksld.App")

public:
    /**
     * @brief The LockState enum represents the current state of the screen lock.
     */
    enum LockState {
        /** The screen is unlocked. */
        Unlocked,
        /** The screen is in the process of being locked. */
        AcquiringLock,
        /** The screen is locked. */
        Locked,
    };
    Q_ENUM(LockState)

    static KSldApp *self();

    explicit KSldApp(QObject *parent = nullptr);
    ~KSldApp() override;

    /**
     * @returns the current lock state.
     **/
    LockState lockState() const
    {
        return m_lockState;
    }

    /**
     * @returns the number of milliseconds passed since the screen has been locked.
     **/
    uint activeTime() const;

    /**
     * @brief Configures the KSldApp instance.
     *
     * This function is responsible for configuring the KSldApp instance by loading the screen saver settings,
     * setting up the idle support, and managing the lock and password requirements.
     *
     * @note This function assumes that the KScreenSaverSettings and KIdleTime instances have been properly initialized.
     */
    void configure();

    /**
     * @brief Handles user activity.
     *
     * This function is called when user activity is detected.
     *
     * If the grace time is still active or password requirement is disabled,
     * it unlocks the screen. If a lock window is present, it also notifies
     * the lock window about the user activity.
     */
    void userActivity();

    /**
     * @return true if the application is in the grace time, false otherwise.
     */
    bool isGraceTime() const;

    /**
     * @brief Sets the Wayland file descriptor.
     *
     * @param fd The Wayland file descriptor to set.
     */
    void setWaylandFd(int fd);

    /**
     * @brief Sets the environment for the greeter process.
     *
     * @param env The QProcessEnvironment object containing the environment
     * variables to be set for the greeter process.
     */
    void setGreeterEnvironment(const QProcessEnvironment &env);

    /**
     * Can be used by the lock window to remove the lock during grace time.
     **/
    void unlock();

    /**
     * @brief Increments the inhibit counter.
     *
     * The inhibit counter is used to keep track of how many times the application
     * has been inhibited from locking the screen.
     */
    void inhibit();

    /**
     * @brief Decrements the inhibit counter.
     *
     * The inhibit counter is used to keep track of how many times the application
     * has been inhibited from locking the screen.
     */
    void uninhibit();

    /**
     * @brief Locks the screen.
     *
     * If the screen is already locked or in the process of acquiring the lock, verify that the lock is established.
     * If the screen is unlocked, it attempts to establish the lock by calling the establishGrab() function.
     * If the lock cannot be established, it retries up to three times.
     * If the lock is successfully established, it shows the lock window, starts the lock process, and emits the lockStateChanged() signal.
     *
     * @param establishLock The type of lock to establish (eg. immediate or delayed).
     * @param attemptCount The number of attempts made to establish the lock.
     */
    void lock(EstablishLock establishLock, int attemptCount = 0);

    /**
     * @brief Initializes the KSldApp object.
     */
    void initialize();

    /**
     * @brief Handles events for the KSldApp class.
     *
     * Checks if the event is a key press event and if a global accelerator is set.
     * If both conditions are met, it calls the keyEvent function of the global accelerator
     * and sets the event as accepted if the key event is handled.
     *
     * @param event The event to be handled.
     * @return Returns false to indicate that the event was not handled by this function.
     */
    bool event(QEvent *event) override;

    /**
     * For testing
     * @internal
     **/
    int idleId() const
    {
        return m_idleId;
    }
    /**
     * For testing
     * @internal
     **/
    void setIdleId(int idleId)
    {
        m_idleId = idleId;
    }
    /**
     * For testing
     * @internal
     **/
    void setGraceTime(int msec)
    {
        m_lockGrace = msec;
    }

    /**
     * @brief Returns whether software rendering is forced.
     */
    bool forceSoftwareRendering() const
    {
        return m_forceSoftwareRendering;
    }

    /**
     * @brief Sets whether software rendering should be forced.
     */
    void setForceSoftwareRendering(bool force)
    {
        m_forceSoftwareRendering = force;
    }

Q_SIGNALS:

    /**
     * Emitted when the screen is about to be locked.
     */
    void aboutToLock();

    /**
     * Emitted before the greeter is (re)started
     */
    void aboutToStartGreeter();

    /**
     * Emitted when the screen has been locked.
     */
    void locked();

    /**
     * Emitted when the screen has been unlocked.
     */
    void unlocked();

    /**
     * Emitted when the lock state has changed.
     */
    void lockStateChanged();

private Q_SLOTS:

    /**
     * @brief Cleans up resources used by KSldApp.
     *
     * This function terminates the lock process if it is running, deletes the lock process and lock window objects,
     * and restores the X screensaver parameters to their original values.
     */
    void cleanUp();

    /**
     * @brief Ends the grace time.
     */
    void endGraceTime();

    /**
     * @brief Suspends the application and locks the screen if necessary.
     *
     * This function is called when the system is about to suspend. If the application is not using logind,
     * it checks if the screen should be locked upon resuming from suspend. If so, it locks the screen immediately.
     */
    void solidSuspend();

public Q_SLOTS:

    /**
     * @brief Called when the lock screen is shown.
     *
     * Updates the lock state and emits signals to inform other components.
     */
    void lockScreenShown();

private:
    /**
     * @brief Initializes the X11 environment for the KSldApp.
     */
    void initializeX11();

    /**
     * @brief Establishes the grab for keyboard and mouse input.
     *
     * Ensures that only the lock window receives keyboard and mouse input.
     *
     * @return True if the grab is successfully established, false otherwise.
     */
    bool establishGrab();

    /**
     * @brief Starts the lock process.
     *
     * This function starts the lock process based on the given EstablishLock parameter.
     * It sets up the necessary environment variables and command line arguments for the lock process.
     * If the Wayland server fails to start, an error is emitted and the function returns.
     *
     * @param establishLock The type of lock to establish (eg. immediate or delayed).
     */
    void startLockProcess(EstablishLock establishLock);

    /**
     * @brief Shows the lock window.
     *
     * Creates the lock window if it doesn't exist and sets up the necessary
     * connections.
     */
    void showLockWindow();

    /**
     * @brief Hides the lock window.
     */
    void hideLockWindow();

    /**
     * @brief Performs the unlocking operation.
     *
     * This function is responsible for unlocking the screen. It releases the keyboard and pointer grabs,
     * hides the lock window, and cleans up state.
     */
    void doUnlock();

    /**
     * @brief The lock process has requested to unlock the screen.
     *
     * Cleans up some state and begins the unlocking process.
     */
    void lockProcessRequestedUnlock();

    bool isInhibited() const;

    void updateIdleTimeout();

    /**
     * @brief The current state of the lock.
     */
    LockState m_lockState;

    /**
     * The lock process used to lock the screen.
     **/
    QProcess *m_lockProcess;

    /**
     * The lock window used to display the lock screen.
     **/
    AbstractLocker *m_lockWindow;

    /**
     * The Wayland server instance used by the lock window.
     **/
    WaylandServer *m_waylandServer;

    /**
     * Timer to measure how long the screen is locked.
     * This information is required by DBus Interface.
     **/
    QElapsedTimer m_lockedTimer;

    /**
     * @brief The unique identifier for the idle timer.
     */
    int m_idleId;

    /**
     * Number of milliseconds after locking in which user activity will result in screen being
     * unlocked without requiring a password.
     **/
    int m_lockGrace;

    /**
     * Controls whether user activity may remove the lock. Only enabled after idle timeout.
     **/
    bool m_inGraceTime;

    /**
     * Grace time ends when timer expires.
     **/
    QTimer *m_graceTimer;

    /**
     * @brief The counter for inhibiting screen locking.
     *
     * This variable keeps track of the number of inhibitions on screen locking.
     */
    int m_inhibitCounter;

    /**
     * Provides handling for interactions with the logind service, such as
     * session management and power management.
     */
    LogindIntegration *m_logind;

    /**
     * @brief Provides handling for global accelerators.
     */
    GlobalAccel *m_globalAccel = nullptr;

    /**
     * @brief Whether the XInput2 extension is available.
     */
    bool m_hasXInput2 = false;

    /**
     * @brief Whether software rendering should be forced.
     */
    bool m_forceSoftwareRendering = false;

    /**
     * @brief Whether the application is running on X11.
     */
    bool m_isX11;

    /**
     * @brief Whether the application is running on Wayland.
     */
    bool m_isWayland;

    /**
     * @brief Counter for tracking the number of times the greeter has crashed.
     */
    int m_greeterCrashedCounter = 0;

    /**
     * @brief The environment for the greeter process.
     */
    QProcessEnvironment m_greeterEnv;

    /**
     * @brief Interface for power management inhibition.
     */
    PowerManagementInhibition *m_powerManagementInhibition;

    /**
     * Whether the lock screen should require a password to unlock.
     *
     * If this is false the lock screen will be dismissed on user activity,
     * without requiring a password. This allows users to use the lock screen
     * as a screen saver.
     **/
    bool m_requirePassword = true;

    /**
     * @brief The file descriptor for the Wayland connection.
     *
     * This file descriptor is used to establish the Wayland connection for the
     * lock window.
     */
    int m_waylandFd = -1;

    // for auto tests
    friend KSldTest;
};
} // namespace
