/**
 * @file
 * Declares utility functions for interacting with Windows Management Instrumentation.
 */
#pragma once

#include <leatherman/util/scoped_resource.hpp>
#include <leatherman/util/strings.hpp>
#include <boost/range/iterator_range.hpp>
#include <string>
#include <map>
#include <vector>

// Forward declarations
class IWbemLocator;
class IWbemServices;

namespace leatherman { namespace windows {

    /**
     * Exception thrown when wmi initialization fails.
     */
    struct wmi_exception : std::runtime_error
    {
        /**
         * Constructs a wmi_exception.
         * @param message The exception message.
         */
        explicit wmi_exception(std::string const& message);
    };

    /**
     * A class for initiating a WMI connection over COM and querying it.
     */
    struct wmi {
        /**
         * Identifier for the WMI class Win32_ComputerSystem
         */
        constexpr static char const* computersystem = "Win32_ComputerSystem";

        /**
         * Identifier for the WMI class Win32_ComputerSystemProduct
         */
        constexpr static char const* computersystemproduct = "Win32_ComputerSystemProduct";

        /**
         * Identifier for the WMI property UUID
         */
        constexpr static char const* uuid = "UUID";

        /**
         * Identifier for the WMI class Win32_OperatingSystem
         */
        constexpr static char const* operatingsystem = "Win32_OperatingSystem";

        /**
         * Identifier for the WMI class Win32_BIOS
         */
        constexpr static char const* bios = "Win32_Bios";

        /**
         * Identifier for the WMI class Win32_Processor
         */
        constexpr static char const* processor = "Win32_Processor";

        /**
         * Identifier for the WMI property Architecture
         */
        constexpr static char const* architecture = "Architecture";

        /**
         * Identifier for the WMI property Name
         */
        constexpr static char const* name = "Name";

        /**
         * Identifier for the WMI property Manufacturer
         */
        constexpr static char const* manufacturer = "Manufacturer";

        /**
         * Identifier for the WMI property Model
         */
        constexpr static char const* model = "Model";

        /**
         * Identifier for the WMI property SerialNumber
         */
        constexpr static char const* serialnumber = "SerialNumber";

        /**
         * Identifier for the WMI property NumberOfLogicalProcessors
         */
        constexpr static char const* numberoflogicalprocessors = "NumberOfLogicalProcessors";

        /**
         * Identifier for the WMI property LastBootUpTime
         */
        constexpr static char const* lastbootuptime = "LastBootUpTime";

        /**
         * Identifier for the WMI property LocalDateTime
         */
        constexpr static char const* localdatetime = "LocalDateTime";

        /**
         * Identifier for the WMI property ProductType
         */
        constexpr static char const* producttype = "ProductType";

        /**
         * Identifier for the WMI property OtherTypeDescription
         */
        constexpr static char const* othertypedescription = "OtherTypeDescription";

        /**
         * Multi-map with case-insensitive lookup.
         */
        using imap = std::multimap<std::string, std::string, util::ciless>;

        /**
         * Vector of case-insensitive multi-maps.
         */
        using imaps = std::vector<imap>;

        /**
         * Range of values for an array type.
         */
        using kv_range = boost::iterator_range<imap::const_iterator>;

        /**
         * Initializes a COM connection for WMI queries. Throws a wmi_exception on failure.
         */
        wmi();

        /**
         * This is a utility for querying WMI classes. Windows queries are case-insensitive,
         * so the returned keys aren't guaranteed to have the same case as the arguments.
         * Returns a vector of case-insensitive maps so the argument keys can safely be used for lookup.
         * Some groups can return multiple objects; in that case the returned vector will
         * have a multi-map for each object. If a property returns an array, it will have
         * multiple entries in the multimap.
         * @param group The class alias to query
         * @param keys A list of keys to query from the specified class
         * @param extra Extra arguments to the WMI query, such as filters
         * @return A vector of case-insensitive maps of the keys argument and their corresponding values
         */
        imaps query(std::string const& group, std::vector<std::string> const& keys, std::string const& extra = "") const;

        /**
         * A utility for retrieving a single entry from an imap. It should only be used if
         * it's known that the requested property is not an array.
         * To retrieve an array, use imap's equal_range.
         * @param kvmap A case-insensitive multimap of keys and their values.
         * @param key The key to lookup.
         * @return Return the value matching the specified key.
         */
        static std::string const& get(imap const& kvmap, std::string const& key);

        /**
         * A utility for retrieving an array entry from an imap. If only one value exists
         * it returns a range of one element.
         * @param kvmap A case-insensitive multimap of keys and their values.
         * @param key The key to lookup.
         * @return An iterator range of key-value pairs matching the specified key.
         */
        static kv_range get_range(imap const& kvmap, std::string const& key);

        /**
         * A utility for retrieving a single entry from an imaps. It should only be used if
         * it's known that the requested group will only return a single object.
         * @param kvmaps A vector of case-insensitive multimap of keys and their values.
         * @param key The key to lookup.
         * @return Return the value matching the specified key.
         */
        static std::string const& get(imaps const& kvmaps, std::string const& key);

        /**
         * A utility for retrieving an array entry from an imaps. It should only be used if
         * it's known that the requested group will only return a single object.
         * @param kvmaps A vector of case-insensitive multimap of keys and their values.
         * @param key The key to lookup.
         * @return An iterator range of key-value pairs matching the specified key.
         */
        static kv_range get_range(imaps const& kvmaps, std::string const& key);

     private:
        util::scoped_resource<bool> _coInit;
        util::scoped_resource<IWbemLocator *> _pLoc;
        util::scoped_resource<IWbemServices *> _pSvc;
    };

}}  // namespace leatherman::windows
