/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2021, Ideas on Board Oy
 *
 * DRM/KMS Helpers
 */

#pragma once

#include <array>
#include <list>
#include <map>
#include <memory>
#include <stdint.h>
#include <string>
#include <vector>

#include <libcamera/base/signal.h>
#include <libcamera/base/span.h>

#include <libdrm/drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

namespace libcamera {
class FrameBuffer;
class PixelFormat;
class Size;
} /* namespace libcamera */

namespace DRM {

class Device;
class Plane;
class Property;
class PropertyValue;

class Object
{
public:
	enum Type {
		TypeCrtc = DRM_MODE_OBJECT_CRTC,
		TypeConnector = DRM_MODE_OBJECT_CONNECTOR,
		TypeEncoder = DRM_MODE_OBJECT_ENCODER,
		TypeMode = DRM_MODE_OBJECT_MODE,
		TypeProperty = DRM_MODE_OBJECT_PROPERTY,
		TypeFb = DRM_MODE_OBJECT_FB,
		TypeBlob = DRM_MODE_OBJECT_BLOB,
		TypePlane = DRM_MODE_OBJECT_PLANE,
		TypeAny = DRM_MODE_OBJECT_ANY,
	};

	Object(Device *dev, uint32_t id, Type type);
	virtual ~Object();

	Device *device() const { return dev_; }
	uint32_t id() const { return id_; }
	Type type() const { return type_; }

	const Property *property(const std::string &name) const;
	const PropertyValue *propertyValue(const std::string &name) const;
	const std::vector<PropertyValue> &properties() const { return properties_; }

protected:
	virtual int setup()
	{
		return 0;
	}

	uint32_t id_;

private:
	friend Device;

	Device *dev_;
	Type type_;
	std::vector<PropertyValue> properties_;
};

class Property : public Object
{
public:
	enum Type {
		TypeUnknown = 0,
		TypeRange,
		TypeEnum,
		TypeBlob,
		TypeBitmask,
		TypeObject,
		TypeSignedRange,
	};

	Property(Device *dev, drmModePropertyRes *property);

	Type type() const { return type_; }
	const std::string &name() const { return name_; }

	bool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; }

	const std::vector<uint64_t> values() const { return values_; }
	const std::map<uint32_t, std::string> &enums() const { return enums_; }
	const std::vector<uint32_t> blobs() const { return blobs_; }

private:
	Type type_;
	std::string name_;
	uint32_t flags_;
	std::vector<uint64_t> values_;
	std::map<uint32_t, std::string> enums_;
	std::vector<uint32_t> blobs_;
};

class PropertyValue
{
public:
	PropertyValue(uint32_t id, uint64_t value)
		: id_(id), value_(value)
	{
	}

	uint32_t id() const { return id_; }
	uint32_t value() const { return value_; }

private:
	uint32_t id_;
	uint64_t value_;
};

class Blob : public Object
{
public:
	Blob(Device *dev, const libcamera::Span<const uint8_t> &data);
	~Blob();

	bool isValid() const { return id() != 0; }
};

class Mode : public drmModeModeInfo
{
public:
	Mode(const drmModeModeInfo &mode);

	std::unique_ptr<Blob> toBlob(Device *dev) const;
};

class Crtc : public Object
{
public:
	Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index);

	unsigned int index() const { return index_; }
	const std::vector<const Plane *> &planes() const { return planes_; }

private:
	friend Device;

	unsigned int index_;
	std::vector<const Plane *> planes_;
};

class Encoder : public Object
{
public:
	Encoder(Device *dev, const drmModeEncoder *encoder);

	uint32_t type() const { return type_; }

	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }

private:
	uint32_t type_;
	std::vector<const Crtc *> possibleCrtcs_;
};

class Connector : public Object
{
public:
	enum Status {
		Connected,
		Disconnected,
		Unknown,
	};

	Connector(Device *dev, const drmModeConnector *connector);

	uint32_t type() const { return type_; }
	const std::string &name() const { return name_; }

	Status status() const { return status_; }

	const std::vector<const Encoder *> &encoders() const { return encoders_; }
	const std::vector<Mode> &modes() const { return modes_; }

private:
	uint32_t type_;
	std::string name_;
	Status status_;
	std::vector<const Encoder *> encoders_;
	std::vector<Mode> modes_;
};

class Plane : public Object
{
public:
	enum Type {
		TypeOverlay,
		TypePrimary,
		TypeCursor,
	};

	Plane(Device *dev, const drmModePlane *plane);

	Type type() const { return type_; }
	const std::vector<uint32_t> &formats() const { return formats_; }
	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }

	bool supportsFormat(const libcamera::PixelFormat &format) const;

protected:
	int setup() override;

private:
	friend class Device;

	Type type_;
	std::vector<uint32_t> formats_;
	std::vector<const Crtc *> possibleCrtcs_;
	uint32_t possibleCrtcsMask_;
};

class FrameBuffer : public Object
{
public:
	struct Plane {
		uint32_t handle;
	};

	~FrameBuffer();

private:
	friend class Device;

	FrameBuffer(Device *dev);

	std::map<int, Plane> planes_;
};

class AtomicRequest
{
public:
	enum Flags {
		FlagAllowModeset = (1 << 0),
		FlagAsync = (1 << 1),
		FlagTestOnly = (1 << 2),
	};

	AtomicRequest(Device *dev);
	~AtomicRequest();

	Device *device() const { return dev_; }
	bool isValid() const { return valid_; }

	int addProperty(const Object *object, const std::string &property,
			uint64_t value);
	int addProperty(const Object *object, const std::string &property,
			std::unique_ptr<Blob> blob);
	int commit(unsigned int flags = 0);

private:
	AtomicRequest(const AtomicRequest &) = delete;
	AtomicRequest(const AtomicRequest &&) = delete;
	AtomicRequest &operator=(const AtomicRequest &) = delete;
	AtomicRequest &operator=(const AtomicRequest &&) = delete;

	int addProperty(uint32_t object, uint32_t property, uint64_t value);

	Device *dev_;
	bool valid_;
	drmModeAtomicReq *request_;
	std::list<std::unique_ptr<Blob>> blobs_;
};

class Device
{
public:
	Device();
	~Device();

	int init();

	int fd() const { return fd_; }

	const std::list<Crtc> &crtcs() const { return crtcs_; }
	const std::list<Encoder> &encoders() const { return encoders_; }
	const std::list<Connector> &connectors() const { return connectors_; }
	const std::list<Plane> &planes() const { return planes_; }
	const std::list<Property> &properties() const { return properties_; }

	const Object *object(uint32_t id);

	std::unique_ptr<FrameBuffer> createFrameBuffer(
		const libcamera::FrameBuffer &buffer,
		const libcamera::PixelFormat &format,
		const libcamera::Size &size,
		const std::array<uint32_t, 4> &strides);

	libcamera::Signal<AtomicRequest *> requestComplete;

private:
	Device(const Device &) = delete;
	Device(const Device &&) = delete;
	Device &operator=(const Device &) = delete;
	Device &operator=(const Device &&) = delete;

	int openCard();
	int getResources();

	void drmEvent();
	static void pageFlipComplete(int fd, unsigned int sequence,
				     unsigned int tv_sec, unsigned int tv_usec,
				     void *user_data);

	int fd_;

	std::list<Crtc> crtcs_;
	std::list<Encoder> encoders_;
	std::list<Connector> connectors_;
	std::list<Plane> planes_;
	std::list<Property> properties_;

	std::map<uint32_t, Object *> objects_;
};

} /* namespace DRM */