
#ifndef __VOLUME_MANAGER_H_
#define __VOLUME_MANAGER_H_

#include <string>
#include <condor_config.h>

const int VOLUME_MANAGER_TIMEOUT = 120;
enum LvmHideMount {
    LVM_DONT_HIDE_MOUNT   = 0,
    LVM_ALWAYS_HIDE_MOUNT = 1,
    LVM_AUTO_HIDE_MOUNT   = 2,
};

struct LeakedLVInfo {
    LeakedLVInfo(const std::string& n, const bool e) : name(n), encrypted(e) {}
    std::string name{};
    bool encrypted{false};
};

class CondorError;
class Env;

class VolumeManager {

public:

    VolumeManager();
    VolumeManager(const VolumeManager&) = delete;
    ~VolumeManager();

    void AdvertiseInfo(ClassAd* ad);
    void UpdateStarterEnv(Env &env, const std::string & lv_name, long long disk_kb, bool encrypt);
    // See RemoveLV() for exit codes | is_encrypted: -1=Unknown, 0=false, 1=true
    int  CleanupLV(const std::string &lv_name, CondorError &err, int is_encrypted=-1);
    bool CleanupLVs(std::vector<LeakedLVInfo>* leaked = nullptr);
    bool GetPoolSize(uint64_t& detected_bytes, uint64_t& free_bytes, uint64_t& non_condor_bytes, CondorError& err);
    static bool DetectLVM() {
        return param_boolean("STARTD_ENFORCE_DISK_LIMITS", false) && is_enabled();
    }

    // Return hide mount enum values
    static int GetHideMount() {
        std::string hideMnt;
        param(hideMnt, "LVM_HIDE_MOUNT", "AUTO");
        lower_case(hideMnt);

        int val = LVM_AUTO_HIDE_MOUNT;

        bool bval;
        if (string_is_boolean_param(hideMnt.c_str(), bval)) { // True or False
            val = bval ? LVM_ALWAYS_HIDE_MOUNT : LVM_DONT_HIDE_MOUNT;
        } else if (hideMnt == "auto") { // Auto
            val = LVM_AUTO_HIDE_MOUNT;
        }

        return val;
    }

    // Check job ad and configured LVM_HIDE_MOUNT value for whether or not to
    // hide the LV mount. Returns false when things strict incompatibilities are detected
    static bool CheckHideMount(const ClassAd* jobAd, const ClassAd* machineAd, bool& hide_mount);

    // Return the number of system devices currently existing for LV
    int CountLVDevices(const std::string& lv);

    // Add disk info to Startd Record
    void PublishDiskInfo(ClassAd& ad) {
        if (m_queried_available_disk) {
            ad.Assign(ATTR_LVM_DETECTED_DISK, (long long)m_total_disk);
            if (m_total_disk > 0) {
                ad.Assign(ATTR_LVM_NON_CONDOR_USAGE, (long long)m_non_condor_usage);
            }
            if (m_disk_quantum_kb > 0) {
                ad.Assign(ATTR_STARTD_DISK_QUANTUM, (long long)m_disk_quantum_kb);
            }
        }
    }

    // Get Discovered disk quantum
    long long GetDiskQuantum() const {
        return static_cast<long long>(m_disk_quantum_kb);
    }

#ifdef LINUX
    static bool is_enabled() { return true; }
    bool IsSetup() const;

    inline std::string GetVG() const { return m_volume_group_name; }
    inline std::string GetPool() const { return m_pool_lv_name; }
    inline std::string GetLoopDev() const { return m_loopdev_name; }
    inline std::string GetLoopFile() const { return m_loopback_filename; }
    inline int GetTimeout() const { return m_cmd_timeout; }
    inline bool IsThin() const { return m_use_thin_provision; }
    inline void SetTotalDisk(uint64_t total) {
        m_total_disk = total;
        m_queried_available_disk = true;
    }

    inline std::string GetBackingDevice() const {
        std::string dev = m_volume_group_name;
        if (m_use_thin_provision) { dev += "/" + m_pool_lv_name; }
        return dev;
    }

    class Handle {
    public:
        Handle() = delete;
        Handle(const Handle&) = delete;
        Handle(const std::string &vg_name, const std::string &volume, const char* pool, bool encrypt);
        ~Handle();
        inline bool HasInfo() const { return !m_volume.empty() && !m_vg_name.empty() && (!m_thin || !m_thinpool.empty()); }
        inline std::string GetVG() const { return m_vg_name; }
        inline std::string GetLVName() const { return m_volume; }
        inline std::string GetPool() const { return m_thinpool; }
        inline std::string GetMount() const { return m_mountpoint; }
        inline int GetTimeout() const { return m_timeout; }
        inline bool IsThin() const { return m_thin; }
        inline bool IsEncrypted() const { return m_encrypt; }
        inline int SetPermission(int perms) { TemporaryPrivSentry sentry(PRIV_ROOT); return chmod(m_mountpoint.c_str(), perms); }
        bool SetupLV(const std::string& mountpoint, uint64_t size_kb, bool hide_mount, CondorError& err);
        bool CleanupLV(CondorError& err);

    private:
        std::string m_mountpoint;
        std::string m_volume;
        std::string m_vg_name;
        std::string m_thinpool;
        int m_timeout{VOLUME_MANAGER_TIMEOUT};
        bool m_thin{false};
        bool m_encrypt{false};
        bool m_cleaned_up{true}; // Set to false when SetupLV() called
    };

    static bool GetVolumeUsage(const VolumeManager::Handle* handle, filesize_t &used_bytes, size_t &numFiles, bool &out_of_space, CondorError &err);

private:
    static bool MountFilesystem(const std::string &device_path, const std::string &mountpoint, CondorError &err, bool hide_mount);
    static std::string CreateLoopback(const std::string &backing_filename, uint64_t size_kb, CondorError &err, int timeout);
    static bool CreateThinPool(const std::string &lv_name, const std::string &vg_name, CondorError &err, int timeout);
    static bool CreateLV(const VolumeManager::Handle &handle, uint64_t size_kb, CondorError &err);
    static bool CreateVG(const std::string &vg_name, const std::string &device, CondorError &err, int timeout);
    static bool CreatePV(const std::string &device, CondorError &err, int timeout);
    static bool CreateFilesystem(const std::string &device_path, CondorError &err, int timeout);
    static bool EncryptLV(const std::string &lv_name, const std::string &vg_name, CondorError &err, int timeout);
    static void RemoveLostAndFound(const std::string& mountpoint);
    static bool RemoveLVEncryption(const std::string &lv_name, const std::string &vg_name, CondorError &err, int timeout);
    static bool CleanupAllDevices(const VolumeManager &info, CondorError &err, bool cleanup_loopback = true);
    static bool UnmountFilesystem(const std::string &mountpoint, CondorError &err);
    /* RemoveLV() returns: 0 on success, <0 on failure, >0 on warnings (2 = LV not found) */
    static int  RemoveLV(const std::string &lv_name, const std::string &vg_name, CondorError &err, bool encrypted, int timeout);
    static bool RemoveVG(const std::string &vg_name, CondorError &err, int timeout);
    static bool RemovePV(const std::string &pv_name, CondorError &err, int timeout);
    static bool RemoveLoopDev(const std::string &loopdev_name, const std::string &backing_file, CondorError &err, int timeout);

    bool m_use_thin_provision{true};
    std::string m_pool_lv_name;
    std::string m_volume_group_name;
    std::string m_loopback_filename;
    std::string m_loopdev_name;
    int m_cmd_timeout{VOLUME_MANAGER_TIMEOUT};
#else
    static bool is_enabled() { return false; }
    bool IsSetup() const { return false; };
    inline std::string GetBackingDevice() const { return "<none>"; }
#endif // LINUX
    uint64_t m_disk_quantum_kb{0};
    uint64_t m_total_disk{0};
    uint64_t m_non_condor_usage{0};
    bool m_queried_available_disk{false};
};


#endif
