/*
 * Copyright © Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2 or 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef MIR_GRAPHICS_COMMON_KMS_UTILS_DRM_MODE_RESOURCES_H_
#define MIR_GRAPHICS_COMMON_KMS_UTILS_DRM_MODE_RESOURCES_H_

#include <mir/fd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <memory>
#include <functional>
#include <string>
#include <unordered_map>

namespace mir
{
namespace graphics
{
namespace kms
{

using DRMModeCrtcUPtr = std::unique_ptr<drmModeCrtc, std::function<void(drmModeCrtc*)>>;
using DRMModeEncoderUPtr = std::unique_ptr<drmModeEncoder, std::function<void(drmModeEncoder*)>>;
using DRMModeConnectorUPtr = std::unique_ptr<drmModeConnector, std::function<void(drmModeConnector*)>>;
using DRMModeResUPtr = std::unique_ptr<drmModeRes, void (*)(drmModeRes*)>;
using DRMModePlaneResUPtr = std::unique_ptr<drmModePlaneRes, void (*)(drmModePlaneRes*)>;
using DRMModePlaneUPtr = std::unique_ptr<drmModePlane, std::function<void(drmModePlane*)>>;
using DRMModeObjectPropsUPtr = std::unique_ptr<drmModeObjectProperties, void (*)(drmModeObjectProperties*)>;
using DRMModePropertyUPtr = std::unique_ptr<drmModePropertyRes, void (*)(drmModePropertyPtr)>;

DRMModeConnectorUPtr get_connector(int drm_fd, uint32_t id);
DRMModeEncoderUPtr get_encoder(int drm_fd, uint32_t id);
DRMModeCrtcUPtr get_crtc(int drm_fd, uint32_t id);
DRMModePlaneUPtr get_plane(int drm_fd, uint32_t id);

class DRMModeResources;
class PlaneResources;

namespace detail
{
template<typename DRMUPtr, DRMUPtr (*)(int, uint32_t)>
class ObjectCollection
{
public:
    class iterator
    {
    public:
        using iterator_category = std::input_iterator_tag;
        using value_type = DRMUPtr;
        using difference_type = ptrdiff_t;
        using pointer = DRMUPtr*;
        using reference = DRMUPtr&;

        iterator(iterator const& from);
        iterator& operator=(iterator const& rhs);

        iterator& operator++();
        iterator operator++(int);

        bool operator==(iterator const& rhs) const;

        DRMUPtr& operator*() const;
        DRMUPtr* operator->() const;

    private:
        friend class ObjectCollection;
        iterator(int drm_fd, uint32_t* id_ptr);

        int drm_fd;
        uint32_t* id_ptr;
        DRMUPtr mutable current;
    };

    iterator begin();
    iterator end();

private:
    friend class mir::graphics::kms::DRMModeResources;
    friend class mir::graphics::kms::PlaneResources;
    ObjectCollection(int drm_fd, uint32_t* begin, uint32_t* end);

    int const drm_fd;
    uint32_t* const begin_;
    uint32_t* const end_;
};
}

class ObjectProperties
{
public:
    struct Prop
    {
        uint32_t id;
        uint64_t value;
    };

    ObjectProperties(int drm_fd, uint32_t object_id, uint32_t object_type);
    ObjectProperties(int drm_fd, DRMModePlaneUPtr const& plane);
    ObjectProperties(int drm_fd, DRMModeCrtcUPtr const& crtc);
    ObjectProperties(int drm_fd, DRMModeConnectorUPtr const& connector);

    uint64_t operator[](char const* name) const;
    uint32_t id_for(char const* property_name) const;
    bool has_property(char const* property_name) const;

    std::unordered_map<std::string, Prop>::const_iterator begin() const;
    std::unordered_map<std::string, Prop>::const_iterator end() const;

    /**
     * The ID of the DRM object these properties relate to
    */
    uint32_t parent_id() const;

private:
    uint32_t const parent_id_;
    std::unordered_map<std::string, Prop> const properties_table;
};

class PlaneResources
{
public:
    explicit PlaneResources(int drm_fd);

    detail::ObjectCollection<DRMModePlaneUPtr, &get_plane> planes() const;

private:
    int const drm_fd;
    DRMModePlaneResUPtr const resources;
};

class DRMModeResources
{
public:
    explicit DRMModeResources(int drm_fd);

    void for_each_connector(std::function<void(DRMModeConnectorUPtr)> const& f) const;

    void for_each_encoder(std::function<void(DRMModeEncoderUPtr)> const& f) const;

    void for_each_crtc(std::function<void(DRMModeCrtcUPtr)> const& f) const;

    size_t num_connectors() const;

    size_t num_encoders() const;

    size_t num_crtcs() const;

    DRMModeConnectorUPtr connector(uint32_t id) const;
    DRMModeEncoderUPtr encoder(uint32_t id) const;
    DRMModeCrtcUPtr crtc(uint32_t id) const;

    detail::ObjectCollection<DRMModeConnectorUPtr, &get_connector> connectors() const;
    detail::ObjectCollection<DRMModeEncoderUPtr, &get_encoder> encoders() const;
    detail::ObjectCollection<DRMModeCrtcUPtr, &get_crtc> crtcs() const;

private:
    int const drm_fd;
    DRMModeResUPtr const resources;
};

auto get_cap_checked(mir::Fd const& drm_node, uint64_t cap) -> uint64_t;

}
}
}

#endif /* MIR_GRAPHICS_COMMON_KMS_UTILS_DRM_MODE_RESOURCES_H_ */
