package PVE::API2::Hardware::PCI;

use strict;
use warnings;

use PVE::JSONSchema qw(get_standard_option);

use PVE::RESTHandler;
use PVE::SysFSTools;

use base qw(PVE::RESTHandler);

my $default_class_blacklist = "05;06;0b";

__PACKAGE__->register_method ({
    name => 'pci_scan',
    path => '',
    method => 'GET',
    description => "List local PCI devices.",
    protected => 1,
    proxyto => "node",
    permissions => {
	check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1],
    },
    parameters => {
	additionalProperties => 0,
	properties => {
	    node => get_standard_option('pve-node'),
	    'pci-class-blacklist' => {
		type => 'string',
		format => 'string-list',
		default => $default_class_blacklist,
		optional => 1,
		description => "A list of blacklisted PCI classes, which will not be returned."
		    ." Following are filtered by default: Memory Controller (05), Bridge (06) and"
		    ." Processor (0b).",
	    },
	    verbose => {
		type => 'boolean',
		default => 1,
		optional => 1,
		description => "If disabled, does only print the PCI IDs. Otherwise, additional"
		    ." information like vendor and device will be returned.",
	    },
	},
    },
    returns => {
	links => [ { rel => 'child', href => "{id}" } ],
	type => 'array',
	items => {
	    type => "object",
	    properties => {
		id => {
		    type => 'string',
		    description => "The PCI ID.",
		},
		class => {
		    type => 'string',
		    description => 'The PCI Class of the device.',
		},
		vendor => {
		    type => 'string',
		    description => 'The Vendor ID.',
		},
		vendor_name => {
		    type => 'string',
		    optional => 1,
		},
		device => {
		    type => 'string',
		    description => 'The Device ID.',
		},
		device_name => {
		    type => 'string',
		    optional => 1,
		},
		subsystem_vendor => {
		    type => 'string',
		    description => 'The Subsystem Vendor ID.',
		    optional => 1,
		},
		subsystem_vendor_name => {
		    type => 'string',
		    optional => 1,
		},
		subsystem_device => {
		    type => 'string',
		    description => 'The Subsystem Device ID.',
		    optional => 1,
		},
		subsystem_device_name => {
		    type => 'string',
		    optional => 1,
		},
		iommugroup => {
		    type => 'integer',
		    description => "The IOMMU group in which the device is in. If no IOMMU group is"
			." detected, it is set to -1.",
		},
		mdev => {
		    type => 'boolean',
		    optional => 1,
		    description => "If set, marks that the device is capable of creating mediated"
			." devices.",
		},
	    },
	},
    },
    code => sub {
	my ($param) = @_;

	my $blacklist = $param->{'pci-class-blacklist'} // $default_class_blacklist;
	my $class_regex = join('|', PVE::Tools::split_list($blacklist));

	my $filter;

	if ($class_regex ne '') {
	    $filter =  sub {
		my ($pcidevice) = @_;

		if ($pcidevice->{class} =~ m/^0x(?:$class_regex)/) {
		    return 0;
		}

		return 1;
	    };
	}

	my $verbose = $param->{verbose} // 1;

	return PVE::SysFSTools::lspci($filter, $verbose);
    }});

__PACKAGE__->register_method ({
    name => 'pci_index',
    path => '{pci-id-or-mapping}',
    method => 'GET',
    description => "Index of available pci methods",
    permissions => {
	user => 'all',
    },
    parameters => {
	additionalProperties => 0,
	properties => {
	    node => get_standard_option('pve-node'),
	    'pci-id-or-mapping' => {
		type => 'string',
		pattern => '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)',
	    },
	},
    },
    returns => {
	type => 'array',
	items => {
	    type => "object",
	    properties => { method => { type => 'string'} },
	},
	links => [ { rel => 'child', href => "{method}" } ],
    },
    code => sub {
	my ($param) = @_;

	my $res = [
	    { method => 'mdev' },
	];

	return $res;
    }});

__PACKAGE__->register_method ({
    name => 'mdevscan',
    path => '{pci-id-or-mapping}/mdev',
    method => 'GET',
    description => "List mediated device types for given PCI device.",
    protected => 1,
    proxyto => "node",
    permissions => {
	check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1],
    },
    parameters => {
	additionalProperties => 0,
	properties => {
	    node => get_standard_option('pve-node'),
	    'pci-id-or-mapping' => {
		type => 'string',
		pattern => '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)',
		description => "The PCI ID or mapping to list the mdev types for."
	    },
	},
    },
    returns => {
	type => 'array',
	items => {
	    type => "object",
	    properties => {
		type => {
		    type => 'string',
		    description => "The name of the mdev type.",
		},
		available => {
		    type => 'integer',
		    description => "The number of still available instances of this type.",
		},
		description => {
		    type => 'string',
		    description => "Additional description of the type."
		},
		name => {
		    type => 'string',
		    optional => 1,
		    description => 'A human readable name for the type.',
		},
	    },
	},
    },
    code => sub {
	my ($param) = @_;

	if ($param->{'pci-id-or-mapping'} =~ m/^(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$/) {
	    return PVE::SysFSTools::get_mdev_types($param->{'pci-id-or-mapping'}); # PCI ID
	} else {
	    my $mapping = $param->{'pci-id-or-mapping'};

	    my $types = {};
	    my $devices = PVE::Mapping::PCI::find_on_current_node($mapping);
	    for my $device ($devices->@*) {
		my $id = $device->{path};
		next if $id =~ m/;/; # mdev not supported for multifunction devices

		my $device_types = PVE::SysFSTools::get_mdev_types($id);

		for my $type_definition ($device_types->@*) {
		    my $type = $type_definition->{type};
		    if (!defined($types->{$type})) {
			$types->{$type} = $type_definition;
		    }
		}
	    }

	    return [sort { $a->{type} cmp $b->{type} } values($types->%*)];
	}

    }});
