#!/usr/bin/perl -w
use strict;

my ($line,$urb, $last_time);

$last_time = 0;

sub process_urb {
    my $text = shift;
    if (!defined($text)) {
	return;
    }
    if ($text =~ m/coming back/) {
	return;
    }
#    print "text=\"$text\"\n";
    my ($time, $TransferBufferLength, $DescriptorType, $Index, $bConfigurationValue, $Value, $Request, $endpoint);
    if ($text =~ m/(\d+) ms/) {
	$time = $1;
    }

    if ($last_time == 0) {
	$last_time = $time;
    } else {
	my $timediff = $time - $last_time;
	if ($timediff > 2500) {
	    $timediff = 2500; # FIXME
	}
	print "usleep($timediff*1000);\n";
	$last_time = $time;
    }
    if ($text =~ m/TransferBufferLength = 0([^ ]+)/) {
	$TransferBufferLength = $1;
	chomp($TransferBufferLength);
    }
    if ($text =~ m/DescriptorType *= 0([^ ]+)/) {
	$DescriptorType = $1;
	chomp($DescriptorType);
    }
    if ($text =~ m/Index *= 0([a-f0-9]+)/) {
	$Index = $1;
	chomp($Index);
    }
    if ($text =~ m/bConfigurationValue *= 0x0([^ ]+)/) {
	$bConfigurationValue = $1;
	chomp($bConfigurationValue);
    }
    if ($text =~ m/Value *= 0([^ ]+)/) {
	$Value = $1;
	chomp($Value);
    }
    if ($text =~ m/Request *= 0([^ ]+)/) {
	$Request = $1;
	chomp($Request);
    }
    if ($text =~ m/endpoint 0x([^ \]]+)/) {
	$endpoint = $1;
	chomp($endpoint);
    }
    if ($text =~ m/URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE/) {
	print "ret = usb_get_descriptor(devh, 0x$DescriptorType, 0x$Index, buf, 0x$TransferBufferLength);\n";
	print "printf(\"get descriptor returned %d, bytes: \", ret);\n";
	print "print_bytes(buf, ret);\n";
	print "printf(\"\\n\");\n";
    } elsif ($text =~ m/URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE/) {
	printf "ret = usb_get_descriptor(devh, 0x$DescriptorType, 0x$Index, buf, 0x$TransferBufferLength);\n";
	print "printf(\"get descriptor returned %d, bytes: \", ret);\n";
	print "print_bytes(buf, ret);\n";
	print "printf(\"\\n\");\n";
    } elsif ($text =~ m/URB_FUNCTION_SELECT_CONFIGURATION/) {
	if (!defined($bConfigurationValue)) {
	 #   print "no bConfigurationValue! text = \"$text\"\n";
	} else {
	    print "ret = usb_set_configuration(devh, 0x$bConfigurationValue);\n";
	    print "printf(\"set configuration returned %d\\n\", ret);\n";
	}
    } elsif ($text =~ m/URB_FUNCTION_CLASS_INTERFACE/) {
	my $requesttype = "USB_TYPE_CLASS";
	if ($text =~ m/USBD_TRANSFER_DIRECTION_OUT/) {
	    $requesttype .= "+ USB_RECIP_INTERFACE";
	}
	if ($text =~ m/00000000: ([^ ].+)/) {
	    my $bytes = $1;
	    $bytes =~ s/ /\\x/g;
	    $bytes =~ s/^/\\x/;
	    print "memcpy(buf, \"$bytes\", 0x$TransferBufferLength);\n";
	}
	print "ret = usb_control_msg(devh, $requesttype, 0x$Request, 0x$Value, 0x$Index, buf, 0x$TransferBufferLength, 1000);\n";
	print "printf(\"control msg returned %d, bytes: \", ret);\n";
	print "print_bytes(buf, ret);\n";
	print "printf(\"\\n\");\n";
    } elsif ($text =~ m/URB_FUNCTION_VENDOR_INTERFACE/) {
	my $requesttype = "USB_TYPE_VENDOR";
	if ($text =~ m/USBD_TRANSFER_DIRECTION_OUT/) {
	    $requesttype .= "+ USB_RECIP_INTERFACE";
	}
	if ($text =~ m/00000000: ([^ ].+)/) {
	    my $bytes = $1;
	    $bytes =~ s/ /\\x/g;
	    $bytes =~ s/^/\\x/;
	    print "memcpy(buf, \"$bytes\", 0x$TransferBufferLength);\n";
	}
	print "ret = usb_control_msg(devh, $requesttype, 0x$Request, 0x$Value, 0x$Index, buf, 0x$TransferBufferLength, 1000);\n";
	print "printf(\"control msg returned %d, bytes: \", ret);\n";
	print "print_bytes(buf, ret);\n";
	print "printf(\"\\n\");\n";
    } elsif ($text =~ m/URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER/) {
	my $method;
	if ($text =~ /USBD_TRANSFER_DIRECTION_IN/) {
	    $method = "read";
	} else {
	    $method = "write";
	}
	# TODO: figure out 'interrupt' from lsusb output
	print "ret = usb_interrupt_$method(devh, 0x$endpoint, buf, 0x$TransferBufferLength, 1000);\n";
	print "printf(\"interrupt $method returned %d, bytes: \", ret);\n";
	if ($method eq "read") {
	    print "print_bytes(buf, ret);\n";
	}
	print "printf(\"\\n\");\n";
    } elsif ($text =~ m/URB_FUNCTION_ABORT_PIPE/) {
	# TODO: implement
    } elsif ($text =~ m/URB_FUNCTION_RESET_PIPE/) {
	# TODO: implement
    } else {
	die "unrecognized URB type, text = \"$text\"";
    }
}


print <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <ctype.h>
#include <usb.h>

struct usb_dev_handle *devh;

void release_usb_device(int dummy) {
    int ret;
    ret = usb_release_interface(devh, 0);
    if (!ret)
	printf("failed to release interface: %d\\n", ret);
    usb_close(devh);
    if (!ret)
	printf("failed to close interface: %d\\n", ret);
    exit(1);
}

void list_devices() {
    struct usb_bus *bus;
    for (bus = usb_get_busses(); bus; bus = bus->next) {
	struct usb_device *dev;
	
	for (dev = bus->devices; dev; dev = dev->next)
	    printf("0x%04x 0x%04x\\n",
		   dev->descriptor.idVendor,
		   dev->descriptor.idProduct);
    }
}    

struct usb_device *find_device(int vendor, int product) {
    struct usb_bus *bus;
    
    for (bus = usb_get_busses(); bus; bus = bus->next) {
	struct usb_device *dev;
	
	for (dev = bus->devices; dev; dev = dev->next) {
	    if (dev->descriptor.idVendor == vendor
		&& dev->descriptor.idProduct == product)
		return dev;
	}
    }
    return NULL;
}

void print_bytes(char *bytes, int len) {
    int i;
    if (len > 0) {
	for (i=0; i<len; i++) {
	    printf("%02x ", (int)((unsigned char)bytes[i]));
	}
	printf("\\"");
        for (i=0; i<len; i++) {
	    printf("%c", isprint(bytes[i]) ? bytes[i] : '.');
        }
        printf("\\"");
    }
}


int main(int argc, char **argv) {
    int ret, vendor, product;
    struct usb_device *dev;
    char buf[65535];

    usb_init();
    usb_find_busses();
    usb_find_devices();

    if (argc!=3) {
	printf("usage: %s vendorID productID\\n", argv[0]);
	printf("ID numbers of currently attached devices:\\n");
	list_devices();
	exit(1);
    }
    vendor = strtol(argv[1], NULL, 16);
    product = strtol(argv[2], NULL, 16);
    if (vendor <= 1 || product <= 1) {
	printf("invalid vendor or product id\\n");
	exit(1);
    }
    dev = find_device(vendor, product);
    assert(dev);

    devh = usb_open(dev);
    assert(devh);
    
    signal(SIGTERM, release_usb_device);

    ret = usb_claim_interface(devh, 0);
    if (ret != 0) {
	printf("claim failed with error %d\\n", ret);
		exit(1);
    }
    
    ret = usb_set_altinterface(devh, 0);
    assert(ret >= 0);

EOF
    ;
    
while (defined($line = <>)) {
    if ($line =~ m/ URB (\d+) (going down|coming back)/) {
        &process_urb($urb);
        $urb = $line;
    } elsif (defined($urb)) {
        $urb .= $line;
    }
}

&process_urb($urb);

print <<EOF
	ret = usb_release_interface(devh, 0);
	assert(ret == 0);
	ret = usb_close(devh);
	assert(ret == 0);
	return 0;
}
EOF
    ;
