/* dracal-usb-cal: A command-line tool for Dracal USB sensors.
 *
 * Copyright (C) 2020-2024  Dracal Technologies Inc.
 *
 * 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 3
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <getopt.h>

#include "usbtenki.h"
#include "usbtenki_provider.h"
#include "source.h"
#include "usbtenki_cmds.h"
#include "usbtenki_version.h"
#include "chip.h"
#include "unit.h"
#include "calpoint.h"

#define MAX_EXTRA_ARGS 8
#define MAX_CHANNELS 32

#ifndef ARRAY_SIZE
    #define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
#endif

static const char INDENT[] = "    ";
static const char NO_DEVICE_FOUND[] = "No device found.\n";
static const char UNSUPPORTED_DEVICE[] = "This device does not support user calibration.\n";
static const char UNSUPPORTED_CHANNEL[] = "This channel does not support user calibration.\n";

int g_deep_trace = 0;
char g_verbose = 0;
char g_7bit_clean = 0;
char g_pretty = 0;

unit_t g_unit_prefs[UNIT_CATEGORIES] = {
	[UNIT_CATEGORY_UNKNOWN] =  UNIT_SENSOR_DEFAULT,
	[UNIT_CATEGORY_TEMPERATURE] = UNIT_CELSIUS,
	[UNIT_CATEGORY_PRESSURE] = UNIT_KPA,
	[UNIT_CATEGORY_FREQUENCY] = UNIT_HZ,
	[UNIT_CATEGORY_VOLTAGE] =  UNIT_VOLT,
	[UNIT_CATEGORY_CURRENT] = UNIT_AMP,
	[UNIT_CATEGORY_POWER] = UNIT_WATT,
	[UNIT_CATEGORY_LENGTH] = UNIT_METER,
	[UNIT_CATEGORY_CONCENTRATION] = UNIT_SENSOR_DEFAULT,
	[UNIT_CATEGORY_RELATIVE_HUMIDITY] = UNIT_RH,
};

static const char COPYRIGHT[] =
	"\n"
	"Copyright (C) 2020-2025  Dracal Technologies Inc.\n"
	"\n"
	"This software is free software: you can redistribute it and/or modify it under the\n"
	"terms of the GNU General Public License as published by the Free Software Foundation,\n"
	"either version 3 of the License, or (at your option) any later version.\n"
	"\n"
	"This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n"
	"without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
	"See the GNU General Public License for more details.\n"
	"\n"
	"This software uses libusb, which can be found at: https://libusb.info\n"
	"libusb is Copyright (C) 2001 Johannes Erdfelt <johannes@erdfelt.com>, and other contributors.\n"
	"libusb is licensed under the GNU Lesser General Public License (LGPL), version 2.1.\n"
	"You can find a copy of this license at: https://www.gnu.org/licenses/lgpl-2.1.en.html\n"
	"\n"
;

static void printVersion(void)
{
	printf("\ndracal-usb-cal version %s\n", USBTENKI_VERSION);
	fputs(COPYRIGHT, stdout);
}

static void printUsage(void)
{
	printf("Usage: ./dracal-usb-cal [options] command [command arguments ...]\n");
	printf("\nValid options:\n");
	printf("    -V          Display version information\n");
	printf("    -v          Run in verbose mode\n");
	printf("    -h          Displays help\n");
	printf("    -s serial   Operate on the device matching the given serial number.\n");
	printf("    -f          Operate on the first device found.\n");
	printf("    -l          List calibration points for selected device, or all devices if none is selected.\n");
	printf("    -T unit     Select the temperature unit to use. Default: Celsius\n");
	printf("    -P unit     Select the pressure unit to use. Default: kPa\n");
	printf("    -F unit     Select the frequency unit to use. Default: Hz\n");
	printf("    -M unit     Select the length unit to use. Default: m\n");
	printf("    -C unit     Select the concentration unit to use. Default: Sensor default\n");
	printf("    -p          Enable pretty output\n");
	printf("    -7          Use 7-bit ASCII output (no Unicode degree symbols)\n");

	printf("\nTo view the list devices, channels, and points, use the '-l' option.\n");

	printf("\nTo operate on a device, select it with '-s serial' or '-f', and provide a command\n");
	printf("along with its arguments, if applicable. Each command operates on a single device.\n");

	printf("\nCommands and arguments:\n");
	printf("    set chid pid sval tval      Set a calibration point on a device.\n");
	printf("        chid    The channel ID\n");
	printf("        pid     The point ID\n");
	printf("        sval    The non-compensated source value returned by the device\n");
	printf("        tval    The compensated target value\n");
	printf("    clear chid [pid]            Clear calibration points of a single channel.\n");
	printf("        chid    The channel ID\n");
	printf("        pid     The point ID (optional; if absent, entire channel is cleared)\n");
	printf("    clearall                    Clear calibration points of all channels.\n");
	printf("        (no arguments)\n");

	printf("\nValid temperature units:\n");
	printf("    Celsius, c, Fahrenheit, f, Kelvin, k\n");
	printf("\nValid pressure units:\n");
	printf("    kPa, hPa, Pa, bar, at (98.0665 kPa), atm (101.325 kPa), Torr, psi, inHg\n");
	printf("\nValid frequency units:\n");
	printf("    mHz, Hz, kHz, MHz, rpm\n");
	printf("\nValid length units:\n");
	printf("    mm, cm, dm, m, mil, in, ft, yd\n");
	printf("\nValid concentration units:\n");
	printf("    ppb, ppm, percent\n");
}


static void print_point(int channel_id, int point_id, Point *pt, int unit)
{
	if (g_pretty) {
		printf("%s%sPoint %d: ", INDENT, INDENT, point_id);
	}
	else {
		printf("%d, %d, ", channel_id, point_id);
	}

	if (POINT_IS_VALID(*pt)) {
		printf("%g, ", pt->x);
		printf("%g, ", pt->y);
		printf(" [%s]\n", unit_to_string(unit, g_7bit_clean));

	}
	else {
		printf("** none **\n");
	}
}

static int print_channel(USBTenki_dev_handle hdl, struct USBTenki_info *info, struct USBTenki_channel *channels, int channel_id)
{
	Point points[CAL_POINTS_COUNT];

	int num_points = usbtenki_getUserCalibrationPoints(hdl, channel_id, points);

	if (num_points == 0) {
		if (!g_pretty) {
			printf("%s, ", info->str_serial);
		}
		printf("%s%s", (g_pretty ? INDENT : ""), UNSUPPORTED_CHANNEL);
		return 0;
	}

	if (g_pretty) {
		printf("%sChannel %d: %s [%s]\n",
				INDENT,
				channel_id,
				chip_description(channels[channel_id].chip_id),
				chip_description_short(channels[channel_id].chip_id));
	}

	// Determine the native unit & preferred unit for this channel
	unit_t chip_unit = chip_native_unit(channels[channel_id].chip_id);
	unit_category_t cat = unit_category(chip_unit);
	unit_t pref_unit = g_unit_prefs[cat];
	if (pref_unit == UNIT_SENSOR_DEFAULT) {
		pref_unit = chip_unit;
	}

	// Convert calibration points to the preferred unit
	for (int i = 0; i < CAL_POINTS_COUNT; i++) {
		unit_convert(&(points[i].x), chip_unit, pref_unit);
		unit_convert(&(points[i].y), chip_unit, pref_unit);
	}

	for (int point_id = 0; point_id < num_points; point_id++) {
		if (!g_pretty) {
			printf("%s, ", info->str_serial);
		}
		print_point(channel_id, point_id, &points[point_id], pref_unit);
	}

	return 0;
}

static void print_device_info(struct USBTenki_info *info, int num_channels)
{

	printf("Device: '%s', Serial: '%s', Version %d.%d, Channels: %d\n",
			info->str_prodname,
			info->str_serial,
			info->major,
			info->minor,
			num_channels);

}

static int cmd_show(int argc, char **argv, struct USBTenki_info *info, USBTenki_dev_handle hdl)
{
	struct USBTenki_channel channels[MAX_CHANNELS];
	int num_channels = usbtenki_listChannels(hdl, channels, sizeof(channels));

	if (g_pretty) {
		print_device_info(info, num_channels);
	}

	for (int channel_id = 0; channel_id < num_channels; channel_id++) {
		if (print_channel(hdl, info, channels, channel_id)) {
			return 1;
		}
	}

	return 0;
}

static int cmd_set(int argc, char **argv, struct USBTenki_info *info, USBTenki_dev_handle hdl)
{
	int num_channels, channel_id, point_id;
	Point calpoint;
	Point points[CAL_POINTS_COUNT];
	char *e;
	int ret;
	struct USBTenki_channel channels[MAX_CHANNELS];

	// set_calpoint channel point_id sensed_value real_value
	if (argc != 5) {
		fprintf(stderr, "Unexpected number of arguments\n");
		return -1;
	}

	channel_id = strtol(argv[1], &e, 0);
	if (e == argv[1] || channel_id < 0 || channel_id > MAX_CHANNELS) {
		fprintf(stderr, "Invalid channel number\n");
		return -1;
	}

	point_id = strtol(argv[2], &e, 0);
	// if (e == argv[2] || point_id < 0 || point_id >= CAL_POINTS_COUNT) {
	if (e == argv[2]) {
		fprintf(stderr, "Invalid point ID\n");
		return -1;
	}

	calpoint.x = strtod(argv[3], &e);
	if (e == argv[3]) {
		fprintf(stderr, "Invalid source value\n");
		return -1;
	}

	calpoint.y = strtod(argv[4], &e);
	if (e == argv[4]) {
		fprintf(stderr, "Invalid target value\n");
		return -1;
	}

	num_channels = usbtenki_listChannels(hdl, channels, sizeof(channels));
	if (channel_id >= num_channels) {
		fprintf(stderr, "Channel %d does not exist\n", channel_id);
		return -1;
	}

	int num_points = usbtenki_getUserCalibrationPoints(hdl, channel_id, points);

	if (!num_points) {
		fprintf(stderr, UNSUPPORTED_CHANNEL);
		return -1;
	}

	// Determine the native unit & preferred unit for this channel
	unit_t chip_unit = chip_native_unit(channels[channel_id].chip_id);
	unit_category_t cat = unit_category(chip_unit);
	unit_t pref_unit = g_unit_prefs[cat];
	if (pref_unit == UNIT_SENSOR_DEFAULT) {
		pref_unit = chip_unit;
	}

	// Convert calibration points to the chip unit
	unit_convert(&(calpoint.x), pref_unit, chip_unit);
	unit_convert(&(calpoint.y), pref_unit, chip_unit);

	// Store new calibration point
	ret = usbtenki_setUserCalibrationPoint(hdl, channel_id, point_id, &calpoint);

	if (ret) {
		usbtenki_perror("Cannot set calibration point");
	}
	else {
		print_device_info(info, num_channels);
		print_channel(hdl, info, channels, channel_id);
		printf("Successfully set calibration point %d of channel %d\n", point_id, channel_id);
	}

	return ret;
}

static int cmd_clear(int argc, char **argv, struct USBTenki_info *info, USBTenki_dev_handle hdl)
{
	int channel_id;
	int point_id = -1;
	int ret = 0;
	char *e;

	int num_channels;
	int num_points;
	struct USBTenki_channel channels[MAX_CHANNELS];
	Point points[CAL_POINTS_COUNT];
	Point calpoint = {
		.x = NAN, .y = NAN
	};

	// clear channel [point_id]
	if (argc != 2 && argc != 3) {
		fprintf(stderr, "Unexpected number of arguments\n");
		return -1;
	}

	channel_id = strtol(argv[1], &e, 0);
	if (e == argv[1]) {
		fprintf(stderr, "Invalid channel number\n");
		return -1;
	}

	if (argc >= 3) {
		point_id = strtol(argv[2], &e, 0);
		if (e == argv[2] || point_id < 0) {
			fprintf(stderr, "Invalid point ID\n");
			return -1;
		}
	}

	num_channels = usbtenki_listChannels(hdl, channels, sizeof(channels));
	if (channel_id >= num_channels) {
		fprintf(stderr, "Channel %d does not exist\n", channel_id);
		return -1;
	}

	num_points = usbtenki_getUserCalibrationPoints(hdl, channel_id, points);
	if (!num_points) {
		fprintf(stderr, UNSUPPORTED_CHANNEL);
		return -1;
	}

	if (point_id >= 0) {
		if (point_id < num_points) {
			ret = usbtenki_setUserCalibrationPoint(hdl, channel_id, point_id, &calpoint);
		}
		else {
			fprintf(stderr, "Invalid point ID\n");
			return -1;
		}
	}
	else {
		// Clear all points on this channel
		for (int i = 0; i < num_points; i++) {
			ret = usbtenki_setUserCalibrationPoint(hdl, channel_id, i, &calpoint);
			if (ret) {
				break;
			}
		}
	}

	if (ret) {
		usbtenki_perror("Cannot clear calibration point");
	}
	else {
		print_device_info(info, num_channels);
		print_channel(hdl, info, channels, channel_id);
		if (point_id >= 0) {
			printf("Successfully cleared calibration point %d of channel %d\n", point_id, channel_id);
		}
		else {
			printf("Successfully cleared calibration points of channel %d\n", channel_id);
		}
	}

	return ret;
}

static int cmd_clearall(int argc, char **argv, struct USBTenki_info *info, USBTenki_dev_handle hdl)
{
	int num_channels, chn, num_points;
	int ret = 0;
	Point points[CAL_POINTS_COUNT];
	Point calpoint = {
		.x = NAN, .y = NAN
	};

	uint16_t cal_channels = 0;
	int success;
	num_channels = 	usbtenki_getNumChannels(hdl);
	success = usbtenki_getUserCalibrationChannels(hdl, &cal_channels);
	if (success && !cal_channels) {
		fprintf(stderr, UNSUPPORTED_DEVICE);
		return -1;
	} 
	else if (!success) {
		cal_channels = 0xFFFF;
	}
	for (chn = 0; chn < num_channels; chn++) {
		if (!(cal_channels & (1 << chn))) {
			continue;
		}
		num_points = usbtenki_getUserCalibrationPoints(hdl, chn, points);

		if (!num_points) {
			fprintf(stderr, UNSUPPORTED_DEVICE);
			return -1;
		}
		for (int i = 0; i < num_points; i++) {
			ret = usbtenki_setUserCalibrationPoint(hdl, chn, i, &calpoint);
			if (ret) {
				break;
			}
		}
	}

	if (ret) {
		usbtenki_perror("Cannot clear calibration points");
		return ret;
	}
	else {
		print_device_info(info, num_channels);
		printf("Successfully cleared all calibration points\n");
	}
	return 0;

}

static int list_all() {

	usbtenki_provider_init();

	struct USBTenki_list_ctx *ctx = usbtenki_provider_get_context();
	int device_found = 0;

	for (int i = 0; i < ctx->n_devices; i++) {
		struct USBTenki_info *info = &ctx->devices[i];
		USBTenki_dev_handle hdl = usbtenki_provider_find_handle(info->str_serial);
		if (!hdl) {
			continue;
		}
		device_found |= 1;
		cmd_show(0, NULL, info, hdl);
		usbtenki_closeDevice(hdl);
	}

	usbtenki_provider_shutdown();

	if (!device_found) {
		printf(NO_DEVICE_FOUND);
		return 1;
	}

	return 0;
}

typedef int (*command_handler_t)(int argc, char **argv, struct USBTenki_info *info, USBTenki_dev_handle hdl);

static struct {
		const char *command;
		command_handler_t handler;
	} handlers[] = {
		{ "set", cmd_set },
		{ "clear", cmd_clear },
		{ "clearall", cmd_clearall },
		{ NULL, NULL }, // terminator
};

int main(int argc, char **argv)
{
	int res, i;
	int use_first = 0;
	int list = 0;
	char *use_serial = NULL;
	int n_extra_args=0;
	char *eargv[MAX_EXTRA_ARGS];
	int retval = 0;


	// Set this so processing stops as soon as a nonoption argument is encountered. Otherwise
	// negative values are treated as arguments...
#ifdef _WIN32
	_putenv("POSIXLY_CORRECT=1");
#else
	setenv("POSIXLY_CORRECT", "1", 0);
#endif

	while (-1 != (res=getopt(argc, argv, "Vvhlfs:p7T:P:F:M:C:")))
	{
		switch (res)
		{
			case 'l':
				list = 1;
				break;

			case 'f':
				use_first = 1;
				break;

			case 'v':
				g_verbose = 1;
				break;

			case 'V':
				printVersion();
				return 0;

			case 'h':
				printUsage();
				return 0;

			case 's':
				use_serial = optarg;
				break;

			case 'T':
				if (strcasecmp(optarg, "Celsius")==0 ||
						strcasecmp(optarg, "Celcius")==0 ||  // backward-compatibility with old typo
						strcasecmp(optarg, "C")==0)
					g_unit_prefs[UNIT_CATEGORY_TEMPERATURE] = UNIT_CELSIUS;
				else if (strcasecmp(optarg, "Fahrenheit")==0 ||
						strcasecmp(optarg, "F")==0)
					g_unit_prefs[UNIT_CATEGORY_TEMPERATURE] = UNIT_FAHRENHEIT;
				else if (strcasecmp(optarg, "Kelvin")==0 ||
						strcasecmp(optarg, "K")==0)
					g_unit_prefs[UNIT_CATEGORY_TEMPERATURE] = UNIT_KELVIN;
				else {
					fprintf(stderr, "Unknown temperature format: '%s'\n",
										optarg);
					return -1;
				}
				break;

			case 'F':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "mHz", UNIT_MILLIHZ },
						{ "Hz", UNIT_HZ },
						{ "kHz", UNIT_KHZ },
						{ "MHz", UNIT_MHZ },
						{ "rpm", UNIT_RPM },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_FREQUENCY] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown frequency unit: '%s'\n", optarg);
					}
				}
				break;

			case 'P':
				{
					printf("Parsing P option\n");
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "pa", UNIT_PA },
						{ "kpa", UNIT_KPA },
						{ "hpa", UNIT_HPA },
						{ "bar", UNIT_BAR },
						{ "at", UNIT_AT },
						{ "atm", UNIT_ATM },
						{ "torr", UNIT_TORR },
						{ "psi", UNIT_PSI },
						{ "inhg", UNIT_INHG },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_PRESSURE] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown pressure format: '%s'\n", optarg);
					}
				}
				break;

			case 'M':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "m", UNIT_METER },
						{ "dm", UNIT_DECIMETER },
						{ "cm", UNIT_CENTIMETER },
						{ "mm", UNIT_MILLIMETER },
						{ "mil", UNIT_MIL },
						{ "mils", UNIT_MIL },
						{ "in", UNIT_INCH },
						{ "ft", UNIT_FEET },
						{ "yd", UNIT_YARD },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_LENGTH] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown length unit: '%s'\n", optarg);
					}
				}
				break;

			case 'C':
				{
					struct {
						const char *name;
						int fmt;
					} tbl[] = {
						{ "ppb", UNIT_PPB },
						{ "ppm", UNIT_PPM },
						{ "percent", UNIT_PERCENT },
						{ "sensor", UNIT_SENSOR_DEFAULT },
					};

					for (i=0; i<ARRAY_SIZE(tbl); i++) {
						if (strcasecmp(tbl[i].name, optarg)==0) {
							g_unit_prefs[UNIT_CATEGORY_CONCENTRATION] = tbl[i].fmt;
							break;
						}
					}
					if (i==ARRAY_SIZE(tbl)) {
						fprintf(stderr,
							"Unknown concentration unit: '%s'\n", optarg);
					}
				}
				break;

			case 'p':
				g_pretty = 1;
				break;

			case '7':
				g_7bit_clean = 1;
				break;

		}
	}

	n_extra_args = argc - optind;

	if (g_verbose) {
		printf("Extra args: %d\n", n_extra_args);
	}

	for (i=optind; i<argc; i++) {
		eargv[i-optind] = argv[i];
		if (g_verbose)
			printf("  %d: %s\n", i-optind, eargv[i-optind]);
	}

	// Validate actions (-l or command) and execute list_all() if nothing is specified

	char device_specified = (use_serial || use_first);

	if (list && n_extra_args) {
		// Both -l and command are specified; error
		fprintf(stderr, "Must specify either -l or a command, not both.\n");
		return 1;
	}

	if (list) {
		// Only -l is specified
		if (!device_specified) {
			list_all();
			return 0;
		}
	}
	else if (n_extra_args) {
		// Only a command is specified
		if (!device_specified) {
			fprintf(stderr, "Serial number is required.\n");
			return 1;
		}
	}
	else {
		// No action is specified
		if (device_specified) {
			fprintf(stderr, "Missing command or -l option.\n");
			return 1;
		}
		else {
			// no action and no device: just list all
			list_all();
			return 0;
		}
	}

	// Execute action

	usbtenki_provider_init();

	uint8_t source_count = usbtenki_provider_get_source_count();
	Source *found_source = NULL;

	for (int i = 0; i < source_count; i++) {

		Source *source = usbtenki_provider_get_source(i);
		Device *device = source->device;

		if (use_serial) {
			if (DEVICE_SERIAL_NUMBERS_EQUAL(use_serial, device->serial_number)) {
				found_source = source;
			}
			else {
				continue;
			}
		}
		else {
			found_source = source; // last found will be used
		}
	}
	
	if (!found_source) {
		if (use_serial) {
			fprintf(stderr, "Could not locate device with serial '%s'. Try dracal-usb-cal -l\n", use_serial);
		}
		else {
			fprintf(stderr, NO_DEVICE_FOUND);
		}
		usbtenki_provider_shutdown();
		return 1;
	}

	struct USBTenki_list_ctx *ctx = usbtenki_provider_get_context();
	struct USBTenki_info *info = NULL;
	USBTenki_dev_handle hdl = NULL;

	for (int i = 0; i < ctx->n_devices; i++) {
		if (strcmp(ctx->devices[i].str_serial, found_source->device->serial_number) == 0) {
			info = &ctx->devices[i];
			hdl = usbtenki_openDevice(info);
			break;
		}
	}

	if (!hdl) {
		fprintf(stderr, "Cannot open device\n");
		usbtenki_provider_shutdown();
		return 2;
	}

	if (list) {
		retval = cmd_show(0, NULL, info, hdl);
		usbtenki_closeDevice(hdl);
		usbtenki_provider_shutdown();
		return retval;
	}

	for (i=0; handlers[i].command; i++) {
		if (strcmp(eargv[0], handlers[i].command)==0) {
			retval = handlers[i].handler(n_extra_args, eargv, info, hdl);
			usbtenki_closeDevice(hdl);
			usbtenki_provider_shutdown();
			return retval;
		}
	}

	fprintf(stderr, "Unknown command '%s'\n", eargv[0]);

	usbtenki_provider_shutdown();
	return 1;

}
