/**
 * Copyright (c) 2022
 *    Marcus Britanicus (https://gitlab.com/marcusbritanicus)
 *    Abrar (https://gitlab.com/s96Abrar)
 *    rahmanshaber (https://gitlab.com/rahmanshaber)
 *
 * IMPL Class
 *  The UPowerBackend watches the org.freedesktop.UPower and
 *  relays the relevent information to DFL::PowerManager class.
 *  This is purely an implementational detail.
 *  This class can be modified/removed at anytime.
 **/

#pragma once

#include <QtCore>
#include <QtDBus>
#include <GammaEffects.hpp>

namespace DFL {
    namespace Power {
        /** Lid State Enums */
        enum LidState {
            LidOpen = 0x32A288,
            LidClosed,
        };

        /** Power State Enums */
        enum PowerState {
            OnAcPower = 0x6FF8DD,
            OnBattery,
            OnLowBattery,
            OnCriticalBattery,
        };

        /**
         * These are all the available actions. Not all actions may suit all
         * situations. Ex: DPMSOn for IdleTimeOut makes no sense.
         * Note: Some actions may require elevated previleges.
         */
        enum Action {
            /** No action to be taken. */
            NoAction             = 0x102500,

            /** Let LoginD decide what to do. This will release inhibitor locks (except power key) */
            LoginD               = 0x102501,

            /** Dim the screen - brightness will be set to a minimum value */
            DimScreen            = 0x102502,

            /** Switch on the screen */
            DPMSOn               = 0x102503,

            /** Switch off the screen */
            DPMSOff              = 0x102504,

            /** Show the logout dialog */
            ShowPowerDialog      = 0x102505,

            /** Lock the screen */
            LockScreen           = 0x102506,

            /** Put the system in standby: if the kernel supports it. */
            Standby              = 0x102507,

            /** Suspend the system (Suspend to RAM) */
            Suspend              = 0x102508,

            /** Suspend the system, then if not woken, hibernate */
            SuspendThenHibernate = 0x102509,

            /** Hibernate the system, (Suspend to Disk) */
            Hibernate            = 0x10250A,

            /** Write system state to disk, then Suspend to RAM */
            HybridSleep          = 0x10250B,

            /** LogOut, then power down the system */
            PowerOff             = 0x10250C,

            /** LogOut, then reboot the system */
            Reboot               = 0x10250D,
        };

        /** Action to be taken and script to be run when lid state changes */
        typedef struct lid_state_config_t {
            /** Action to be taken when lid is opened or closed */
            int     Action;

            /** Script to be run when lid is opened or closed */
            QString Script;
        } LidStateConfig;

        /** Various actions */
        typedef struct power_state_config_t {
            /** Action to be taken when switching to a particular power state */
            int                       Action;

            /** Script to be run when switching to a particular power state */
            QString                   Script;

            /** Brightness percentage to be set when switching to a particular power state */
            double                    Brightness;

            /** Idle time after which @OnIdle/@IdleScript will be activated/run (ms) */
            int                       IdleTimeOut;

            /** Action to be taken when system is idle */
            int                       OnIdle;

            /** Action to be taken when system comes out of idle */
            int                       OnResume;

            /** Script to be run when system is idle */
            QString                   IdleScript;

            /** Script to be run when system comes out of idle */
            QString                   ResumeScript;

            /** LidState configs */
            QMap<int, LidStateConfig> LidConfigs;
        } PowerStateConfig;

        /** Power status: Charging state, Battery Charge, Lid */
        typedef struct power_status_t {
            PowerState powerState;
            double     batteryCharge;
            LidState   lidState;
            qlonglong  timeToFull;
            qlonglong  timeToEmpty;
        } PowerStatus;

        /** Simple utility functions */
        QString textForAction( Action act );
        Action actionFromText( QString text );

        class Manager;
    }
}

// struct PowerManagerImpl;
typedef struct power_manager_implt_t PowerManagerImpl;

class DFL::Power::Manager : public QObject {
    Q_OBJECT;

    public:

        /**
         * This class is ultimately responsible for performing various actions
         * on lid or power events. To start the power management, add configurations
         * to the manager using @addLidConfiguration(...) and @addPowerConfiguration
         * classes.
         */
        Manager( QObject *parent = nullptr );
        ~Manager();

        /**
         * Get the current power status.
         * This will refresh the batteries, and then collect the statistics.
         * A struct will be returned containing the PowerState, LidState and Charge.
         */
        PowerStatus powerStatus();

        /**
         * Add a configuration for a power event.
         * If multiple configurations are added corresponding to a single power state,
         * only the last added configuration will be retained.
         * If for a pwer state, a configuration is not defined, all inhibitor locks will
         * be released, and LoginD will takeover.
         */
        void addPowerConfiguration( DFL::Power::PowerState, DFL::Power::PowerStateConfig );

        /**
         * Start the power management
         * This function will connect various signals to slots.
         */
        void startManagement();

        /**
         * Stop the power management
         * This function will disconnect various signals from slots.
         * This will also release inhibit locks, effectively letting logind take over.
         */
        void stopManagement();

        /**
         * Enable Sunset Mode
         * Switching on sunset mode adjusts the color temperature of the
         * colors of the screen.
         */
        void setSunsetMode( bool on );

        /**
         * GammaEffects configuration.
         * This is provided so that user can set custom temperature, lat-long, etc.
         * Settings any value in the configuration to -1 or (QTime() for sunrise/sunset),
         * will retain it's previously set value.
         * Ex: cfg.minTemp = -1 will result the retaining previously set minimum temperature.
         * Note: Brightness cannot be changed using this function; use adjustBrightness(...)
         */
        void updateGammaConfig( DFL::GammaEffectsConfig cfg );

        /**
         * Hack: Brightness management for LED Displays
         * LED Monitors do not use backlight to control display brightness.
         * We can use gamma tables to manage their brightness as an alternative.
         * In this hack, we specify the technology to use for a given display.
         * @display: A preferably unique ID, like HDMI-A-1, or eDP-1
         * @backend: either backlight, or gamma
         */
        void setBrightnessBackend( QString display, QString backend );

        /**
         * Set brightness of backlight devices. Range = [0.0: 1.0]
         * Typically, laptop screens have a LCD backlight.
         * Most modern PC Monitors are LED displays, and are unlikely to support this.
         * If a brightness hack is specified for a display, then after all backlights
         * are set, we will use the specified hack on that display.
         */
        void adjustBrightness( double );

    private:
        PowerManagerImpl *impl;

        /** When the power state changes, load the suitable configuration */
        void loadPowerConfig( DFL::Power::PowerStateConfig cfg );

        /** Perform the action as dictated by the current configuration */
        void takeAction( int action );

        /** Run the script specified in the current configuration */
        void runScript( QString script );

        /** Lid state watcher */
        void handleLidStateChange( bool );

        /** Connect/Disconnect signals and slots */
        void connectSignalsToSlots();
        void disconnectSignals();

    Q_SIGNALS:

        /* Lid Open/Close Signals */
        void lidStateChanged( bool open );

        /* Battery Charge/Discharge Signals */
        void switchedToBattery();
        void switchedToACPower();

        /* Battery charge changed */
        void batteryChargeChanged( double );

        void timeToFull( qlonglong );
        void timeToEmpty( qlonglong );

        /* Battery nearly (5m) and almost (1m) empty */
        void batteryNearlyEmpty();
        void batteryEmpty();
};
