// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.
package com.cloud.hypervisor.kvm.resource;

import com.cloud.agent.api.VgpuTypesInfo;
import org.apache.cloudstack.gpu.GpuDevice;

public class LibvirtGpuDef {

    private VgpuTypesInfo vgpuType;

    public LibvirtGpuDef() {}

    public void defGpu(VgpuTypesInfo vgpuType) {
        this.vgpuType = vgpuType;
    }

    @Override
    public String toString() {
        StringBuilder gpuBuilder = new StringBuilder();
        GpuDevice.DeviceType deviceType = vgpuType.getDeviceType();

        if (deviceType == GpuDevice.DeviceType.MDEV) {
            // Generate XML for MDEV device (vGPU, including MIG instances)
            generateMdevXml(gpuBuilder);
        } else {
            // Generate XML for PCI device (passthrough GPU or VF)
            generatePciXml(gpuBuilder);
        }

        return gpuBuilder.toString();
    }

    private void generateMdevXml(StringBuilder gpuBuilder) {
        String mdevUuid = vgpuType.getBusAddress(); // For MDEV devices, busAddress contains the UUID
        String displayAttribute = vgpuType.isDisplay() ? "on' ramfb='on" : "off";

        gpuBuilder.append("<hostdev mode='subsystem' type='mdev' model='vfio-pci' display='").append(displayAttribute).append("'>\n");
        gpuBuilder.append("  <source>\n");
        gpuBuilder.append("    <address uuid='").append(mdevUuid).append("'/>\n");
        gpuBuilder.append("  </source>\n");
        gpuBuilder.append("</hostdev>\n");
    }

    private void generatePciXml(StringBuilder gpuBuilder) {
        String busAddress = vgpuType.getBusAddress();

        // For VDI use cases with display=on, ramfb provides early boot framebuffer
        // before GPU driver loads. This is critical for:
        // - Windows VDI guests (require framebuffer during boot)
        // - UEFI/OVMF firmware environments
        // - ARM64 hosts (cache coherency issues with traditional VGA)
        // - Multi-monitor VDI setups (primary display)
        String managed = "yes";
        // To support passthrough NVIDIA GPUs with SR-IOV & vendor specific GPU integration
        if (vgpuType.getVendorId().equals("10de") && !vgpuType.getModelName().equals("passthrough")) {
            managed = "no";
        }
        if (vgpuType.isDisplay()) {
            gpuBuilder.append("<hostdev mode='subsystem' type='pci' managed='").append(managed).append("' display='on' ramfb='on'>\n");
        } else {
            // Compute-only workloads don't need display or ramfb
            gpuBuilder.append("<hostdev mode='subsystem' type='pci' managed='").append(managed).append("' display='off'>\n");
        }
        gpuBuilder.append("  <driver name='vfio'/>\n");
        gpuBuilder.append("  <source>\n");

        // Parse the bus address into domain, bus, slot, function. Two input formats are accepted:
        //   - "dddd:bb:ss.f"  full PCI address with domain (e.g. 0000:00:02.0)
        //   - "bb:ss.f"       legacy short BDF; domain defaults to 0000
        // Each segment is parsed as a hex integer and formatted with fixed widths
        // (domain: 4 hex digits, bus/slot: 2 hex digits, function: 1 hex digit) to
        // produce canonical libvirt XML values regardless of input casing or padding.
        int domainVal = 0, busVal = 0, slotVal = 0, funcVal = 0;

        if (busAddress != null && !busAddress.isEmpty()) {
            String[] parts = busAddress.split(":");
            try {
                String slotFunction;
                if (parts.length == 3) {
                    domainVal = Integer.parseInt(parts[0], 16);
                    busVal = Integer.parseInt(parts[1], 16);
                    slotFunction = parts[2];
                } else if (parts.length == 2) {
                    busVal = Integer.parseInt(parts[0], 16);
                    slotFunction = parts[1];
                } else {
                    throw new IllegalArgumentException("Invalid PCI bus address format: '" + busAddress + "'");
                }
                String[] sf = slotFunction.split("\\.");
                if (sf.length == 2) {
                    slotVal = Integer.parseInt(sf[0], 16);
                    funcVal = Integer.parseInt(sf[1].trim(), 16);
                } else {
                    throw new IllegalArgumentException("Invalid PCI bus address format: '" + busAddress + "'");
                }
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("Invalid PCI bus address format: '" + busAddress + "'", e);
            }
        }

        String domain = String.format("0x%04x", domainVal);
        String bus = String.format("0x%02x", busVal);
        String slot = String.format("0x%02x", slotVal);
        String function = String.format("0x%x", funcVal);

        gpuBuilder.append("    <address domain='").append(domain).append("' bus='").append(bus).append("' slot='")
                .append(slot).append("' function='").append(function).append("'/>\n");
        gpuBuilder.append("  </source>\n");
        gpuBuilder.append("</hostdev>\n");
    }
}
