#pragma once

#include "activation.hpp"
#include "item_updater_helper.hpp"
#include "msl_verify.hpp"
#include "update_manager.hpp"
#include "version.hpp"
#include "xyz/openbmc_project/Collection/DeleteAll/server.hpp"

#include <sdbusplus/async.hpp>
#include <sdbusplus/server.hpp>
#include <xyz/openbmc_project/Association/Definitions/server.hpp>
#include <xyz/openbmc_project/Common/FactoryReset/server.hpp>
#include <xyz/openbmc_project/Control/FieldMode/server.hpp>
#include <xyz/openbmc_project/Software/MinimumVersion/server.hpp>

#include <string>
#include <vector>

namespace phosphor
{
namespace software
{
namespace updater
{

using ActivationIntf =
    sdbusplus::xyz::openbmc_project::Software::server::Activation;
using ItemUpdaterInherit = sdbusplus::server::object_t<
    sdbusplus::server::xyz::openbmc_project::common::FactoryReset,
    sdbusplus::server::xyz::openbmc_project::control::FieldMode,
    sdbusplus::server::xyz::openbmc_project::association::Definitions,
    sdbusplus::server::xyz::openbmc_project::collection::DeleteAll>;
using MinimumVersionInherit = sdbusplus::server::object_t<
    sdbusplus::server::xyz::openbmc_project::software::MinimumVersion>;

namespace MatchRules = sdbusplus::bus::match::rules;
using VersionClass = phosphor::software::manager::Version;
using AssociationList =
    std::vector<std::tuple<std::string, std::string, std::string>>;
using UpdateManager = phosphor::software::update::Manager;

/** @class MinimumVersion
 *  @brief OpenBMC MinimumVersion implementation.
 *  @details A concrete implementation for
 *  xyz.openbmc_project.Software.MinimumVersion DBus API.
 */
class MinimumVersion : public MinimumVersionInherit
{
  public:
    /** @brief Constructs MinimumVersion
     *
     * @param[in] bus - The D-Bus bus object
     * @param[in] path - The D-bus object path
     */
    MinimumVersion(sdbusplus::bus_t& bus, const std::string& path) :
        MinimumVersionInherit(bus, path.c_str(), action::emit_interface_added)
    {}
};

/** @class ItemUpdater
 *  @brief Manages the activation of the BMC version items.
 */
class ItemUpdater : public ItemUpdaterInherit
{
  public:
    /*
     * @brief Types of Activation status for image validation.
     */
    enum class ActivationStatus
    {
        ready,
        invalid,
        active
    };

    /** @brief Types of Updater. */
    enum class UpdaterType
    {
        BMC,
        BIOS,
        ALL
    };

    /** @brief Constructs ItemUpdater
     *
     * @param[in] bus    - The D-Bus bus object
     */
    ItemUpdater(sdbusplus::async::context& ctx, const std::string& path,
                UpdaterType type = UpdaterType::ALL,
                bool useUpdateDBusInterface = true) :
        ItemUpdaterInherit(ctx.get_bus(), path.c_str(),
                           ItemUpdaterInherit::action::defer_emit),
        type(type), useUpdateDBusInterface(useUpdateDBusInterface), ctx(ctx),
        bus(ctx.get_bus()), helper(bus)
    {
        if (!useUpdateDBusInterface)
        {
            versionMatch = std::make_unique<sdbusplus::bus::match_t>(
                bus,
                MatchRules::interfacesAdded() +
                    MatchRules::path("/xyz/openbmc_project/software"),
                std::bind(std::mem_fn(&ItemUpdater::createActivation), this,
                          std::placeholders::_1));
        }
        getRunningSlot();
        setBMCInventoryPath();
        if (type == UpdaterType::BMC || type == UpdaterType::ALL)
        {
            processBMCImage();
        }
        if (type == UpdaterType::BIOS || type == UpdaterType::ALL)
        {
#ifdef HOST_BIOS_UPGRADE
            createBIOSObject();
#endif
        }
        restoreFieldModeStatus();
        emit_object_added();
    };

    /** @brief Save priority value to persistent storage (flash and optionally
     *  a U-Boot environment variable)
     *
     *  @param[in] versionId - The Id of the version
     *  @param[in] value - The priority value
     *  @return None
     */
    void savePriority(const std::string& versionId, uint8_t value);

    /** @brief Sets the given priority free by incrementing
     *  any existing priority with the same value by 1
     *
     *  @param[in] value - The priority that needs to be set free.
     *  @param[in] versionId - The Id of the version for which we
     *                         are trying to free up the priority.
     *  @return None
     */
    void freePriority(uint8_t value, const std::string& versionId);

    /**
     * @brief Create and populate the active BMC Version.
     */
    void processBMCImage();

    /**
     * @brief Verifies the image at filepath and creates the version and
     * activation object. In case activation object already exists for the
     * specified id, update the activation status based on image verification.
     * @param[in] id - The unique identifier for the update.
     * @param[in] path - The object path for the relevant objects.
     * @param[in] version - The version of the image.
     * @param[in] purpose - The purpose of the image.
     * @param[in] extendedVersion The extended version of the image.
     * @param[in] filePath - The file path where the image is located.
     * @param[in] compatibleNames - The compatible name for the image.
     * @param[out] Activations - Whether the image is ready to activate or not.
     */
    ActivationIntf::Activations verifyAndCreateObjects(
        std::string& id, std::string& path, std::string& version,
        VersionClass::VersionPurpose purpose, std::string& extendedVersion,
        std ::string& filePath, std::vector<std::string>& compatibleNames);

    /**
     * @brief Creates the activation object
     * @param[in] id - The unique identifier for the update.
     * @param[in] path - The object path for the activation object.
     * @param[in] applyTime - The apply time for the image
     */
    void createActivationWithApplyTime(
        std::string& id, std::string& path,
        ApplyTimeIntf::RequestedApplyTimes applyTime);

    /**
     * @brief Request the activation for the specified update.
     * @param[in] id - The unique identifier for the update.
     * @param[out] bool - status for the action.
     */
    bool requestActivation(std::string& id);

    /**
     * @brief Change the activation status for the specified update.
     * @param[in] id - The unique identifier for the update.
     * @param[in] status - The activation status to set.
     * @param[out] bool - status for the action.
     */
    bool updateActivationStatus(std::string& id,
                                ActivationIntf::Activations status);

    /**
     * @brief Create the Update object
     * @param[in] id - The unique identifier for the update.
     * @param[in] path - The object path for the update object.
     */
    void createUpdateObject(const std::string& id, const std::string& path);

    /**
     * @brief Erase specified entry D-Bus object
     *        if Action property is not set to Active
     *
     * @param[in] entryId - unique identifier of the entry
     */
    void erase(std::string entryId);

    /**
     * @brief Deletes all versions except for the current one
     */
    void deleteAll() override;

    /** @brief Creates an active association to the
     *  newly active software image
     *
     * @param[in]  path - The path to create the association to.
     */
    void createActiveAssociation(const std::string& path);

    /** @brief Removes the associations from the provided software image path
     *
     * @param[in]  path - The path to remove the associations from.
     */
    void removeAssociations(const std::string& path);

    /** @brief Determine if the given priority is the lowest
     *
     *  @param[in] value - The priority that needs to be checked.
     *
     *  @return boolean corresponding to whether the given
     *      priority is lowest.
     */
    bool isLowestPriority(uint8_t value);

    /**
     * @brief Updates the U-Boot variables to point to the requested
     *        versionId, so that the systems boots from this version on
     *        the next reboot.
     *
     * @param[in] versionId - The version to point the system to boot from.
     */
    void updateUbootEnvVars(const std::string& versionId);

    /**
     * @brief Updates the uboot variables to point to BMC version with lowest
     *        priority, so that the system boots from this version on the
     *        next boot.
     */
    void resetUbootEnvVars();

    /** @brief Brings the total number of active BMC versions to
     *         ACTIVE_BMC_MAX_ALLOWED -1. This function is intended to be
     *         run before activating a new BMC version. If this function
     *         needs to delete any BMC version(s) it will delete the
     *         version(s) with the highest priority, skipping the
     *         functional BMC version.
     *
     * @param[in] caller - The Activation object that called this function.
     */
    void freeSpace(const Activation& caller);

    /** @brief Creates a updateable association to the
     *  "running" BMC software image
     *
     * @param[in]  path - The path to create the association.
     */
    void createUpdateableAssociation(const std::string& path);

    /** @brief Persistent map of Version D-Bus objects and their
     * version id */
    std::map<std::string, std::unique_ptr<VersionClass>> versions;

    /** @brief Vector of needed BMC images in the tarball*/
    std::vector<std::string> imageUpdateList;

    /** @brief The slot of running BMC image */
    uint32_t runningImageSlot = 0;

    /** @brief The type of updater. */
    UpdaterType type;

    /** @brief Flag to indicate if the update interface is used or not */
    bool useUpdateDBusInterface;

  private:
    /** @brief Callback function for Software.Version match.
     *  @details Creates an Activation D-Bus object.
     *
     * @param[in]  msg       - Data associated with subscribed signal
     */
    void createActivation(sdbusplus::message_t& msg);

    /**
     * @brief Validates the presence of SquashFS image in the image dir.
     *
     * @param[in]  filePath  - The path to the image dir.
     * @param[out] result    - ActivationStatus Enum.
     *                         ready if validation was successful.
     *                         invalid if validation fail.
     *                         active if image is the current version.
     *
     */
    ActivationStatus validateSquashFSImage(const std::string& filePath);

    /** @brief BMC factory reset - marks the read-write partition for
     * recreation upon reboot. */
    void reset() override;

    /**
     * @brief Enables field mode, if value=true.
     *
     * @param[in]  value  - If true, enables field mode.
     * @param[out] result - Returns the current state of field mode.
     *
     */
    bool fieldModeEnabled(bool value) override;

    /** @brief Sets the BMC inventory item path under
     *  /xyz/openbmc_project/inventory/system/chassis/. */
    void setBMCInventoryPath();

    /** @brief The path to the BMC inventory item. */
    std::string bmcInventoryPath;

    /** @brief Restores field mode status on reboot. */
    void restoreFieldModeStatus();

    /** @brief Creates a functional association to the
     *  "running" BMC software image
     *
     * @param[in]  path - The path to create the association to.
     */
    void createFunctionalAssociation(const std::string& path);

    /** @brief D-Bus context */
    sdbusplus::async::context& ctx;

    /** @brief Persistent sdbusplus D-Bus bus connection. */
    sdbusplus::bus_t& bus;

    /** @brief The helper of image updater. */
    Helper helper;

    /** @brief Persistent map of Activation D-Bus objects and their
     * version id */
    std::map<std::string, std::unique_ptr<Activation>> activations;

    /** @brief sdbusplus signal match for Software.Version */
    std::unique_ptr<sdbusplus::bus::match_t> versionMatch;

    /** @brief This entry's associations */
    AssociationList assocs;

    /** @brief Clears read only partition for
     * given Activation D-Bus object.
     *
     * @param[in]  versionId - The version id.
     */
    void removeReadOnlyPartition(const std::string& versionId);

    /** @brief Copies U-Boot from the currently booted BMC chip to the
     *  alternate chip.
     */
    void mirrorUbootToAlt();

    /** @brief Check the required image files
     *
     * @param[in] filePath - BMC tarball file path
     * @param[in] imageList - Image filenames included in the BMC tarball
     * @param[out] result - Boolean
     *                      true if all image files are found in BMC tarball
     *                      false if one of image files is missing
     */
    static bool checkImage(const std::string& filePath,
                           const std::vector<std::string>& imageList);

    /** @brief Persistent MinimumVersion D-Bus object */
    std::unique_ptr<MinimumVersion> minimumVersionObject;

    /** @brief Persistent map of Update D-Bus objects and their SwIds */
    std::map<std::string, std::unique_ptr<UpdateManager>> updateManagers;

#ifdef HOST_BIOS_UPGRADE
    /** @brief Create the BIOS object without knowing the version.
     *
     *  The object is created only to provide the DBus access so that an
     *  external service could set the correct BIOS version.
     *  On BIOS code update, the version is updated accordingly.
     */
    void createBIOSObject();

    /** @brief Persistent Activation D-Bus object for BIOS */
    std::unique_ptr<Activation> biosActivation;

  public:
    /** @brief Persistent Version D-Bus object for BIOS */
    std::unique_ptr<VersionClass> biosVersion;
#endif

    /** @brief Get the slot number of running image */
    void getRunningSlot();
};

} // namespace updater
} // namespace software
} // namespace phosphor