#ifndef _VA_RESOURCE_POOL
#define _VA_RESOURCE_POOL 1

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <va/va.h>

#include <list>
#include <mutex>

#define CHECK_VASTATUS(va_status, func) \
	if (va_status != VA_STATUS_SUCCESS) { \
		fprintf(stderr, "%s:%d (%s) failed: %s\n", __func__, __LINE__, func, vaErrorStr(va_status)); \
		exit(1); \
	}

#define CHECK_VASTATUS_RET(va_status, func) \
	if (va_status != VA_STATUS_SUCCESS) { \
		fprintf(stderr, "%s:%d (%s) failed with %d\n", __func__, __LINE__, func, va_status); \
		return nullptr; \
	}

class VAResourcePool {
public:
	struct VAResources {
		unsigned width, height;
		uint32_t fourcc;
		VASurfaceID surface;
		VAContextID context;
		VABufferID data_buffer;
		VAImage image;
	};

	VAResourcePool(VADisplay va_dpy, VAImageFormat uyvy_format, VAImageFormat nv12_format, VAConfigID config_id_422, VAConfigID config_id_420, bool with_data_buffer)
		: va_dpy(va_dpy),
		  uyvy_format(uyvy_format),
		  nv12_format(nv12_format),
		  config_id_422(config_id_422),
		  config_id_420(config_id_420),
		  with_data_buffer(with_data_buffer) {}
	VAResources get_va_resources(unsigned width, unsigned height, uint32_t fourcc);
	void release_va_resources(VAResources resources);

private:
	const VADisplay va_dpy;
	VAImageFormat uyvy_format, nv12_format;
	const VAConfigID config_id_422, config_id_420;
	const bool with_data_buffer;

	std::mutex mu;
	std::list<VAResources> freelist;  // Under mu.
};

// RAII wrapper to release VAResources on return (even on error).
class ReleaseVAResources {
public:
	ReleaseVAResources() : committed(true) {}

	ReleaseVAResources(VAResourcePool *pool, const VAResourcePool::VAResources &resources)
		: pool(pool), resources(resources) {}

	ReleaseVAResources(ReleaseVAResources &) = delete;

	ReleaseVAResources(ReleaseVAResources &&other)
		: pool(other.pool), resources(other.resources), committed(other.committed) {
		other.commit();
	}

	ReleaseVAResources &operator= (ReleaseVAResources &) = delete;

	ReleaseVAResources &operator= (ReleaseVAResources &&other) {
		if (!committed) {
			pool->release_va_resources(resources);
		}
		pool = other.pool;
		resources = std::move(other.resources);
		committed = other.committed;
		other.commit();
		return *this;
	}

	~ReleaseVAResources()
	{
		if (!committed) {
			pool->release_va_resources(resources);
		}
	}

	void commit() { committed = true; }

private:
	VAResourcePool *pool = nullptr;
	VAResourcePool::VAResources resources;
	bool committed = false;
};

#endif  // !defined(_VA_RESOURCE_POOL)
