/*
 * Copyright © 2008-2011 Kristian Høgsberg
 * Copyright © 2011 Intel Corporation
 * Copyright © 2017, 2018 Collabora, Ltd.
 * Copyright © 2017, 2018 General Electric Company
 * Copyright (c) 2018 DisplayLink (UK) Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "drm-internal.h"
#include "pixel-formats.h"
#include "renderer-gl/gl-renderer.h"
#include "shared/weston-assert.h"

#define POISON_PTR ((void *)8)

/**
 * Create a drm_crtc for virtual output
 *
 * It will leave its ID and pipe zeroed, as virtual outputs should not use real
 * CRTC's. Also, as this is a fake CRTC, it will not try to populate props.
 */
static struct drm_crtc *
drm_virtual_crtc_create(struct drm_device *device, struct drm_output *output)
{
	struct drm_crtc *crtc;

	crtc = zalloc(sizeof(*crtc));
	if (!crtc)
		return NULL;

	crtc->device = device;
	crtc->output = output;

	crtc->crtc_id = 0;
	crtc->pipe = 0;

	/* Poisoning the pointers as CRTC's of virtual outputs should not be
	 * added to the DRM-backend CRTC list. With this we can assure (in
	 * function drm_virtual_crtc_destroy()) that this did not happen. */
	crtc->link.prev = POISON_PTR;
	crtc->link.next = POISON_PTR;

	return crtc;
}

/**
 * Destroy drm_crtc created by drm_virtual_crtc_create()
 */
static void
drm_virtual_crtc_destroy(struct drm_crtc *crtc)
{
	assert(crtc->link.prev == POISON_PTR);
	assert(crtc->link.next == POISON_PTR);
	free(crtc);
}

static uint32_t
get_drm_plane_index_maximum(struct drm_device *device)
{
	uint32_t max = 0;
	struct drm_plane *p;

	wl_list_for_each(p, &device->plane_list, link) {
		if (p->plane_idx > max)
			max = p->plane_idx;
	}

	return max;
}

/**
 * Create a drm_plane for virtual output
 *
 * Call drm_virtual_plane_destroy to clean up the plane.
 *
 * @param device DRM device
 * @param output Output to create internal plane for
 */
static struct drm_plane_handle *
drm_virtual_plane_create(struct drm_device *device, struct drm_output *output)
{
	struct drm_backend *b = device->backend;
	struct drm_plane *plane;
	struct weston_drm_format *fmt;
	uint64_t mod;

	plane = zalloc(sizeof(*plane));
	if (!plane) {
		weston_log("%s: out of memory\n", __func__);
		return NULL;
	}

	plane->type = WDRM_PLANE_TYPE_PRIMARY;
	plane->device = device;
	plane->state_cur = drm_plane_state_alloc(NULL, plane);
	plane->state_cur->complete = true;

	weston_drm_format_array_init(&plane->formats);
	fmt = weston_drm_format_array_add_format(&plane->formats,
						 output->format->format);
	if (!fmt)
		goto err;

	/* If output supports linear modifier, we add it to the plane.
	 * Otherwise we add DRM_FORMAT_MOD_INVALID, as explicit modifiers
	 * are not supported. */
	if ((output->gbm_bo_flags & GBM_BO_USE_LINEAR) && device->fb_modifiers)
		mod = DRM_FORMAT_MOD_LINEAR;
	else
		mod = DRM_FORMAT_MOD_INVALID;

	if (weston_drm_format_add_modifier(fmt, mod) < 0)
		goto err;

	weston_plane_init(&plane->base, b->compositor);

	plane->plane_idx = get_drm_plane_index_maximum(device) + 1;
	wl_list_insert(&device->plane_list, &plane->link);

	return drm_plane_create_handle(plane, output);

err:
	drm_plane_state_free(plane->state_cur, true);
	weston_drm_format_array_fini(&plane->formats);
	free(plane);
	return NULL;
}

/**
 * Destroy one DRM plane
 *
 * @param handle Plane handle to deallocate (will be freed along with the plane)
 */
static void
drm_virtual_plane_destroy(struct drm_plane_handle *handle)
{
	struct drm_plane *plane = handle->plane;

	drm_plane_destroy_handle(handle);

	drm_plane_state_free(plane->state_cur, true);
	weston_plane_release(&plane->base);
	wl_list_remove(&plane->link);
	weston_drm_format_array_fini(&plane->formats);
	free(plane);
}

static int
drm_virtual_output_start_repaint_loop(struct weston_output *output_base)
{
	weston_output_finish_frame(output_base, NULL,
				   WP_PRESENTATION_FEEDBACK_INVALID);

	return 0;
}

static int
drm_virtual_output_submit_frame(struct drm_output *output,
				struct drm_fb *fb)
{
	int fd, ret;

	assert(fb->num_planes == 1);
	ret = drmPrimeHandleToFD(fb->fd, fb->handles[0], DRM_CLOEXEC, &fd);
	if (ret) {
		weston_log("drmPrimeHandleFD failed, errno=%d\n", errno);
		return -1;
	}

	drm_fb_ref(fb);
	ret = output->virtual_submit_frame(&output->base, fd, fb->strides[0],
					   fb);
	if (ret < 0) {
		drm_fb_unref(fb);
		close(fd);
	}
	return ret;
}

static int
drm_virtual_output_repaint(struct weston_output *output_base)
{
	struct drm_output_state *state = NULL;
	struct weston_compositor *compositor = output_base->compositor;
	struct drm_output *output = to_drm_output(output_base);
	struct drm_plane *scanout_plane = output->scanout_handle->plane;
	struct drm_plane_state *scanout_state;
	struct drm_pending_state *pending_state;
	struct drm_device *device;

	assert(output->is_virtual);

	device = output->device;
	pending_state = device->repaint_data;

	if (output->disable_pending || output->destroy_pending)
		goto err;

	/* Drop frame if there isn't free buffers */
	if (!gbm_surface_has_free_buffers(output->gbm_surface)) {
		weston_log("%s: Drop frame!!\n", __func__);
		return -1;
	}

	assert(!output->state_last);

	/* assign_planes() is always called before a repaint, so we must have a
	 * valid output state here. */
	state = drm_pending_state_get_output(pending_state, output);
	weston_assert_ptr_not_null(compositor, state);

	drm_output_render(state);
	scanout_state = drm_output_state_get_plane(state, scanout_plane);
	if (!scanout_state || !scanout_state->fb)
		goto err;

	if (drm_virtual_output_submit_frame(output, scanout_state->fb) < 0)
		goto err;

	return 0;

err:
	drm_output_state_free(state);
	return -1;
}

static void
drm_virtual_output_deinit(struct weston_output *base)
{
	struct drm_output *output = to_drm_output(base);

	drm_output_fini_egl(output);

	drm_virtual_plane_destroy(output->scanout_handle);
	drm_virtual_crtc_destroy(output->crtc);
}

void
drm_virtual_output_destroy(struct weston_output *base)
{
	struct drm_output *output = to_drm_output(base);

	assert(output->is_virtual);

	if (output->base.enabled)
		drm_virtual_output_deinit(&output->base);

	weston_output_release(&output->base);

	drm_output_state_free(output->state_cur);

	if (output->virtual_destroy)
		output->virtual_destroy(base);

	free(output);
}

static int
drm_virtual_output_enable(struct weston_output *output_base)
{
	struct drm_output *output = to_drm_output(output_base);
	struct drm_device *device = output->device;
	struct drm_backend *b = device->backend;

	assert(output->is_virtual);

	if (output_base->compositor->renderer->type == WESTON_RENDERER_PIXMAN) {
		weston_log("Not support pixman renderer on Virtual output\n");
		goto err;
	}

	if (!output->virtual_submit_frame) {
		weston_log("The virtual_submit_frame hook is not set\n");
		goto err;
	}

	output->scanout_handle = drm_virtual_plane_create(device, output);
	if (!output->scanout_handle) {
		weston_log("Failed to find primary plane for output %s\n",
			   output->base.name);
		return -1;
	}

	if (drm_output_init_egl(output, b) < 0) {
		weston_log("Failed to init output gl state\n");
		goto err;
	}

	output->base.start_repaint_loop = drm_virtual_output_start_repaint_loop;
	output->base.repaint = drm_virtual_output_repaint;
	output->base.assign_planes = drm_assign_planes;
	output->base.set_dpms = NULL;
	output->base.switch_mode = NULL;

	return 0;
err:
	return -1;
}

static int
drm_virtual_output_disable(struct weston_output *base)
{
	struct drm_output *output = to_drm_output(base);

	assert(output->is_virtual);

	if (output->base.enabled)
		drm_virtual_output_deinit(&output->base);

	return 0;
}

static void
drm_virtual_prepare_repaint(struct weston_output *base)
{
       struct drm_output *output = to_drm_output(base);
       output->device->will_repaint = true;
}

static struct weston_output *
drm_virtual_output_create(struct weston_compositor *c, char *name,
			  void (*destroy_func)(struct weston_output *))
{
	struct drm_output *output;
	struct drm_backend *b = to_drm_backend(c);
	/* Always use the main device for virtual outputs */
	struct drm_device *device = b->drm;

	output = zalloc(sizeof *output);
	if (!output)
		return NULL;

	output->device = device;
	output->crtc = drm_virtual_crtc_create(device, output);
	if (!output->crtc) {
		free(output);
		return NULL;
	}

	output->is_virtual = true;
	output->virtual_destroy = destroy_func;
	output->gbm_bo_flags = GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING;

	weston_output_init(&output->base, c, name);

	output->base.enable = drm_virtual_output_enable;
	output->base.destroy = drm_virtual_output_destroy;
	output->base.disable = drm_virtual_output_disable;
	output->base.prepare_repaint = drm_virtual_prepare_repaint;
	output->base.attach_head = NULL;

	output->backend = b;
	output->base.backend = &b->base;
	output->state_cur = drm_output_state_alloc(output);

	weston_compositor_add_pending_output(&output->base, c);

	return &output->base;
}

static uint32_t
drm_virtual_output_set_gbm_format(struct weston_output *base,
				  const char *gbm_format)
{
	struct drm_output *output = to_drm_output(base);
	struct drm_device *device = output->device;
	struct drm_backend *b = device->backend;

	if (parse_gbm_format(gbm_format, b->format, &output->format) == -1)
		output->format = b->format;

	return output->format->format;
}

static void
drm_virtual_output_set_submit_frame_cb(struct weston_output *output_base,
				       submit_frame_cb cb)
{
	struct drm_output *output = to_drm_output(output_base);

	output->virtual_submit_frame = cb;
}

static int
drm_virtual_output_get_fence_fd(struct weston_output *output_base)
{
	struct weston_compositor *compositor = output_base->compositor;
	const struct weston_renderer *renderer = compositor->renderer;

	return renderer->gl->create_fence_fd(output_base);
}

static void
drm_virtual_output_buffer_released(struct drm_fb *fb)
{
	drm_fb_unref(fb);
}

static void
drm_virtual_output_finish_frame(struct weston_output *output_base,
				struct timespec *stamp,
				uint32_t presented_flags)
{
	struct drm_output *output = to_drm_output(output_base);
	struct drm_plane_state *ps;

	wl_list_for_each(ps, &output->state_cur->plane_list, link)
		ps->complete = true;

	drm_output_state_free(output->state_last);
	output->state_last = NULL;

	weston_output_finish_frame(&output->base, stamp, presented_flags);
}

static const struct weston_drm_virtual_output_api virt_api = {
	drm_virtual_output_create,
	drm_virtual_output_set_gbm_format,
	drm_virtual_output_set_submit_frame_cb,
	drm_virtual_output_get_fence_fd,
	drm_virtual_output_buffer_released,
	drm_virtual_output_finish_frame
};

int drm_backend_init_virtual_output_api(struct weston_compositor *compositor)
{
	return weston_plugin_api_register(compositor,
					  WESTON_DRM_VIRTUAL_OUTPUT_API_NAME,
					  &virt_api, sizeof(virt_api));
}
