#!/usr/bin/python3
#
# SPDX-License-Identifier: GPL-2.0-only
# (c) 2026 Gerd Hoffmann
#
""" build qemu command line """
import os
import tempfile
import subprocess

# pylint: disable=too-many-public-methods
# pylint: disable=too-many-instance-attributes
class QemuCfg():
    """ build qemu command line """

    # pylint: disable=too-many-arguments
    # pylint: disable=too-many-positional-arguments
    def __init__(self, logger = None, pcie = True, scsi = True, nvme = False, sata = False):

        # config
        self.logger = logger
        self.use_pcie = pcie
        self.use_scsi = scsi
        self.use_nvme = nvme
        self.use_sata = sata

        # state
        self.have_scsi = False
        self.have_xhci = False
        self.have_bot = False
        self.have_virtio_serial = False
        self.have_nvme = False

        # generate ids
        self.pcie = 0
        self.ide = 0
        self.bid = 0
        self.nid = 0
        self.cid = 0

        # device lists
        self.blkdev = []
        self.netdev = []
        self.chardev = []
        self.devices = []


    def cmdline(self):
        cmdline = []
        for blk in self.blkdev:
            cmdline.append('-blockdev')
            cmdline.append(blk)
        for net in self.netdev:
            cmdline.append('-netdev')
            cmdline.append(net)
        for char in self.chardev:
            cmdline.append('-chardev')
            cmdline.append(char)
        for dev in self.devices:
            cmdline.append('-device')
            cmdline.append(dev)
        return cmdline

    # --- devices ---

    def add_pcie_port(self):
        name = f'port{self.pcie}'
        slot = 8 + (self.pcie >> 3)
        func = self.pcie & 7
        device = f'pcie-root-port,id={name},bus=pcie.0,addr={slot:x}.{func:x},chassis={self.pcie}'
        if func == 0:
            device += ',multifunction=on'
        self.devices.append(device)
        self.pcie += 1
        return name

    def add_virtio_device(self, kind, args = None):
        device = f'virtio-{kind}-pci'
        if self.use_pcie:
            port = self.add_pcie_port()
            device += f',bus={port}'
        if args:
            device += f',{args}'
        self.devices.append(device)

    def add_virtio_net(self, netdev, bootindex = None):
        args = f'netdev={netdev}'
        if bootindex is not None:
            args += f',bootindex={bootindex}'
        self.add_virtio_device('net', args)

    def add_virtio_blk(self, blkdev, bootindex = None):
        args = f'drive={blkdev}'
        if bootindex is not None:
            args += f',bootindex={bootindex}'
        self.add_virtio_device('blk', args)

    def add_virtio_scsi(self):
        self.add_virtio_device('scsi', 'id=virtio')
        self.have_scsi = True

    def add_virtio_rng(self):
        self.add_virtio_device('rng')

    def add_virtio_serial(self):
        self.add_virtio_device('serial')
        self.have_virtio_serial = True

    def add_virtio_serial_port(self, chardev, name):
        if not self.have_virtio_serial:
            self.add_virtio_serial()
        device = f'virtserialport,chardev={chardev},name={name}'
        self.devices.append(device)

    def add_scsi_hd(self, blkdev, bootindex = None):
        if not self.have_scsi:
            self.add_virtio_scsi()
        device = f'scsi-hd,bus=virtio.0,drive={blkdev}'
        if bootindex is not None:
            device += f',bootindex={bootindex}'
        self.devices.append(device)

    def add_scsi_cd(self, blkdev, bootindex = None):
        if not self.have_scsi:
            self.add_virtio_scsi()
        device = f'scsi-cd,bus=virtio.0,drive={blkdev}'
        if bootindex is not None:
            device += f',bootindex={bootindex}'
        self.devices.append(device)

    def add_sata_hd(self, blkdev, bootindex = None):
        device = f'ide-hd,bus=ide.{self.ide},drive={blkdev}'
        self.ide += 1
        if bootindex is not None:
            device += f',bootindex={bootindex}'
        self.devices.append(device)

    def add_sata_cd(self, blkdev, bootindex = None):
        device = f'ide-cd,bus=ide.{self.ide},drive={blkdev}'
        self.ide += 1
        if bootindex is not None:
            device += f',bootindex={bootindex}'
        self.devices.append(device)

    def add_nvme_ctrl(self):
        device = 'nvme,serial=1234'
        if self.use_pcie:
            port = self.add_pcie_port()
            device += f',bus={port}'
        self.devices.append(device)
        self.have_nvme = True

    def add_nvme_ns(self, blkdev, bootindex = None):
        if not self.have_nvme:
            self.add_nvme_ctrl()
        device = f'nvme-ns,drive={blkdev}'
        if bootindex is not None:
            device += f',bootindex={bootindex}'
        self.devices.append(device)

    def add_usb_xhci(self):
        ports = 8
        device = f'qemu-xhci,p2={ports},p3={ports}'
        if self.use_pcie:
            port = self.add_pcie_port()
            device += f',bus={port}'
        self.devices.append(device)
        self.have_xhci = True

    def add_usb_cd(self, blkdev, bootindex = None):
        if not self.have_xhci:
            self.add_usb_xhci()
        if not self.have_bot:
            self.devices.append('usb-bot,id=usb.bot')
            self.have_bot = True
        device = f'scsi-cd,bus=usb.bot.0,drive={blkdev}'
        if bootindex is not None:
            device += f',bootindex={bootindex}'
        self.devices.append(device)

    def add_isa_debugcon(self, chardev, iobase = 0x402):
        device = f'isa-debugcon,chardev={chardev},iobase={iobase}'
        self.devices.append(device)

    # --- network ---

    def add_netdev_user(self, nid):
        netdev = f'user,id={nid}'
        self.netdev.append(netdev)

    # --- storage ---

    def add_blkdev_qcow2(self, bid, filename):
        if self.logger:
            self.logger.info('# blkdev/qcow2: id=%s file=%s', bid, filename)
        blkdev = f'node-name={bid}'
        blkdev += f',driver=qcow2,file.driver=file,file.filename={filename}'
        self.blkdev.append(blkdev)

    def add_blkdev_iso(self, bid, filename):
        if self.logger:
            self.logger.info('# blkdev/iso: id=%s file=%s', bid, filename)
        blkdev = f'node-name={bid}'
        blkdev += f',driver=file,filename={filename}'
        blkdev += ',read-only=on'
        self.blkdev.append(blkdev)

    # --- chardev ---

    def add_chardev_logfile(self, cid, filename):
        chardev = f'file,id={cid},path={filename}'
        self.chardev.append(chardev)

    # --- helpers ---

    # pylint: disable=consider-using-with
    def create_tempfile_qcow2(self, image):
        copy = tempfile.NamedTemporaryFile(suffix = '.qcow2')
        if self.logger:
            self.logger.info('# qcow2: copy=%s backing=%s', copy.name, image)
        qemuimg = [ 'qemu-img', 'create', '-q', '-f', 'qcow2', '-F', 'qcow2',
                    '-b', os.path.abspath(image), copy.name ]
        subprocess.run(qemuimg, check = True)
        return copy

    def create_empty_qcow2(self, filename, size = '8G'):
        if os.path.exists(filename):
            if self.logger:
                self.logger.info("# qcow2: skip=%s [file exists]", filename)
            return

        if self.logger:
            self.logger.info("# qcow2: create=%s size=%s", filename, size)
        qemuimg = [ 'qemu-img', 'create', '-q', '-f', 'qcow2', filename, size ]
        subprocess.run(qemuimg, check = True)

    def add_disk_qcow2(self, filename, bootindex = None):
        bid = f'blk{self.bid}'
        self.bid += 1
        self.add_blkdev_qcow2(bid, filename)
        if self.use_nvme:
            self.add_nvme_ns(blkdev = bid, bootindex = bootindex)
        elif self.use_scsi:
            self.add_scsi_hd(blkdev = bid, bootindex = bootindex)
        elif self.use_sata:
            self.add_sata_hd(blkdev = bid, bootindex = bootindex)
        else:
            self.add_virtio_blk(blkdev = bid, bootindex = bootindex)

    def add_cdrom_iso(self, filename, bootindex = None):
        bid = f'blk{self.bid}'
        self.bid += 1
        self.add_blkdev_iso(bid, filename)
        if self.use_scsi:
            self.add_scsi_cd(blkdev = bid, bootindex = bootindex)
        elif self.use_sata:
            self.add_sata_cd(blkdev = bid, bootindex = bootindex)
        else:
            self.add_usb_cd(blkdev = bid, bootindex = bootindex)

    def add_network(self, bootindex = None):
        nid = f'net{self.nid}'
        self.nid += 1
        self.add_netdev_user(nid = nid)
        self.add_virtio_net(netdev = nid, bootindex = bootindex)

    def add_virtio_serial_log(self, name, filename):
        cid = f'char{self.cid}'
        self.cid += 1
        self.add_chardev_logfile(cid, filename)
        self.add_virtio_serial_port(cid, name)

    def add_x64_firmware_log(self, filename):
        cid = f'char{self.cid}'
        self.cid += 1
        self.add_chardev_logfile(cid, filename)
        self.add_isa_debugcon(cid)
