// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "DisplaySurfaceKHR.hpp"

#include "Vulkan/VkDeviceMemory.hpp"
#include "Vulkan/VkImage.hpp"

#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>

namespace vk {

static int openCard()
{
	constexpr size_t DIR_NAME_MAX = sizeof("/dev/dri/") - 1;
	constexpr size_t PRE_NODE_NAME_MAX = sizeof("card") - 1;
	constexpr size_t POST_NODE_NAME_MAX = sizeof("255") - 1;
	constexpr size_t NODE_NAME_MAX =
	    DIR_NAME_MAX + PRE_NODE_NAME_MAX + POST_NODE_NAME_MAX;
	char name[NODE_NAME_MAX] = "/dev/dri/";
	int fd = -VK_NOT_READY;

	/*
	 * Open the first DRM/KMS device. The libdrm drmOpen*() functions
	 * from drmOpen() is of no practical use as any modern system will
	 * handle that through udev or an equivalent component.
	 */
	DIR *folder = opendir(name);
	if(!folder)
	{
		return -errno;
	}

	strncat(name, "card", 5);
	for(struct dirent *res; (res = readdir(folder));)
	{
		if(!strncmp(res->d_name, "card", 4))
		{
			strncat(name, res->d_name + PRE_NODE_NAME_MAX, 4);
			fd = open(name, O_RDWR);
			if(fd >= 0)
			{
				break;
			}

			name[DIR_NAME_MAX + PRE_NODE_NAME_MAX] = 0;
			fd = -errno;
		}
	}

	closedir(folder);

	return fd;
}

VkResult DisplaySurfaceKHR::GetDisplayModeProperties(uint32_t *pPropertyCount, VkDisplayModePropertiesKHR *pProperties)
{
	*pPropertyCount = 1;

	if(pProperties)
	{
		const int fd = openCard();
		if(fd < 0)
		{
			return VK_NOT_READY;
		}

		drmModeRes *res = drmModeGetResources(fd);
		drmModeConnector *connector = drmModeGetConnector(fd, res->connectors[0]);
		pProperties->displayMode = (uintptr_t)connector->modes[0].name;
		pProperties->parameters.visibleRegion.width = connector->modes[0].hdisplay;
		pProperties->parameters.visibleRegion.height = connector->modes[0].vdisplay;
		pProperties->parameters.refreshRate = connector->modes[0].vrefresh * 1000;
		drmModeFreeConnector(connector);
		drmModeFreeResources(res);
		close(fd);
	}

	return VK_SUCCESS;
}

VkResult DisplaySurfaceKHR::GetDisplayPlaneCapabilities(VkDisplayPlaneCapabilitiesKHR *pCapabilities)
{
	const int fd = openCard();
	if(fd < 0)
	{
		return VK_NOT_READY;
	}

	drmModeRes *res = drmModeGetResources(fd);
	drmModeConnector *connector = drmModeGetConnector(fd, res->connectors[0]);
	pCapabilities->supportedAlpha = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
	pCapabilities->minSrcPosition.x = 0;
	pCapabilities->minSrcPosition.y = 0;
	pCapabilities->maxSrcPosition.x = 0;
	pCapabilities->maxSrcPosition.y = 0;
	pCapabilities->minSrcExtent.width = connector->modes[0].hdisplay;
	pCapabilities->minSrcExtent.height = connector->modes[0].vdisplay;
	pCapabilities->maxSrcExtent.width = connector->modes[0].hdisplay;
	pCapabilities->maxSrcExtent.height = connector->modes[0].vdisplay;
	pCapabilities->minDstPosition.x = 0;
	pCapabilities->minDstPosition.y = 0;
	pCapabilities->maxDstPosition.x = 0;
	pCapabilities->maxDstPosition.y = 0;
	pCapabilities->minDstExtent.width = connector->modes[0].hdisplay;
	pCapabilities->minDstExtent.height = connector->modes[0].vdisplay;
	pCapabilities->maxDstExtent.width = connector->modes[0].hdisplay;
	pCapabilities->maxDstExtent.height = connector->modes[0].vdisplay;
	drmModeFreeConnector(connector);
	drmModeFreeResources(res);
	close(fd);

	return VK_SUCCESS;
}

VkResult DisplaySurfaceKHR::GetDisplayPlaneSupportedDisplays(uint32_t *pDisplayCount, VkDisplayKHR *pDisplays)
{
	*pDisplayCount = 1;

	if(pDisplays)
	{
		const int fd = openCard();
		if(fd < 0)
		{
			return VK_NOT_READY;
		}

		drmModeRes *res = drmModeGetResources(fd);
		*pDisplays = res->connectors[0];
		drmModeFreeResources(res);
		close(fd);
	}

	return VK_SUCCESS;
}

VkResult DisplaySurfaceKHR::GetPhysicalDeviceDisplayPlaneProperties(uint32_t *pPropertyCount, VkDisplayPlanePropertiesKHR *pProperties)
{
	*pPropertyCount = 1;

	if(pProperties)
	{
		const int fd = openCard();
		if(fd < 0)
		{
			return VK_NOT_READY;
		}

		drmModeRes *res = drmModeGetResources(fd);
		pProperties->currentDisplay = res->connectors[0];
		pProperties->currentStackIndex = 0;
		drmModeFreeResources(res);
		close(fd);
	}

	return VK_SUCCESS;
}

VkResult DisplaySurfaceKHR::GetPhysicalDeviceDisplayProperties(uint32_t *pPropertyCount, VkDisplayPropertiesKHR *pProperties)
{
	*pPropertyCount = 1;

	if(pProperties)
	{
		const int fd = openCard();
		if(fd < 0)
		{
			return VK_NOT_READY;
		}

		drmModeRes *res = drmModeGetResources(fd);
		drmModeConnector *connector = drmModeGetConnector(fd, res->connectors[0]);
		pProperties->display = res->connectors[0];
		pProperties->displayName = "monitor";
		pProperties->physicalDimensions.width = connector->mmWidth;
		pProperties->physicalDimensions.height = connector->mmHeight;
		if(pProperties->physicalDimensions.width <= 0 || pProperties->physicalDimensions.height <= 0)
		{
			pProperties->physicalDimensions.width = connector->modes[0].hdisplay * 25.4 / 96;
			pProperties->physicalDimensions.height = connector->modes[0].vdisplay * 25.4 / 96;
		}
		pProperties->physicalResolution.width = connector->modes[0].hdisplay;
		pProperties->physicalResolution.height = connector->modes[0].vdisplay;
		pProperties->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
		pProperties->planeReorderPossible = VK_FALSE;
		pProperties->persistentContent = VK_FALSE;
		drmModeFreeConnector(connector);
		drmModeFreeResources(res);
		close(fd);
	}

	return VK_SUCCESS;
}

DisplaySurfaceKHR::DisplaySurfaceKHR(const VkDisplaySurfaceCreateInfoKHR *pCreateInfo, void *mem)
{
	fd = openCard();
	if(fd < 0)
	{
		return;
	}

	drmModeRes *res = drmModeGetResources(fd);
	connector_id = res->connectors[0];
	drmModeFreeResources(res);
	drmModeConnector *connector = drmModeGetConnector(fd, connector_id);
	encoder_id = connector->encoder_id;
	memcpy(&mode_info, &connector->modes[0], sizeof(drmModeModeInfo));
	drmModeFreeConnector(connector);
	drmModeEncoder *encoder = drmModeGetEncoder(fd, encoder_id);
	crtc_id = encoder->crtc_id;
	drmModeFreeEncoder(encoder);

	crtc = drmModeGetCrtc(fd, crtc_id);

	struct drm_mode_create_dumb creq;
	memset(&creq, 0, sizeof(struct drm_mode_create_dumb));
	creq.width = mode_info.hdisplay;
	creq.height = mode_info.vdisplay;
	creq.bpp = 32;
	drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);

	handle = creq.handle;
	width = creq.width;
	height = creq.height;
	pitch = creq.pitch;
	size = creq.size;

	drmModeAddFB(fd, width, height, 24, 32, pitch, handle, &fb_id);

	struct drm_mode_map_dumb mreq;
	memset(&mreq, 0, sizeof(struct drm_mode_map_dumb));
	mreq.handle = handle;
	drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);

	fb_buffer = static_cast<uint8_t *>(mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd, mreq.offset));
}

void DisplaySurfaceKHR::destroySurface(const VkAllocationCallbacks *pAllocator)
{
	munmap(fb_buffer, size);

	drmModeRmFB(fd, fb_id);

	struct drm_mode_destroy_dumb dreq;
	memset(&dreq, 0, sizeof(struct drm_mode_destroy_dumb));
	dreq.handle = handle;
	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);

	drmModeSetCrtc(fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, &connector_id, 1, &crtc->mode);
	drmModeFreeCrtc(crtc);

	close(fd);
}

size_t DisplaySurfaceKHR::ComputeRequiredAllocationSize(const VkDisplaySurfaceCreateInfoKHR *pCreateInfo)
{
	return 0;
}

VkResult DisplaySurfaceKHR::getSurfaceCapabilities(const void *pSurfaceInfoPNext, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities, void *pSurfaceCapabilitiesPNext) const
{
	VkExtent2D extent = { width, height };

	pSurfaceCapabilities->currentExtent = extent;
	pSurfaceCapabilities->minImageExtent = extent;
	pSurfaceCapabilities->maxImageExtent = extent;

	setCommonSurfaceCapabilities(pSurfaceInfoPNext, pSurfaceCapabilities, pSurfaceCapabilitiesPNext);
	return VK_SUCCESS;
}

void DisplaySurfaceKHR::attachImage(PresentImage *image)
{
}

void DisplaySurfaceKHR::detachImage(PresentImage *image)
{
}

VkResult DisplaySurfaceKHR::present(PresentImage *image)
{
	image->getImage()->copyTo(fb_buffer, pitch);
	drmModeSetCrtc(fd, crtc_id, fb_id, 0, 0, &connector_id, 1, &mode_info);

	return VK_SUCCESS;
}

}  // namespace vk
