/*
 * Copyright (c) 2006
 *	Protium Computing, Inc.  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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Protium Computing, Inc.
 * 4. The name of Protium Computing, Inc. may not be used to endorse or 
 *    promote products derived from this software without specific prior 
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY PROTIUM COMPUTING ``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 PROTIUM COMPUTING 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 <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <termios.h>

#include "scc.h"

char * prog;
int initialize = 0;
int verbose = 0;

scc_cmd_t powercmds[] = {
 	{	"a", 		0x61000000}, 
 	{	"run", 		SCC_RUN}, 	
	{	"off", 		SCC_STOP},
 	{	"advoff", 	SCC_ADVISESTOP}, 
	{	"restart", 	SCC_RESTART,},
 	{	"advrestart", 	SCC_ADVISERESTART}, 
	{	"reset", 	SCC_RESET,},
 	{	"advreset", 	SCC_ADVISERESET}, 
	{	"EOT",		-1}};

scc_cmd_t ledcmds[] =   {	
	{	"off", 		SCC_LEDOFF},
	{	"blue", 	SCC_LEDBLUE},
	{	"red", 		SCC_LEDRED},
	{	"blueflash", 	SCC_LEDBLUEFLASH},
	{	"redflash",	SCC_LEDREDFLASH},
	{	"alternate", 	SCC_LEDALTERNATE1},
	{	"alternate3", 	SCC_LEDALTERNATE3},
	{	"EOT",		-1}};

scc_cmd_t fancmds[]=   {	
	{	"auto", 	SCC_FANAUTO},
	{	"on", 		SCC_FANON},
	{	"EOT",		-1}};

void
usage(void)
{
	int i;
	scc_cmd_t *c;

	fprintf(stderr,	"Usage: %s OPTIONS\n", prog);
	fprintf(stderr,"%s controls the Iomega StorCenter's LED, fan and soft power.\n\n", prog);
	fprintf(stderr,"  -?\t\tthis message\n");
	fprintf(stderr,"  -v\t\tverbose, show all packets\n");
	fprintf(stderr,"  -d\t\tsend send directly, stop/restart only\n");
	fprintf(stderr,"\n  -p POWER\tcontrol soft power\n");
	fprintf(stderr,"\t\tPOWER=[");
	for (c = powercmds,i=0; c->scc_cmdval != -1; c++,i++) {
		if (i) fprintf(stderr,"|");
		fprintf(stderr,"%s", c->scc_cmd);
	}
	fprintf(stderr,"]\n\n");

	fprintf(stderr,"  -l LED\tcontrol LED state\n");
	fprintf(stderr,"\t\tLED=[");
	for (c = ledcmds,i=0; c->scc_cmdval != -1; c++,i++) {
		if (i) fprintf(stderr,"|");
		fprintf(stderr,"%s", c->scc_cmd);
	}
	fprintf(stderr,"]\n  -r RATE\tRate the LED flashes (0<=RATE<256)\n\n");


	fprintf(stderr,"  -f FAN\tcontrol fan operation\n");
	fprintf(stderr,"\t\tFAN=[");
	for (c = fancmds,i=0; c->scc_cmdval != -1; c++,i++) {
		if (i) fprintf(stderr,"|");
		fprintf(stderr,"%s", c->scc_cmd);
	}
	fprintf(stderr,"]\n  -h TEMP\tHot temp when FAN=auto should go on (0<=TEMP<256)\n");
	fprintf(stderr,"  -c TEMP\tCold temp when FAN=auto should go off (0<=TEMP<256)\n");
	
	(void)exit(2);
}

int
sendrecv(int fd, int *i, int *j)
{
	char			buf[8];
	int   		rc;
	fd_set		fds;
	struct timeval	sec = {1, 0};
	struct timespec	tenth_sec = {0, 100000000};


	/* build the buffer */
	*(int *)(buf) = *i;
	*(int *)(buf + 4) = *j;

	if (verbose) 
		printf("Send: [0x%08x 0x%08x]\n", *i, *j);

	if ((rc = write(fd, buf, 8)) != 8) {
		perror("write failed failed");
		return(-1);
	}
	
	/* Wait for data */
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
	rc = select(1024, &fds, NULL, NULL, &sec);

	if (rc == -1) {
      	perror("select failed");
		return (-1);
	} else if (!rc) {
		perror("select timedout");
		return (-1);
	} 

	/* Consume it */
	if ((rc = read(fd, buf, 8)) != 8) {
		perror("read failed failed");
		return(-1);
	}

	*i = *(int *)(buf);
	*j = *(int *)(buf + 4);

	if (verbose) 
		printf("Recv: [0x%08x 0x%08x]\n", *i, *j);

	/* Let it rest */
	nanosleep(&tenth_sec, NULL);

	return(0);
}
	
int 
setup_clnt()
{
	int s, len;
	struct sockaddr_un remote;

	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		return(-1);
        }

	remote.sun_family = AF_UNIX;
        strcpy(remote.sun_path, SCC_SOCKET);
        len = strlen(remote.sun_path) + sizeof(remote.sun_family);
        if (connect(s, (struct sockaddr *)&remote, len) == -1) {
		return(-1);
        }

	return(s);
}

int 
setup_cntlr(int *i, int *j)
{
	int	fd;
	char	*dev = SCC_DEVICE;
	struct termios	ti;

	/* setup the serial connection to 9600, 8n1 */
	if ((fd = open(dev, O_RDWR|O_NOCTTY)) < 0) {
		perror("Open failed");
		return(-1);
	}

	if (ioctl(fd, TCGETS, &ti) < 0) {
		perror("TCGETS failed");
		return(-1);
	}
	
	if (ioctl(fd, TCFLSH, 0) < 0) {
		perror("TCFLSH failed");
		return(-1);
	}	

	ti.c_iflag = IGNPAR;
	ti.c_oflag = 0;
	ti.c_cflag = (B9600 | CS8 | CREAD | CLOCAL);
	ti.c_lflag = 0;
	ti.c_line = N_TTY; 
	bzero(ti.c_cc, NCCS);
	ti.c_cc[VMIN] = 0x08;

	if (ioctl(fd, TCSETS, &ti) < 0) {
		perror("TCSETS failed");
		return(-1);
	}
	
	if (verbose) printf("Resetting microcontroller\n");
	*i = SCC_RESETW1;
	*j = SCC_RESETW2;
	scc_setval(i, SCC_IDMASK, SCC_HOST);
	scc_setval(j, SCC_CKSUMMASK, (int)scc_cksum(i, j));
	if (sendrecv(fd, i, j) < 0) 
		return(-1); 

	return(fd);
}

int 
main(int argc, char *argv[])
{

	int	devfd, w1, w2, h1, h2;
	int	coldtemp, hottemp, fancmd;
	int	ledrate, ledcmd;
	int	powercmd;
	int	direct;
	int	c;
	char	buf[SCC_PACKETLEN];
	scc_cmd_t	*ct;

	extern char	*optarg;
 	extern int	optind, opterr, optopt;

	prog = argv[0];
	fancmd = coldtemp = hottemp = -1;
	ledcmd = ledrate = -1;
	powercmd = -1;
	direct = 0;
	
	while ((c = getopt(argc, argv, "c:df:h:l:p:r:wv?")) != EOF) {
		switch (c) {
		case 'c':
			if (coldtemp != -1)  /* more than one -c arg */
				usage();

			coldtemp = atoi(optarg);
			if ((coldtemp > 255) || (coldtemp < 0))
				usage();
			break;
		case 'd':
			direct = 1;
			break;
		case 'f':
			if (fancmd != -1)  /* more than one -f arg */
				usage();

			for (ct = fancmds; ct->scc_cmdval != -1; ct++) {
				if (!strcmp(ct->scc_cmd, optarg)) {
					fancmd = ct->scc_cmdval;
				}
			}

			if (fancmd == -1)  /* Failed to find the req */
				usage();

			break;
		case 'h':
			if (hottemp != -1)  /* more than one -h arg */
				usage();

			hottemp = atoi(optarg);
			if ((hottemp > 255) || (hottemp < 0))
				usage();
			break;
		case 'l':
			if (ledcmd != -1)  /* more than one -l arg */
				usage();

			for (ct = ledcmds; ct->scc_cmdval != -1; ct++) {
				if (!strcmp(ct->scc_cmd, optarg)) {
					ledcmd = ct->scc_cmdval;
				}
			}

			if (ledcmd == -1)  /* Failed to find the req */
				usage();
			break;
		case 'p':
			if (powercmd != -1)  /* more than one -p arg */
				usage();

			for (ct = powercmds; ct->scc_cmdval != -1; ct++) {
				if (!strcmp(ct->scc_cmd, optarg)) {
					powercmd = ct->scc_cmdval;
				}
			}

			if (powercmd == -1)  /* Failed to find the req */
				usage();
			break;
		case 'r':
			if (ledrate != -1)  /* more than one -h arg */
				usage();

			ledrate = atoi(optarg);
			if ((ledrate > 255) || (ledrate < 0))
				usage();
			break;
		case 'v':
			verbose=1;
			break;
		case '?':
			usage();
			break;

		}
	}

	if (direct) {
		/* 
		 * Setup the cntlr connection 
		 */
		if ((devfd = setup_cntlr(&w1, &w2)) < 0)
			exit(1);
		scc_defaults(&w1, &w2);
	} else {
		/* 
		 * Setup the clnt connection, if we get an connection
		 * refused that means the daemon isn't listening.
		 * so go direct
		 */
		w1 = 0;
		w2 = 0;
		if ((devfd = setup_clnt()) < 0) {
			/* log it */
			if ((devfd = setup_cntlr(&w1, &w2)) < 0)
				exit(1);
			direct = 1;
			scc_defaults(&w1, &w2);
		}
	}
	
	/* 
	 * Direct (direct to the cntlr):
	 * w1 and w2 are setup with the default cntlr packet.
	 * Now lets poke the command line args into the packet.
	 *
	 * Nondirect (via the daemon):
	 * w1 and w2 are setup with nulls. The packet sent to the daemon 
	 * contains only the changes to the current state.
	 */
	if (powercmd != -1) 
		scc_setval(&w1, SCC_POWERMASK, powercmd);
	if (ledcmd != -1)
		scc_setval(&w1, SCC_LEDMASK, ledcmd);
	if (ledrate != -1)
		scc_setrate(&w1, SCC_LEDRATEMASK, SCC_LEDRATESHIFT, ledrate);
	if (fancmd != -1)
		scc_setval(&w1, SCC_FANMASK, fancmd);

	if (coldtemp != -1)
		scc_setrate(&w2, 
			    SCC_FANTEMPOFFMASK, SCC_FANTEMPOFFSHIFT, coldtemp);
	if (hottemp != -1)
		scc_setrate(&w2, 
			    SCC_FANTEMPONMASK, SCC_FANTEMPONSHIFT, hottemp);

	scc_setval(&w2, SCC_IDMASK, SCC_HOST);
	scc_setval(&w2, SCC_CKSUMMASK, (int)scc_cksum(&w1, &w2));


	if (direct) {
		if (verbose) {
			printf("New State: \n");
			scc_validate(&w1, &w2, verbose);
		}

		if (sendrecv(devfd, &w1, &w2) < 0) 
			exit(1); 
	} else {
		*(int *)(&buf[0]) = w1;
		*(int *)(&buf[4]) = w2;
		
		if (send(devfd, buf, SCC_PACKETLEN, 0) == -1) {
			perror("send");
			exit(1);
		}
	}

	close(devfd);
  	exit(0);
}