#!/usr/bin/env perl

# Based on listArduinos.pl from https://github.com/todbot/usbSearch (License: MIT)
# Original (C) 2012, Tod E. Kurt, http://todbot.com/blog/
# This version by Michael Richters <gedankenexperimenter@gmail.com> and Jesse Vincent <jesse@keyboard.io>

use warnings;
use strict;

my $vid = shift @ARGV;
my $pid = shift @ARGV;

if (!defined $vid || !defined $pid) {
  die "$0 has two required parameters, VID and PID";
}

# ioreg might be more machine-readable than system_profiler, but I haven't been able to
# get it to produce useful output
my @output = qx(/usr/sbin/system_profiler SPUSBDataType 2> /dev/null);

my $serial   = "";
my $location = "";

my $output = join('', @output);

my @stanzas = split(/\n\n/, $output);
foreach my $stanza (@stanzas) {
    if ($stanza =~ /Product ID: ${pid}/ && $stanza =~ /Vendor ID: ${vid}/) {
        if ($stanza =~ /Serial Number: (.*?)$/m) {
            $serial = $1;
        }
        if ($stanza =~ /Location ID: (.*?)$/m) {
            $location = $1;
        }

        if ($serial) {
            try_for_raw_serialnum($serial);
        }
        if ($location) {
            try_for_location_id($location);
        }
        if ($serial) {
            try_for_sn_prefix($serial);
        }

    }
}

sub try_for_raw_serialnum {
    my $sn = shift;

    my $serial_port_name = "/dev/cu.usbmodem" . $sn;
    exit_with_port_if_exists($serial_port_name);

    # High Sierra sometimes has a mismatch between the serial number and the device
    # filename. I'm not sure why, but system_profiler has a serial number ending in "E",
    # whereas the device filename ends in "1". In fact, when I change HID.getShortName()
    # to return "kbio02", the final character is replaced with a "1". 
    
    if ($serial_port_name =~ /\d$/) {
    	chop $serial_port_name;
    	exit_with_port_if_exists($serial_port_name . "1");
    } else {
	# If the serial port name doesn't end with a digit, try -appending- rather than replacing 
	# the last character of the port name
    	exit_with_port_if_exists($serial_port_name . "1");
    
	# and if that didn't work, try replacing the last character with a "1" anyway.
    	# Jason Koh reports that he saw this behavior as required on Catalina in May 2020.
    
    	chop $serial_port_name;
    	exit_with_port_if_exists($serial_port_name . "1");
    }


}

sub try_for_location_id {
    my $location_id = shift;

    # macOS truncates the string of "0"s from the right of the location id.
    # Here, also, the final character is an appended "1", so if macOS ever stops doing that,
    # this will need an update, as well. 
    if ($location_id =~ /0x(\d+?)0*\b/) {
        my $loc = $1;
        exit_with_port_if_exists("/dev/cu.usbmodem" . $loc . "1");
    }
}

sub try_for_sn_prefix {
    my $sn = shift;
    # If macOS has appended 'E', take it off to maximise our chances of a match.
    $sn =~ s/E$//;

    # If none of the above tests succeeds, just list the directory and see if there are any
    # files that have the device shortname that we expect:
    foreach my $line (qx(ls /dev/cu.usbmodem*)) {
        if ($line =~ /${sn}/) {
            chomp $line;
            print $line;
            exit 0;
        }
    }
}

sub exit_with_port_if_exists {
    my $serial_port_name = shift;

    if (-e $serial_port_name) {
        print $serial_port_name;
        exit 0;
    }
}