#!/usr/bin/env bash

# an auxiliary script to produce a "stub" usbhid-ups subdriver from
# the output of
#
# drivers/usbhid-ups -s ups -DD -u root -x explore -x vendorid=XXXX -x productid=XXXX -x port=auto -d1 > debuginfo 2>&1
#
# Usage: cat debuginfo | gen-usbhid-subdriver.sh
#
# See also: docs/hid-subdrivers.txt

usage() {
    echo "Usage: $0 [options] [file] < debuginfo"
    echo "with data prepared by a driver walk:"
    echo "    drivers/usbhid-ups -s ups -DD -u root -x vendorid=XXXX -x productid=XXXX \\"
    echo "        -x port=auto -x explore -d1 > debuginfo 2>&1"
    echo "Options:"
    echo " -h, --help           -- show this message and quit"
    echo " -n name              -- driver name (use natural capitalization)"
    echo " -v XXXX              -- vendor id (learned from debuginfo by default)"
    echo " -p XXXX              -- product id (learned from debuginfo by default)"
    echo " -k                   -- keep temporary files (for debugging)"
    echo " file                 -- read from file instead of stdin"
}

# tools
[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; }
[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; }

DRIVER=""
VENDORID=""
PRODUCTID=""
KEEP=""

while [ $# -gt 0 ]; do
    if [ $# -gt 1 -a "$1" = "-n" ]; then
        DRIVER="$2"
        shift 2
    elif [ $# -gt 1 -a "$1" = "-v" ]; then
        VENDORID="$2"
        shift 2
    elif [ $# -gt 1 -a "$1" = "-p" ]; then
        PRODUCTID="$2"
        shift 2
    elif [ "$1" = "-k" ]; then
        KEEP=yes
        shift
    elif echo "$1" | ${GREP} -v '^-' >/dev/null ; then
	FILE="$1"
	shift
    elif [ "$1" = "--help" -o "$1" = "-h" ]; then
        usage
        exit 0
    else
        echo "Illegal option $1. Try --help for more info." >&2
        exit 1
    fi
done

# delete temporary files: this is called just before exiting.
cleanup () {
    rm -f "$DEBUG" "$UTABLE" "$USAGES" "$SUBST" "$SEDFILE" "$NEWUTABLE"
}
if [ -z "$KEEP" ]; then
    trap cleanup EXIT
fi

if (command -v mktemp) >/dev/null ; then true ; else
# Have a simple (unsafe, unfeatured) fallback implementation:
mktemp() {
    if [ x"$1" = x"-d" ] ; then
        shift
        mkdir -p "$1.$$" || return
    else
        cat /dev/null > "$1.$$" || return
    fi
    echo "$1.$$"
}
fi

NAME=gen-usbhid-subdriver
TMPDIR="${TEMPDIR:-/tmp}"
DEBUG="`mktemp \"$TMPDIR/$NAME-DEBUG.XXXXXX\"`"
UTABLE="`mktemp \"$TMPDIR/$NAME-UTABLE.XXXXXX\"`"
USAGES="`mktemp \"$TMPDIR/$NAME-USAGES.XXXXXX\"`"
SUBST="`mktemp \"$TMPDIR/$NAME-SUBST.XXXXXX\"`"
SEDFILE="`mktemp \"$TMPDIR/$NAME-SEDFILE.XXXXXX\"`"
NEWUTABLE="`mktemp \"$TMPDIR/$NAME-NEWUTABLE.XXXXXX\"`"

# save standard input to a file
if [ -z "$FILE" ]; then
    FILE="$DEBUG"
    cat > "$DEBUG"
fi

# prompt use for name of driver
while [ -z "$DRIVER" ]; do
    echo "
Please enter a name for this driver. Use only letters and numbers. Use
natural (upper- and lowercase) capitalization, e.g., 'Belkin', 'APC'."
    read -p "Name of subdriver: " DRIVER < /dev/tty
    if echo $DRIVER | ${EGREP} '[^a-zA-Z0-9]' >/dev/null ; then
	echo "Please use only letters and digits"
	DRIVER=""
    fi
done

# try to determine product and vendor id, if not specified by user
[ -n "$VENDORID" ] || VENDORID=`cat "$FILE" | sed -n 's/.*- VendorID: \([0-9a-fA-F]*\).*/\1/p' | tail -1`
[ -n "$PRODUCTID" ] || PRODUCTID=`cat "$FILE" | sed -n 's/.*- ProductID: \([0-9a-fA-F]*\).*/\1/p' | tail -1`

# prompt for productid, vendorid if necessary
if [ -z "$VENDORID" ]; then
    read -p "Vendor ID: " VENDORID < /dev/tty
fi
if [ -z "$PRODUCTID" ]; then
    read -p "Product ID: " PRODUCTID < /dev/tty
fi

# Platforms vary with tooling abilitites...
TOLOWER="cat"
for TR_VARIANT in "tr 'A-Z' 'a-z'" "tr '[:upper:]' '[:lower:]'" "tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'" ; do
    if [ x"`echo C | $TR_VARIANT`" = xc ] ; then
        TOLOWER="$TR_VARIANT"
        break
    fi
done

TOUPPER="cat"
for TR_VARIANT in "tr 'a-z' 'A-Z'" "tr '[:lower:]' '[:upper:]'" "tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" ; do
    if [ x"`echo c | $TR_VARIANT`" = xC ] ; then
        TOUPPER="$TR_VARIANT"
        break
    fi
done

LDRIVER="`echo $DRIVER | $TOLOWER`"
UDRIVER="`echo $DRIVER | $TOUPPER`"
CFILE="$LDRIVER-hid.c"
HFILE="$LDRIVER-hid.h"

# extract Usage Table
cat "$FILE" | sed -n 's/.*Path: \([^,][^,]*\), Type:.*/\1/p' > "$UTABLE"

# extract Usage codes
cat "$UTABLE" | tr '.' $'\n' | sort -u > "$USAGES"

# make up dummy names for unknown usages
count=0
cat "$USAGES" | ${EGREP} '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]' |\
while read U; do
    count=`expr $count + 1`
    echo "$U $UDRIVER$count"
done > "$SUBST"

# create an sed script for substitutions
cat "$SUBST" | sed 's/\(.*\) \(.*\)/s!\1!\2!g;/' > "$SEDFILE"

# create modified usage table
sed -f "$SEDFILE" < "$UTABLE" > "$NEWUTABLE"

# generate header file
echo "Creating $HFILE"
cat > "$HFILE" <<EOF
/* ${HFILE} - subdriver to monitor ${DRIVER} USB/HID devices with NUT
 *
 *  Copyright (C)
 *  2003 - 2009	Arnaud Quette <ArnaudQuette@Eaton.com>
 *  2005 - 2006	Peter Selinger <selinger@users.sourceforge.net>
 *  2008 - 2009	Arjen de Korte <adkorte-guest@alioth.debian.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef ${UDRIVER}_HID_H
#define ${UDRIVER}_HID_H

#include "usbhid-ups.h"

extern subdriver_t ${LDRIVER}_subdriver;

#endif /* ${UDRIVER}_HID_H */
EOF

# generate source file
echo "Creating $CFILE"
cat > "$CFILE" <<EOF
/* ${CFILE} - subdriver to monitor ${DRIVER} USB/HID devices with NUT
 *
 *  Copyright (C)
 *  2003 - 2012	Arnaud Quette <ArnaudQuette@Eaton.com>
 *  2005 - 2006	Peter Selinger <selinger@users.sourceforge.net>
 *  2008 - 2009	Arjen de Korte <adkorte-guest@alioth.debian.org>
 *  2013 Charles Lepple <clepple+nut@gmail.com>
 *
 *  TODO: Add year and name for new subdriver author (contributor)
 *  Mention in docs/acknowledgements.txt if this is a vendor contribution
 *
 *  Note: this subdriver was initially generated as a "stub" by the
 *  gen-usbhid-subdriver script. It must be customized.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "usbhid-ups.h"
#include "${HFILE}"
#include "main.h"	/* for getval() */
#include "usb-common.h"

#define ${UDRIVER}_HID_VERSION	"${DRIVER} HID 0.1"
/* FIXME: experimental flag to be put in upsdrv_info */

/* ${DRIVER} */
#define ${UDRIVER}_VENDORID	0x${VENDORID}

/* USB IDs device table */
static usb_device_id_t ${LDRIVER}_usb_device_table[] = {
	/* ${DRIVER} */
	{ USB_DEVICE(${UDRIVER}_VENDORID, 0x${PRODUCTID}), NULL },

	/* Terminating entry */
	{ 0, 0, NULL }
};


/* --------------------------------------------------------------- */
/*      Vendor-specific usage table */
/* --------------------------------------------------------------- */

/* ${UDRIVER} usage table */
static usage_lkp_t ${LDRIVER}_usage_lkp[] = {
EOF

cat "$SUBST" | sed 's/\(.*\) \(.*\)/\t{ "\2",\t0x\1 },/' >> "$CFILE"

cat >> "$CFILE" <<EOF
	{ NULL, 0 }
};

static usage_tables_t ${LDRIVER}_utab[] = {
	${LDRIVER}_usage_lkp,
	hid_usage_lkp,
	NULL,
};

/* --------------------------------------------------------------- */
/* HID2NUT lookup table                                            */
/* --------------------------------------------------------------- */

static hid_info_t ${LDRIVER}_hid2nut[] = {

/* Please revise values discovered by data walk for mappings to
 * docs/nut-names.txt and group the rest under the ifdef below:
 */
#if WITH_UNMAPPED_DATA_POINTS
EOF

cat "$NEWUTABLE" | sort -u | while read U; do
    UL="`echo $U | $TOLOWER`"
    cat >> "$CFILE" <<EOF
	{ "unmapped.${UL}", 0, 0, "${U}", NULL, "%.0f", 0, NULL },
EOF
done

cat >> "$CFILE" <<EOF
#endif	/* if WITH_UNMAPPED_DATA_POINTS */

	/* end of structure. */
	{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
};

static const char *${LDRIVER}_format_model(HIDDevice_t *hd) {
	return hd->Product;
}

static const char *${LDRIVER}_format_mfr(HIDDevice_t *hd) {
	return hd->Vendor ? hd->Vendor : "${DRIVER}";
}

static const char *${LDRIVER}_format_serial(HIDDevice_t *hd) {
	return hd->Serial;
}

/* this function allows the subdriver to "claim" a device: return 1 if
 * the device is supported by this subdriver, else 0. */
static int ${LDRIVER}_claim(HIDDevice_t *hd)
{
	int status = is_usb_device_supported(${LDRIVER}_usb_device_table, hd);

	switch (status)
	{
	case POSSIBLY_SUPPORTED:
		/* by default, reject, unless the productid option is given */
		if (getval("productid")) {
			return 1;
		}
		possibly_supported("${DRIVER}", hd);
		return 0;

	case SUPPORTED:
		return 1;

	case NOT_SUPPORTED:
	default:
		return 0;
	}
}

subdriver_t ${LDRIVER}_subdriver = {
	${UDRIVER}_HID_VERSION,
	${LDRIVER}_claim,
	${LDRIVER}_utab,
	${LDRIVER}_hid2nut,
	${LDRIVER}_format_model,
	${LDRIVER}_format_mfr,
	${LDRIVER}_format_serial,
	fix_report_desc,	/* may optionally be customized, see cps-hid.c for example */
};
EOF

cat <<EOF
Done.

If you are looking to extend an existing subdriver with data points
not yet handled, now is a good time to compare ${LDRIVER}_hid2nut[]
tables in existing sources vs. content generated from this device walk.
Using a GUI tool like Meld or WinMerge is recommended.

If you are crafting a new subdriver, do not forget to:
* add #include "${HFILE}" to drivers/usbhid-ups.c,
* add &${LDRIVER}_subdriver to drivers/usbhid-ups.c:subdriver_list,
* add ${LDRIVER}-hid.c to USBHID_UPS_SUBDRIVERS in drivers/Makefile.am
* add ${LDRIVER}-hid.h to dist_noinst_HEADERS in drivers/Makefile.am
* "autoreconf" from the top level directory

For new data points in ${LDRIVER}_hid2nut[] tables be sure to not
invent new names, but use standard ones from docs/nut-names.txt file.
If you need to standardize a name for some concept not addressed yet,
please do so via nut-upsdev mailing list discussion.
EOF
