/**
 * MVGAL - Multi-Vendor GPU Aggregation Layer for Linux
 * 
 * Intel GPU Driver Integration (i915 and Xe)
 * 
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Study references:
 * - drivers/gpu/drm/i915/ for integrated graphics
 * - drivers/gpu/drm/xe/ for discrete Xe GPUs
 * - Intel Media Driver: https://github.com/intel/media-driver
 * - Intel Linux kernel: https://github.com/intel/linux-intel-lts
 * - Freedesktop Xe kernel driver: https://gitlab.freedesktop.org/drm/intel
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/dma-buf.h>
#include <linux/fs.h>

#include "../mvgal_core.h"
#include "../mvgal_device.h"
#include "../mvgal_scheduler.h"
#include "mvgal_intel.h"

/* Intel vendor operations */
const struct mvgal_vendor_ops mvgal_intel_ops = {
	.init = mvgal_intel_init,
	.fini = mvgal_intel_fini,
	.submit_cs = mvgal_intel_submit_cs,
	.alloc_vram = mvgal_intel_alloc_vram,
	.free_vram = mvgal_intel_free_vram,
	.wait_idle = mvgal_intel_wait_idle,
	.set_power_state = mvgal_intel_set_power_state,
	.export_dmabuf = mvgal_intel_export_dmabuf,
	.import_dmabuf = mvgal_intel_import_dmabuf,
	.query_utilization = mvgal_intel_query_utilization,
};

/*
 * Per-GPU Intel private data
 */
struct mvgal_intel_priv {
	struct pci_dev *pdev;
	struct drm_device *drm_dev;       /* Pointer to i915 or xe DRM device */
	
	/* Driver type */
	bool is_i915;                    /* Using i915 driver */
	bool is_xe;                      /* Using xe driver */
	bool is_dg2;                     /* Intel Arc discrete GPU */
	
	/* Capabilities */
	uint64_t vram_total;             /* Total VRAM for discrete GPUs */
	uint64_t vram_stolen;            /* Stolen memory for integrated graphics */
	uint32_t eu_count;               /* Number of Execution Units */
	
	/* DMA-BUF support */
	bool can_export_dmabuf;
	bool can_import_dmabuf;
	
	/* Power management */
	bool has_rps;                    /* Has Render Power States */
	uint32_t min_freq_mhz;           /* Minimum frequency */
	uint32_t max_freq_mhz;           /* Maximum frequency */
	uint32_t current_freq_mhz;       /* Current frequency */
	
	/* Display */
	bool is_display_connected;      /* This GPU drives a display */
};

/**
 * mvgal_intel_init - Initialize Intel GPU
 */
int mvgal_intel_init(struct mvgal_gpu_device *gpu)
{
	struct mvgal_intel_priv *priv;
	uint16_t device_id = gpu->pci_device_id;

	if (!gpu) {
		return -EINVAL;
	}

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		return -ENOMEM;
	}

	priv->pdev = gpu->pdev;
	
	/* Determine driver type based on device ID */
	/* Intel Arc GPUs (DG2 and later) use xe driver */
	priv->is_dg2 = (device_id >= 0x4680 && device_id <= 0x469F) || /* DG2 */
			(device_id >= 0x5680 && device_id <= 0x569F); /* DG2 for laptop */
	
	/* Most Intel GPUs still use i915 */
	priv->is_i915 = !priv->is_dg2;
	priv->is_xe = priv->is_dg2; /* For now, xe = DG2 */

	/* Query real VRAM from hardware */
	if (priv->is_i915) {
		/* Integrated graphics — query stolen memory from sysfs */
		char sysfs_path[128];
		struct file *f;
		char buf[32];
		loff_t pos = 0;
		ssize_t n;
		unsigned long vram_kb = 0;

		/* Try PCI BDF path for stolen mem_info */
		snprintf(sysfs_path, sizeof(sysfs_path),
			 "/sys/bus/pci/devices/%04x:%02x:%02x.%u/mem_info_vram_total",
			 pci_domain_nr(gpu->pdev->bus),
			 gpu->pdev->bus->number,
			 PCI_SLOT(gpu->pdev->devfn),
			 PCI_FUNC(gpu->pdev->devfn));

		f = filp_open(sysfs_path, O_RDONLY, 0);
		if (!IS_ERR(f)) {
			memset(buf, 0, sizeof(buf));
			n = kernel_read(f, buf, sizeof(buf) - 1, &pos);
			filp_close(f, NULL);
			if (n > 0 && kstrtoul(buf, 10, &vram_kb) == 0 && vram_kb > 0) {
				priv->vram_stolen = (u64)vram_kb * 1024;
			}
		}

		/* Fallback: DRM card path */
		if (priv->vram_stolen == 0) {
			snprintf(sysfs_path, sizeof(sysfs_path),
				 "/sys/class/drm/card%u/device/mem_info_vram_total",
				 gpu->gpu_index);
			f = filp_open(sysfs_path, O_RDONLY, 0);
			if (!IS_ERR(f)) {
				memset(buf, 0, sizeof(buf));
				pos = 0;
				n = kernel_read(f, buf, sizeof(buf) - 1, &pos);
				filp_close(f, NULL);
				if (n > 0 && kstrtoul(buf, 10, &vram_kb) == 0 && vram_kb > 0)
					priv->vram_stolen = (u64)vram_kb * 1024;
			}
		}

		/* Final fallback: 2GB estimate */
		if (priv->vram_stolen == 0)
			priv->vram_stolen = 2ULL * 1024 * 1024 * 1024;

		gpu->vram_size = priv->vram_stolen;
		gpu->vram_bandwidth = 100 * 1024; /* ~100 GB/s — no sysfs for this */
		gpu->compute_units = 128;
	} else {
		/* Discrete Xe GPUs — query VRAM from PCI BAR */
		resource_size_t bar2_size = pci_resource_len(gpu->pdev, 2);
		resource_size_t bar4_size = pci_resource_len(gpu->pdev, 4);

		/* DG2 uses BAR2 or BAR4 for VRAM aperture */
		if (bar4_size > bar2_size && bar4_size > 0x1000000)
			priv->vram_total = bar4_size;
		else if (bar2_size > 0x1000000)
			priv->vram_total = bar2_size;
		else
			priv->vram_total = 8ULL * 1024 * 1024 * 1024; /* 8GB fallback */

		gpu->vram_size = priv->vram_total;
		gpu->vram_bandwidth = 256 * 1024; /* ~256 GB/s */
		gpu->compute_units = 512;
	}

	/* Intel supports Vulkan and OpenCL via open-source drivers */
	gpu->api_flags = MVGAL_API_VULKAN | MVGAL_API_OPENCL;
	
	/* If it's a discrete GPU, also support CUDA via oneAPI */
	if (priv->is_dg2) {
		gpu->api_flags |= MVGAL_API_CUDA; /* Via oneAPI */
	}

	/* All Intel GPUs support DMA-BUF */
	priv->can_export_dmabuf = true;
	priv->can_import_dmabuf = true;

	/* Power management */
	priv->has_rps = true; /* Intel GPUs have RPS (Render Power States) */

	/* Set private data */
	gpu->vendor_priv = priv;

	pr_info("MVGAL: Intel GPU initialized (pci %04x:%04x, driver=%s)\n",
		gpu->pci_vendor_id, gpu->pci_device_id,
		priv->is_i915 ? "i915" : "xe");

	return 0;
}

/**
 * mvgal_intel_fini - Cleanup Intel GPU
 */
void mvgal_intel_fini(struct mvgal_gpu_device *gpu)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv) {
		return;
	}

	kfree(priv);
	gpu->vendor_priv = NULL;

	pr_info("MVGAL: Intel GPU finalized\n");
}

/**
 * mvgal_intel_submit_cs - Submit command stream to Intel GPU
 */
int mvgal_intel_submit_cs(struct mvgal_gpu_device *gpu, struct mvgal_workload *workload)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv || !workload) {
		return -EINVAL;
	}

	/* In the full implementation:
	 * For i915: Use drm_i915_gem_execbuffer2()
	 * For xe: Use xe_exec_queue_submit()
	 * 
	 * Study references:
	 * - i915: drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c
	 * - xe: drivers/gpu/drm/xe/xe_exec.c
	 */

	pr_debug("MVGAL: Intel submit_cs (workload %d, size %zu)\n",
		workload->id, workload->command_buffer_size);

	return 0;
}

/**
 * mvgal_intel_alloc_vram - Allocate VRAM on Intel GPU
 */
int mvgal_intel_alloc_vram(struct mvgal_gpu_device *gpu, size_t size, uint64_t *gpu_addr)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv) {
		return -EINVAL;
	}

	/* Kernel-internal VRAM allocation requires vendor DRM GEM access.
	 * Userspace allocation goes through the vendor driver directly. */
	return -EOPNOTSUPP;
}

/**
 * mvgal_intel_free_vram - Free VRAM on Intel GPU
 */
void mvgal_intel_free_vram(struct mvgal_gpu_device *gpu, uint64_t gpu_addr)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv) {
		return;
	}

	/* In the full implementation:
	 * For i915: Use drm_gem_object_put() or i915_gem_object_put()
	 * For xe: Use xe_bo_put()
	 */

	pr_debug("MVGAL: Intel free_vram (addr %016llx)\n", gpu_addr);
}

/**
 * mvgal_intel_wait_idle - Wait for Intel GPU to be idle
 */
int mvgal_intel_wait_idle(struct mvgal_gpu_device *gpu, uint32_t timeout_ms)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv) {
		return -EINVAL;
	}

	/* In the full implementation:
	 * For i915: Use i915_gem_wait_idle()
	 * For xe: Use xe_exec_wait() or xe_device_wait_idle()
	 * 
	 * Study references:
	 * - i915: drivers/gpu/drm/i915/gem/i915_gem_wait.c
	 */

	pr_debug("MVGAL: Intel wait_idle (timeout %ums)\n", timeout_ms);

	return 0;
}

/**
 * mvgal_intel_set_power_state - Set Intel GPU power state
 */
int mvgal_intel_set_power_state(struct mvgal_gpu_device *gpu, enum mvgal_power_state state)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv) {
		return -EINVAL;
	}

	char sysfs_path[128];
	struct file *f;
	const char *level_str;
	loff_t pos = 0;
	ssize_t n;

	switch (state) {
	case MVGAL_POWER_STATE_ACTIVE:
	case MVGAL_POWER_STATE_SUSTAINED:
		level_str = "on";
		break;
	case MVGAL_POWER_STATE_IDLE:
	case MVGAL_POWER_STATE_PARK:
		level_str = "auto";
		break;
	case MVGAL_POWER_STATE_OFF:
		level_str = "auto";
		break;
	default:
		level_str = "auto";
		break;
	}

	/* Intel uses /sys/class/drm/cardN/device/power/control */
	snprintf(sysfs_path, sizeof(sysfs_path),
		 "/sys/bus/pci/devices/%04x:%02x:%02x.%u/power/control",
		 pci_domain_nr(priv->pdev->bus),
		 priv->pdev->bus->number,
		 PCI_SLOT(priv->pdev->devfn),
		 PCI_FUNC(priv->pdev->devfn));

	f = filp_open(sysfs_path, O_WRONLY, 0);
	if (IS_ERR(f)) {
		/* Try drm card path */
		snprintf(sysfs_path, sizeof(sysfs_path),
			 "/sys/class/drm/card%u/device/power/control",
			 gpu->gpu_index);
		f = filp_open(sysfs_path, O_WRONLY, 0);
		if (IS_ERR(f)) {
			pr_debug("MVGAL: Intel power state sysfs not available for GPU %u\n",
				 gpu->gpu_index);
			return 0;
		}
	}

	n = kernel_write(f, level_str, strlen(level_str), &pos);
	filp_close(f, NULL);

	if (n < 0) {
		pr_warn("MVGAL: Intel power state write failed for GPU %u: %zd\n",
			gpu->gpu_index, n);
		return (int)n;
	}

	pr_debug("MVGAL: Intel GPU %u power state set to '%s'\n", gpu->gpu_index, level_str);
	return 0;
}

/**
 * mvgal_intel_export_dmabuf - Export Intel memory as DMA-BUF
 * 
 * Uses drm_gem_prime_export() for i915 or xe_bo_dmabuf_export() for xe.
 * This is the kernel-side integration point for cross-vendor sharing.
 */
struct dma_buf *mvgal_intel_export_dmabuf(struct mvgal_gpu_device *gpu, uint64_t gpu_addr, size_t size)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv) {
		return ERR_PTR(-EINVAL);
	}

	if (!priv->can_export_dmabuf) {
		return ERR_PTR(-ENOTSUPP);
	}

	/* In the full implementation:
	 * For i915: Use drm_gem_prime_export() on the GEM object
	 * For xe: Use xe_bo_dmabuf_export()
	 * 
	 * Study references:
	 * - i915: drivers/gpu/drm/i915/gem/i915_gem_dmabuf.c
	 * - xe: drivers/gpu/drm/xe/xe_dmabuf.c
	 */

	pr_debug("MVGAL: Intel export_dmabuf (addr %016llx, size %zu)\n", gpu_addr, size);

	/* Return a placeholder error - actual implementation requires
	 * access to the GEM object which is managed by the vendor driver */
	return ERR_PTR(-ENOSYS);
}

/**
 * mvgal_intel_import_dmabuf - Import DMA-BUF into Intel GPU
 */
int mvgal_intel_import_dmabuf(struct mvgal_gpu_device *gpu, struct dma_buf *buf, uint64_t *gpu_addr)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv || !buf) {
		return -EINVAL;
	}

	if (!priv->can_import_dmabuf) {
		return -ENOTSUPP;
	}

	/* Import requires DRM GEM access — not possible from this module context. */
	return -EOPNOTSUPP;
}

/**
 * mvgal_intel_query_utilization - Query Intel GPU utilization
 */
int mvgal_intel_query_utilization(struct mvgal_gpu_device *gpu, uint32_t *util_percent)
{
	struct mvgal_intel_priv *priv = gpu->vendor_priv;

	if (!priv || !util_percent) {
		return -EINVAL;
	}

	char sysfs_path[128];
	struct file *f;
	char buf[16];
	loff_t pos = 0;
	ssize_t n;
	unsigned long val;
	int ret;

	/* Try PCI BDF path first (works for both i915 and xe on modern kernels) */
	snprintf(sysfs_path, sizeof(sysfs_path),
		 "/sys/bus/pci/devices/%04x:%02x:%02x.%u/gt/gt0/busy_percent",
		 pci_domain_nr(priv->pdev->bus),
		 priv->pdev->bus->number,
		 PCI_SLOT(priv->pdev->devfn),
		 PCI_FUNC(priv->pdev->devfn));

	f = filp_open(sysfs_path, O_RDONLY, 0);
	if (IS_ERR(f)) {
		/* Fallback: try DRM class path (i915 traditional location) */
		snprintf(sysfs_path, sizeof(sysfs_path),
			 "/sys/class/drm/card%u/gt/gt0/busy_percent",
			 gpu->gpu_index);
		f = filp_open(sysfs_path, O_RDONLY, 0);
		if (IS_ERR(f)) {
			/* sysfs not available — return 0 rather than a fake value */
			*util_percent = 0;
			return 0;
		}
	}

	memset(buf, 0, sizeof(buf));
	n = kernel_read(f, buf, sizeof(buf) - 1, &pos);
	filp_close(f, NULL);

	if (n <= 0) {
		*util_percent = 0;
		return 0;
	}

	ret = kstrtoul(buf, 10, &val);
	if (ret != 0) {
		*util_percent = 0;
		return 0;
	}

	*util_percent = (uint32_t)min(val, 100UL);
	return 0;
}
