#!/usr/bin/env python3

import argparse
import re
import sys

re_vendor = re.compile(r'^(?P<vendor_id>[0-9a-fA-F]{4})\s+(?P<vendor_name>.*)')
re_device = re.compile(r'^\t(?P<device_id>[0-9a-fA-F]{4})\s+(?P<device_name>.*)')
re_subsystem = re.compile(r'^\t\t[0-9a-fA-F]{4}\s+[0-9a-fA-F]{4}\s+.*')
re_class = re.compile(r'^C\s+[0-9a-fA-F]{2}\s+.*')
re_comment = re.compile(r'^(\s*#.*|\s*)$')

def x2int(x):
    if isinstance(x, str): return int(x, 16)
    return x

def parse_pci_file(ids_file, file_name):
    """
    @return dictionary in format:
        { vendor_id: ( vendor_name
                     , { device_id : device_name
                       , ... }
                     )
        , ...
        }
      where
        vendor_id and device_id are integers
    """
    res = {}
    vendor = None

    def _add_vendor(file_name, line, vendor_id, vendor_name):
        vendor = x2int(vendor_id)
        if vendor in res:
            sys.stderr.write('Warning: vendor "%04x" redeclared!'
                ' File: %s : %d\n'%(vendor, file_name, line))
            # Update vendor name but keep existing devices
            res[vendor] = (vendor_name, res[vendor][1])
        else:
            res[vendor] = (vendor_name, {})
        return vendor

    def _add_device(file_name, line, vendor, dev_id, dev_name):
        dev_id = x2int(dev_id)
        if dev_id in res[vendor][1]:
            sys.stderr.write('Warning: device "%04x:%04x" redeclared!'
                ' File: %s : %d\n'%(vendor, dev_id, file_name, line))
        res[vendor][1][dev_id] = dev_name

    for i, l in enumerate(ids_file.readlines(), 1):
        # Skip comments, blank lines, subsystems, and class definitions
        if re_comment.match(l) or re_subsystem.match(l) or re_class.match(l):
            continue

        m = re_vendor.match(l)
        if m:
            vendor = _add_vendor(file_name, i, m.group('vendor_id'), m.group('vendor_name'))
            continue

        m = re_device.match(l)
        if m:
            if vendor is None:
                sys.stderr.write("Warning: device without vendor at line %d\n" % i)
                continue
            _add_device(file_name, i,
                    vendor, m.group('device_id'), m.group('device_name'))
            continue

        # Ignore unrecognized lines (subclasses, prog-if, etc.)
        if not l.startswith('\t'):
            sys.stderr.write("Warning: unrecognized line %d: \"%s\"\n" % (i, l.rstrip()))

    return res

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Compare new pci.ids to old.")
    parser.add_argument('old', type=argparse.FileType('r'),
            help="old pci.ids file")
    parser.add_argument('new', type=argparse.FileType('r'),
            help="new pci.ids file")

    args = parser.parse_args()
    old = parse_pci_file(args.old, args.old.name)
    new = parse_pci_file(args.new, args.new.name)
    
    vendors_removed = 0
    vendors_added = 0
    vendors_renamed = 0
    devices_removed = 0
    devices_added = 0
    devices_renamed = 0
    for vendor, (vendor_name, devices) in old.items():
        if vendor not in new:
            vendors_removed += 1
            devices_removed += len(devices)
        else:
            if vendor_name != new[vendor][0]:
                vendors_renamed += 1
            for dev_id, dev_name in devices.items():
                if dev_id not in new[vendor][1]:
                    devices_removed += 1
                elif dev_name != new[vendor][1][dev_id]:
                        devices_renamed += 1

    for vendor, (vendor_name, devices) in new.items():
        if vendor not in old:
            vendors_added += 1
            devices_added += len(devices)
        else:
            for dev_id, dev_name in devices.items():
                if dev_id not in old[vendor][1]:
                    devices_added += 1

    print("old vendor count: %d" % len(old))
    print("new vendor count: %d" % len(new))
    print("vendors added:    %d" % vendors_added)
    print("vendors removed:  %d" % vendors_removed)
    print("vendors renamed:  %d" % vendors_renamed)
    print("devices added:    %d" % devices_added)
    print("devices removed:  %d" % devices_removed)
    print("devices renamed:  %d" % devices_renamed)

    sys.exit(0)

