/*	$NetBSD: data.c,v 1.6 2009/04/28 10:57:24 nonaka Exp $	*/

/*-
 * Copyright (c) 2002 TAKEMRUA Shin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/param.h>

#include "tpctl.h"

#ifndef lint
#include <sys/cdefs.h>
__RCSID("$NetBSD: data.c,v 1.6 2009/04/28 10:57:24 nonaka Exp $");
#endif /* not lint */

static void *
alloc(int size)
{
	void *res;

	if ((res = malloc(size)) == NULL) {
		perror(getprogname());
		exit(EXIT_FAILURE);
	}

	return (res);
}

/*
 * duplicate string
 * trailing white space will be removed.
 */
static char *
strdup_prune(char *s)
{
	char *res, *tail;

	tail = &s[strlen(s) - 1];
	while (s <= tail && strchr(" \t", *tail) != NULL)
		tail--;

	res = alloc(tail - s + 2);
	memcpy(res, s, tail - s + 1);
	res[tail - s + 1] = '\0';

	return (res);
}

int
init_data(struct tpctl_data *data)
{
	TAILQ_INIT(&data->list);

	return (0);
}

int
read_data(const char *filename, struct tpctl_data *data)
{
	int res, len, n, i, t;
	char buf[MAXDATALEN + 2], *p, *p2;
	FILE *fp;
	struct tpctl_data_elem *elem;

	data->lineno = 0;

	if ((fp = fopen(filename, "r")) == NULL)
		return (ERR_NOFILE);

	while (fgets(buf, sizeof(buf), fp) != NULL) {
		data->lineno++;
		buf[MAXDATALEN + 1] = '\0';
		len = strlen(buf);
		if (MAXDATALEN < len) {
			res = ERR_SYNTAX;
			goto exit_func;
		}

		/* prune trailing space and newline */;
		p = &buf[len - 1];
		while (buf <= p && strchr(" \t\n\r", *p) != NULL)
			*p-- = '\0';

		/* skip space */;
		p = buf; 
		while (*p != '\0' && strchr(" \t", *p) != NULL)
			p++;

		/* comment or empty line */
		if (*p == '#' || *p == '\0') {
			elem = alloc(sizeof(*elem));
			elem->type = TPCTL_COMMENT;
			elem->name = strdup_prune(buf);
			TAILQ_INSERT_TAIL(&data->list, elem, link);
			continue;
		}

		/* calibration parameter */
		elem = alloc(sizeof(*elem));
		elem->type = TPCTL_CALIBCOORDS;
		p2 = p;
		while (*p2 != ',' && *p2 != '\0')
			p2++;
		if (*p2 != ',') {
			/* missing ',' */
			res = ERR_SYNTAX;
			free(elem);
			goto exit_func;
		}
		*p2 = '\0';
		elem->name = strdup_prune(p);
		if (search_data(data, elem->name) != NULL) {
			free(elem);
			res = ERR_DUPNAME;
			goto exit_func;
		}
		TAILQ_INSERT_TAIL(&data->list, elem, link);
		p = p2 + 1;

		/*
		 * minX, maxX, minY, maxY
		 */
		for (i = 0; i < 4; i++) {
			t = strtol(p, &p2, 0);
			if (p == p2) {
				res = ERR_SYNTAX;
				goto exit_func;
			}
			p = p2;
			while (*p != '\0' && strchr(" \t", *p) != NULL)
				p++;
			if (*p != ',') {
				res = ERR_SYNTAX;
				goto exit_func;
			}
			p++;
			switch (i % 4) {
			case 0:
				elem->calibcoords.minx = t;
				break;
			case 1:
				elem->calibcoords.miny = t;
				break;
			case 2:
				elem->calibcoords.maxx = t;
				break;
			case 3:
				elem->calibcoords.maxy = t;
				break;
			}
		}

		/*
		 * number of samples
		 */
		n = strtol(p, &p2, 0);
		if (p == p2) {
			res = ERR_SYNTAX;
			goto exit_func;
		}
		p = p2;
		while (*p != '\0' && strchr(" \t", *p) != NULL)
			p++;

		if (WSMOUSE_CALIBCOORDS_MAX < n) {
			res = ERR_SYNTAX;
			goto exit_func;
		}
		elem->calibcoords.samplelen = n;

		/*
		 * samples
		 */
		for (i = 0; i < n * 4; i++) {
			if (*p != ',') {
				res = ERR_SYNTAX;
				goto exit_func;
			}
			p++;
			t = strtol(p, &p2, 0);
			if (p == p2) {
				res = ERR_SYNTAX;
				goto exit_func;
			}
			p = p2;
			while (*p != '\0' && strchr(" \t", *p) != NULL)
				p++;
			switch (i % 4) {
			case 0:
				elem->calibcoords.samples[i / 4].rawx = t;
				break;
			case 1:
				elem->calibcoords.samples[i / 4].rawy = t;
				break;
			case 2:
				elem->calibcoords.samples[i / 4].x = t;
				break;
			case 3:
				elem->calibcoords.samples[i / 4].y = t;
				break;
			}
		}
		if (*p != '\0') {
			res = ERR_SYNTAX;
			goto exit_func;
		}
	}

	if (ferror(fp))
		res = ERR_IO;
	else
		res = ERR_NONE;

 exit_func:
	fclose(fp);
	if (res != ERR_NONE) {
		free_data(data);
	}

	return (res);
}

int
write_data(const char *filename, struct tpctl_data *data)
{
	int res, fd;
	FILE *fp;
	struct tpctl_data_elem *elem;
	char *p, tempfile[MAXPATHLEN + 1];

	fd = 0;		/* XXXGCC -Wuninitialized [hpcarm] */

	if (filename == NULL) {
		fp = stdout;
	} else {
		strncpy(tempfile, filename, MAXPATHLEN);
		tempfile[MAXPATHLEN] = '\0';
		if ((p = strrchr(tempfile, '/')) == NULL) {
			strcpy(tempfile, TPCTL_TMP_FILENAME);
		} else {
			p++;
			if (MAXPATHLEN <
			    p - tempfile + strlen(TPCTL_TMP_FILENAME))
				return (ERR_NOFILE);/* file name is too long */
			strcat(tempfile, TPCTL_TMP_FILENAME);
		}
		if ((fd = open(tempfile, O_RDWR|O_CREAT|O_EXCL, 0644)) < 0) {
			fprintf(stderr, "%s: can't create %s\n",
			    getprogname(), tempfile);
			return (ERR_NOFILE);
		}
		if ((fp = fdopen(fd, "w")) == NULL) {
			perror("fdopen");
			exit(EXIT_FAILURE);
		}
	}

	TAILQ_FOREACH(elem, &data->list, link) {
		switch (elem->type) {
		case TPCTL_CALIBCOORDS:
			write_coords(fp, elem->name, &elem->calibcoords);
			break;
		case TPCTL_COMMENT:
			fprintf(fp, "%s\n", elem->name);
			break;
		default:
			fprintf(stderr, "%s: internal error\n", getprogname());
			exit(EXIT_FAILURE);
			break;
		}
	}

	if (filename != NULL) {
		fclose(fp);
		close(fd);
		if (rename(tempfile, filename) < 0) {
			unlink(tempfile);
			return (ERR_NOFILE);
		}
	}
	res = ERR_NONE;

	return (res);
}

void
write_coords(FILE *fp, char *name, struct wsmouse_calibcoords *coords)
{
	int i;

	fprintf(fp, "%s,%d,%d,%d,%d,%d", name,
		coords->minx, coords->miny,
		coords->maxx, coords->maxy,
		coords->samplelen);
	for (i = 0; i < coords->samplelen; i++) {
		fprintf(fp, ",%d,%d,%d,%d",
		    coords->samples[i].rawx,
		    coords->samples[i].rawy,
		    coords->samples[i].x,
		    coords->samples[i].y);
	}
	fprintf(fp, "\n");
}

void
free_data(struct tpctl_data *data)
{
	struct tpctl_data_elem *elem;

	while (!TAILQ_EMPTY(&data->list)) {
		elem = TAILQ_FIRST(&data->list);
		TAILQ_REMOVE(&data->list, elem, link);

		switch (elem->type) {
		case TPCTL_CALIBCOORDS:
		case TPCTL_COMMENT:
			free(elem->name);
			break;
		default:
			fprintf(stderr, "%s: internal error\n", getprogname());
			exit(EXIT_FAILURE);
			break;
		}
	}
}

int
replace_data(struct tpctl_data *data, char *name, struct wsmouse_calibcoords *calibcoords)
{
	struct tpctl_data_elem *elem;

	TAILQ_FOREACH(elem, &data->list, link) {
		if (elem->type == TPCTL_CALIBCOORDS &&
		    strcmp(name, elem->name) == 0) {
			elem->calibcoords = *calibcoords;
			return (0);
		}
	}

	elem = alloc(sizeof(*elem));
	elem->type = TPCTL_CALIBCOORDS;
	elem->name = strdup(name);
	elem->calibcoords = *calibcoords;
	if (elem->name == NULL) {
		perror(getprogname());
		exit(EXIT_FAILURE);
	}
	TAILQ_INSERT_TAIL(&data->list, elem, link);

	return (1);
}

struct wsmouse_calibcoords *
search_data(struct tpctl_data *data, char *name)
{
	struct tpctl_data_elem *elem;

	TAILQ_FOREACH(elem, &data->list, link) {
		if (elem->type == TPCTL_CALIBCOORDS &&
		    strcmp(name, elem->name) == 0) {
			return (&elem->calibcoords);
		}
	}

	return (NULL);
}
