/*
 * 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_DISPLAY_CONFIGURATION_H_
#define MIR_GRAPHICS_DISPLAY_CONFIGURATION_H_

#include <mir/int_wrapper.h>
#include <mir/geometry/size.h>
#include <mir/geometry/rectangle.h>
#include <mir/geometry/point.h>
#include <mir/graphics/gamma_curves.h>
#include <mir_toolkit/common.h>

#include <glm/glm.hpp>

#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <vector>

namespace mir
{
namespace graphics
{
namespace detail
{
struct GraphicsConfCardIdTag;
struct GraphicsConfOutputIdTag;
struct GraphicsConfLogicalGroupId;
}

using DisplayConfigurationCardId = IntWrapper<detail::GraphicsConfCardIdTag>;
using DisplayConfigurationOutputId = IntWrapper<detail::GraphicsConfOutputIdTag>;
using DisplayConfigurationLogicalGroupId = IntWrapper<detail::GraphicsConfLogicalGroupId>;

/**
 * Configuration information for a display card.
 */
struct DisplayConfigurationCard
{
    DisplayConfigurationCardId id;
    size_t max_simultaneous_outputs;

    bool operator==(DisplayConfigurationCard const&) const = default;
};

/**
 * The type of a display output.
 */
enum class DisplayConfigurationOutputType
{
    unknown = mir_output_type_unknown,
    vga = mir_output_type_vga,
    dvii = mir_output_type_dvii,
    dvid = mir_output_type_dvid,
    dvia = mir_output_type_dvia,
    composite = mir_output_type_composite,
    svideo = mir_output_type_svideo,
    lvds = mir_output_type_lvds,
    component = mir_output_type_component,
    ninepindin = mir_output_type_ninepindin,
    displayport = mir_output_type_displayport,
    hdmia = mir_output_type_hdmia,
    hdmib = mir_output_type_hdmib,
    tv = mir_output_type_tv,
    edp = mir_output_type_edp,
    virt = mir_output_type_virtual,
    dsi = mir_output_type_dsi,
    dpi = mir_output_type_dpi,
};

/**
 * Configuration information for a display output mode.
 */
struct DisplayConfigurationMode
{
    geometry::Size size;
    double vrefresh_hz;

    bool operator==(DisplayConfigurationMode const&) const = default;
};

/**
 * Hardware information for a display output.
 */
struct DisplayInfo
{
    DisplayInfo() = default;
    explicit DisplayInfo(std::vector<uint8_t> const& edid);
    DisplayInfo& operator=(std::vector<uint8_t> const& edid);

    std::vector<uint8_t> raw_edid;
    std::optional<std::string> vendor;
    std::optional<std::string> model;
    std::optional<std::string> serial;
    std::optional<uint16_t> product_code;
    std::optional<uint32_t> serial_number;
};

/**
 * Configuration information for a display output.
 */
struct DisplayConfigurationOutput
{
    /** The output's id. */
    DisplayConfigurationOutputId id;
    /** The id of the card the output is connected to. */
    DisplayConfigurationCardId card_id;
    /** The id of the logical output group (aka display wall) this output belongs to, or 0 for none. */
    DisplayConfigurationLogicalGroupId logical_group_id;
    /** The type of the output. */
    DisplayConfigurationOutputType type;
    /** The pixel formats supported by the output */
    std::vector<MirPixelFormat> pixel_formats;
    /** The modes supported by the output. */
    std::vector<DisplayConfigurationMode> modes;
    /** The index in the 'modes' vector of the preferred output mode. */
    uint32_t preferred_mode_index;
    /** The physical size of the output. */
    geometry::Size physical_size_mm;
    /** Whether the output is connected. */
    bool connected;
    /** Whether the output is used in the configuration. */
    bool used;
    /** The top left point of this output in the virtual coordinate space. */
    geometry::Point top_left;
    /** The index in the 'modes' vector of the current output mode. */
    uint32_t current_mode_index;
    /** The current output pixel format. A matching entry should be found in the 'pixel_formats' vector*/
    MirPixelFormat current_format;
    /** Current power mode **/
    MirPowerMode power_mode;
    MirOrientation orientation;

    /** Requested scale factor for this output, for HiDPI support */
    float scale{1.0f};
    /** Form factor of this output; phone display, tablet, monitor, TV, projector... */
    MirFormFactor form_factor;

    /** Subpixel arrangement of this output */
    MirSubpixelArrangement subpixel_arrangement;

    /** The current gamma for the display */
    GammaCurves gamma;
    MirOutputGammaSupported gamma_supported;

    std::optional<geometry::Size> custom_logical_size;

    /** The output's displayable name. */
    // TODO: Better output names that are consistant between sessions
    std::string name = "OUT-" + std::to_string(id.as_value());

    /// Custom attributes (typically set via the .display configuration file
    std::map<std::string, std::optional<std::string>> custom_attribute = {};

    /// Display information for this output, if available.
    DisplayInfo display_info{};

    /** The logical rectangle occupied by the output, based on its position,
        current mode and orientation (rotation) */
    geometry::Rectangle extents() const;
    bool valid() const;

    /**
     * The additional transformation (if any) that this output must undergo
     * to appear correctly on screen, including rotation (orientation),
     * inversion and skew.
     *   Note that scaling and translation are not part of this transformation
     * matrix because those are already built in to the extents() rectangle
     * for the renderer to use in compositing.
     */
    glm::mat2 transformation() const;
};

/**
 * Mirror of a DisplayConfigurationOutput, with some fields limited to
 * being read-only, preventing users from changing things they shouldn't.
 */
struct UserDisplayConfigurationOutput
{
    DisplayConfigurationOutputId const& id;
    DisplayConfigurationCardId const& card_id;
    DisplayConfigurationLogicalGroupId& logical_group_id;
    DisplayConfigurationOutputType const& type;
    std::vector<MirPixelFormat> const& pixel_formats;
    std::vector<DisplayConfigurationMode> const& modes;
    uint32_t const& preferred_mode_index;
    geometry::Size const& physical_size_mm;
    bool const& connected;
    bool& used;
    geometry::Point& top_left;
    uint32_t& current_mode_index;
    MirPixelFormat& current_format;
    MirPowerMode& power_mode;
    MirOrientation& orientation;
    float& scale;
    MirFormFactor& form_factor;
    MirSubpixelArrangement& subpixel_arrangement;
    GammaCurves& gamma;
    MirOutputGammaSupported const& gamma_supported;
    std::optional<geometry::Size>& custom_logical_size;
    std::string const& name;
    /// Custom attributes (typically set by the .display configuration file
    std::map<std::string, std::optional<std::string>>& custom_attribute;
    DisplayInfo const& display_info;

    UserDisplayConfigurationOutput(DisplayConfigurationOutput& main);
    geometry::Rectangle extents() const;
};

std::ostream& operator<<(std::ostream& out, DisplayConfigurationCard const& val);

std::ostream& operator<<(std::ostream& out, DisplayConfigurationMode const& val);

std::ostream& operator<<(std::ostream& out, DisplayConfigurationOutput const& val);
bool operator==(DisplayConfigurationOutput const& val1, DisplayConfigurationOutput const& val2);

/**
 * Interface to a configuration of display cards and outputs.
 */
class DisplayConfiguration
{
public:
    virtual ~DisplayConfiguration() = default;

    /** Executes a function object for each output in the configuration. */
    virtual void for_each_output(std::function<void(DisplayConfigurationOutput const&)> f) const = 0;
    virtual void for_each_output(std::function<void(UserDisplayConfigurationOutput&)> f) = 0;

    virtual std::unique_ptr<DisplayConfiguration> clone() const = 0;

    virtual bool valid() const;

protected:
    DisplayConfiguration() = default;
    DisplayConfiguration(DisplayConfiguration const& c) = delete;
    DisplayConfiguration& operator=(DisplayConfiguration const& c) = delete;
};

bool operator==(DisplayConfiguration const& lhs, DisplayConfiguration const& rhs);

std::ostream& operator<<(std::ostream& out, DisplayConfiguration const& val);

}
}

#endif /* MIR_GRAPHICS_DISPLAY_CONFIGURATION_H_ */
