/* Daemon for the Pertelian LCD display.

   This program will horizontally scroll information across the display.

	 Written by Ron Lauzon - 2007
	 I release this code into the Public Domain.
	 Use this at your own risk.
*/

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdlib.h>
#include "fifo.h"
#include "pert_interf.h"

# define BUF_SIZE 512

#ifdef DEBUG
#define CONFIG_FILE_NAME "./pertd2.conf"
#else
#define CONFIG_FILE_NAME "/etc/pertd2.conf"
#endif

/* Commands */
char *command_text[] = {"backlight on",
                    "backlight off",
                    "stop",
                    "line1",
                    "line2",
                    "line3",
                    "line4",
                    "delay time",
                    "char delay",
                    "backlight mgt on",
                    "backlight mgt off",
                    NULL};
enum command_enum {
	backlight_on = 0,
	backlight_off,
	stop,
	line1,
	line2,
	line3,
	line4,
	delay_time_cmd,
	char_delay_cmd,
	backlight_mgt_on,
	backlight_mgt_off,
	error_cmd=999};

/* Config information */
char *fifo_name = NULL;
char *device_name = NULL;
unsigned int delay_time = 0;
int backlight_mgt = 0;

/* Information about the data to display */
char *lines[4]; /* The line text */
int pos[4]; /* The position we are in the line */
time_t refresh_time[4]; /* Time data was last refreshed */
int timeout[4]; /* Number of seconds before we expire the line */
int backlight_status; /* 0 - backlight not on, 1 - backlight on */

/* This routine puts the program into "daemon" mode.
 * i.e. it disconnects from the current session so that
 * it's not automatically killed when the user logs off.
 */
int daemon_init() {
	pid_t pid;
	FILE *pidfile;

#ifdef DEBUG
	return(0); /* Don't go daemon in debug mode */
#endif
	/* Fork off 8-) */
	if ( (pid=fork()) < 0)
		return(-1);  /* If error - leave */
	else if (pid != 0)
		exit(0);     /* parent exits */

	/* Child continues */

	/* Become session leader */
	setsid();

	return(0);
  }


int is_numeric(char *buffer) {
	int i;

	/* Look through all the chars in the buffer */
	for (i=0; i<strlen(buffer); i++) {
		/* If we find a character that isn't a digit */
		if (!isdigit(buffer[i])) {
				/* It was bad data */
				return(0); /* false - not numeric */
			}	
	}
	/* By the time we get here, all the characters are digits */
	return(1); /* true - numeric */
}

int read_config(char *config_file_name) {
	FILE *config_file;
	char line[1024];
	char *parm;
	char *value;

#ifdef DEBUG
	printf("Opening config file %s\n",config_file_name);
#endif

	char_delay = 1; /* default correctly for old systems */

	/* Try to open the config file */
	if ((config_file = fopen(config_file_name,"r")) != NULL) {

		/* Read lines from the config file */
		while(fgets(line,1024,config_file) != NULL) {

			/* Trim the new line */
			line[strlen(line)-1] = '\0';

			/* If comment, ignore */
			if (line[0] == '#')
				continue;
 			/* If no = in the string, ignore */
			if (strchr(line,'=') == NULL)
				continue;

			/* We found a parm/value pair.  Point to the value. */
			value=strchr(line,'=');
			value[0] = '\0';
			value++;

			/* Now, process the parm */
			if (strcmp(line,"fifo_name") == 0) {
				fifo_name = (char *)malloc(strlen(value)+1);
				strcpy(fifo_name,value);
		  }
			if (strcmp(line,"device") == 0) {
				device_name = (char *)malloc(strlen(value)+1);
				strcpy(device_name,value);
      }
			if (strcmp(line,"delay_time") == 0) {
				delay_time=atoi(value);
		  }
			if (strcmp(line,"char_delay") == 0) {
				char_delay=atoi(value);
		  }
			if (strcmp(line,"backlight_mgt") == 0) {
				backlight_mgt=atoi(value);
		  }
		}
		fclose(config_file);
	}

	/* Set defaults for any values not in the config file */
	if (fifo_name == NULL) {
			fifo_name = (char *)malloc(1024);
			strcpy(fifo_name,"/tmp/pertd2.fifo");
	}
	if (device_name == NULL) {
		device_name = (char *)malloc(1024);
		strcpy(device_name,"/dev/ttyUSB0");
		}
	if (delay_time == 0)
		delay_time = 500000; /* 1/2 second (500,000 milliseconds) */
}

int get_command() {
	char *command;
	int i;

	command = read_line();
	if (command == NULL) {
		return(error_cmd); /* No data */
	}

	/* lower case the command */
	for(i=0;i<strlen(command);i++)
		command[i] = tolower(command[i]);

#ifdef DEBUG
	printf("Command:%s\n",command);
#endif

	/* Loop through the commands */
	i = 0;
	while(command_text[i] != NULL) {
		/* If we found the command text */
		if (strcmp(command,command_text[i]) == 0) {
			free(command); /* memory leaks are bad */
#ifdef DEBUG
			printf("Command_num:%d\n",i);
#endif
			/* Return that command number */
   		return(i);
		}
		i++;
	}

	/* If we got here, we didn't find the command
	 * so the data is invalid.  Throw it out. */
	free(command); /* memory leaks are bad */
	return(error_cmd); /* bad data */
}

int get_timeout() {
	char *timeout;
	int i;
	int int_timeout;

	timeout = read_line();
	if (timeout == NULL) {
		return(-1); /* No data */
	}

  /* Validate that the timeout is a number */
	if (!is_numeric(timeout)) {
		/* If it wasn't a digit, it was bad data */
		free(timeout);
		return(-1); /* bad data */
	}

#ifdef DEBUG
	printf("Timeout:%s\n",timeout);
#endif

	/* Convert the buffer into a int and return it */
	int_timeout = atoi(timeout);
	free(timeout);
	return(int_timeout);
}

char *get_line() {
	char *EOD;
	char *current_line, *temp;
	char *line_buffer;
	int current_length;

	/* Get EOD string */
	EOD = read_line();
	if (EOD == NULL) {
		return(NULL); /* no data */
	}

	/* We have data */

	/* Allocate a buffer for the line */
	current_length = 1;
	line_buffer = (char *)malloc(current_length);
	line_buffer[0] = '\0';

	/* Read the next line from the fifo */
	current_line = read_line();

	/* While we didn't hit the end of data marker
   * and we didn't run out of fifo data */
	while ((current_line != NULL)
      && (strcmp(current_line,EOD) != 0)) {
		/* If our buffer is too small */
		if ((strlen(current_line) + strlen(line_buffer)+1) > current_length) {
			/* Allocate new buffer and copy the old data over */
			temp = line_buffer;
			line_buffer = (char *)malloc(current_length+BUF_SIZE);
			memcpy(line_buffer,temp,current_length);
			current_length += BUF_SIZE;
			free(temp);
		}

		/* Add the current line to the buffer */
		if (strlen(line_buffer) > 0)
			strcat(line_buffer," ");
		strcat(line_buffer,current_line);

		free(current_line); /* memory leaks are bad */

		current_line = read_line();
	}

	if (current_line != NULL)
		free(current_line); /* memory leaks are bad */
	free(EOD);
	return(line_buffer);
}

void process_delay_time() {
	char *cur_line;

  /* Get the delay time */
	cur_line = read_line();
#ifdef DEBUG
	printf("Read delay_time:%s\n",cur_line);
#endif

	/* No delay time - exit */
	if (cur_line == NULL)
		return;
	if (strlen(cur_line) == 0)
		return;

	/* The delay time must be a number */
	if (is_numeric(cur_line)) {
		delay_time = atoi(cur_line);
		free(cur_line);
#ifdef DEBUG
		printf("Processed delay_time:%d\n",delay_time);
#endif
		return;
	}

	/* If it's not a number, it's bad data - ignore the command */
}

void process_char_delay() {
	char *cur_line;

  /* Get the delay time */
	cur_line = read_line();

	/* No char delay - exit */
	if (cur_line == NULL)
		return;
	if (strlen(cur_line) == 0)
		return;

	/* The delay time must be a number */
	if (is_numeric(cur_line)) {
		char_delay = atoi(cur_line);
		free(cur_line);
		return;
	}

	/* If it's not a number, it's bad data - ignore the command */
}

void process_line(int line_num) {
	int cur_timeout;
	char *cur_line;
	time_t now;

	time(&now);
	cur_timeout = get_timeout();
	if (cur_timeout < 0) {
		cur_timeout = 0;
	}

	cur_line = get_line();

	if (lines[line_num] != NULL)
		free(lines[line_num]);

	lines[line_num] = cur_line;
	timeout[line_num] = cur_timeout;
	refresh_time[line_num] = now;
}

int data_to_display() {
	int temp;
	int i;
	temp = 0;

	/* Look through all the lines */
	for (i=0; i<4; i++) {
		/* If the line isn't empty */
		if (lines[i] != NULL) {
			if (strlen(lines[i]) > 0) {
				return 1; /* we have data */
			}
		}
	}

	/* We only get here when no lines have data */
	return 0;
}

void display_date_time() {
	time_t now_t;
	struct tm *now_tm;
	char line2[PERT_DISPLAY_WIDTH+1], line3[PERT_DISPLAY_WIDTH+1];

	/* First we need to get the current time */
	now_t = time(NULL);

	/* Now, we need to convert the time_t to something useful */
	now_tm = localtime(&now_t);

	/* Format the date/time into a date line and a time line */
	/* DDD MMM DD, YYYY - 16 chars pad 4 spaces */
	strftime(line2,PERT_DISPLAY_WIDTH+1,"  %a %b %d, %Y  ",now_tm);
	/* HH:MM:SS - 8 chars - pad 12 spaces */
	strftime(line3,PERT_DISPLAY_WIDTH+1,"      %T      ",now_tm);
	line2[PERT_DISPLAY_WIDTH] = '\0';
	line3[PERT_DISPLAY_WIDTH] = '\0';

#ifdef DEBUG
	printf("line2= %s\n",line2);
	printf("line3= %s\n",line3);
#endif

	/* Put the lines on the display */
	wrtln(1,line2);
	wrtln(2,line3);
}

/* Copy over PERT_DISPLAY_WIDTH chars of data into line - wrapping around 
 * when at the end of the data */
char *fill_line(int lineno,int offset) {
	static char temp_line[21];
	int i;
	int temp_pos;

	/* If the line contains no data */
	if (lines[lineno] == NULL) {
		/* If we are on the first line */
		if (lineno == 0) {
			/* No data to display - return blanks to clear line */
			memset(temp_line,' ',PERT_DISPLAY_WIDTH);
      temp_line[PERT_DISPLAY_WIDTH] = '\0';
			return(temp_line);
		} else {
      /* Try to fill with some of the previous line */
      return(fill_line(lineno-1,offset + PERT_DISPLAY_WIDTH));
    }
	}
	if (strlen(lines[lineno]) == 0) {
    /* If we are on the first line */
    if (lineno == 0) {
    	/* No data to display - return blanks to clear line */
			memset(temp_line,' ',PERT_DISPLAY_WIDTH);
			temp_line[PERT_DISPLAY_WIDTH] = '\0';
			return(temp_line);
		} else {
			/* Try to fill with some of the previous line */
			return(fill_line(lineno-1,offset + PERT_DISPLAY_WIDTH));
			}
	}

	/* If there are PERT_DISPLAY_WIDTH or less chars to display */
	if (strlen(lines[lineno]) < 21) {
		/* If this is the first time called */
   	if (offset == 0) {
				/* Return the line and pad with blanks */
		    strcpy(temp_line,(lines[lineno]));
		    for (i=strlen(lines[lineno]); i<PERT_DISPLAY_WIDTH; i++)
			    temp_line[i] = ' ';
		    temp_line[PERT_DISPLAY_WIDTH] = '\0';
		    return(temp_line);
		} else { /* not the first time called - return blanks */
        /* No data to display - return blanks to clear line */
		    memset(temp_line,' ',PERT_DISPLAY_WIDTH);
        temp_line[PERT_DISPLAY_WIDTH] = '\0';
		    return(temp_line);
        }
		}

    /* If there's not enough data to display */
    if (strlen(lines[lineno]) <= offset) {
        /* No data to display - return blanks to clear line */
        memset(temp_line,' ',PERT_DISPLAY_WIDTH);
        temp_line[PERT_DISPLAY_WIDTH] = '\0';
        return(temp_line);
    }
        
	/* If there are more than PERT_DISPLAY_WIDTH chars to display,
	 * display PERT_DISPLAY_WIDTH starting at the last position
	 * displayed */
	temp_pos = pos[lineno] + offset;
	while (temp_pos > strlen(lines[lineno]))
		temp_pos = temp_pos - strlen(lines[lineno]);
	if (temp_pos < 0)
		temp_pos = 0;

	/* For each char in the temp line */
	for (i=0; i<PERT_DISPLAY_WIDTH; i++) {
		/* If we are pointing beyond the original line,
		 * start over at the beginning */
		if (temp_pos >= strlen(lines[lineno]))
			temp_pos = 0;
			/* Copy over 1 char from the original line */
      temp_line[i] = lines[lineno][temp_pos];
			temp_pos++;
	}

	/* Increment where we start */
	pos[lineno]++;

	/* If we went past the end of the line,
	 * start over */
	if (pos[lineno] >= strlen(lines[lineno]))
		pos[lineno] = 0;

	/* Force an end of line */
	temp_line[PERT_DISPLAY_WIDTH] = '\0';
	return(temp_line);
}

int main(int argc, char *argv[]) {
	int stop_indicated;
	int processing_command;
	int i;
	time_t now;

	/* Read config file */
	if (argc > 1) {
		/* We have an argument - try to read the config file
		 * using that argument */
	  if (!read_config(argv[1])) {
			/* If we failed - try to read the default config file */
	  	read_config(CONFIG_FILE_NAME);
		}
	} else {
		/* No parm - use default config file */
	  read_config(CONFIG_FILE_NAME);
	}

#ifdef DEBUG
	printf("Device = %s\n",device_name);
	printf("Fifo = %s\n",fifo_name);
	printf("Delay time = %d\n",delay_time);
	printf("Char Delay time = %d\n",char_delay);
#endif

	/* Initialize arrays */
	time(&now);
	for(i=0;i<4;i++) {
		lines[i] = NULL;
		refresh_time[i] = now;
		timeout[i] = 0;
		pos[i] = 0;
	}

	/* Initialize the Pertelian */
  display_init(device_name);

	/* The backlight is on when the program starts */
	backlight_status = 1;
	backlight(1);

	/* Go daemon */
	if (daemon_init() != 0) {
		fprintf(stderr,"Failed to go daemon\n");
		return(1);
		}

	/* Try to open the fifo */
	if (!open_fifo(fifo_name)) {
		fprintf(stderr,"Error creating FIFO: %s\n",fifo_name);
		return(1);
	}

	/* Loop until we are told to stop */
	stop_indicated = 0;
	while (!stop_indicated) {
	 	time(&now);

		/* Get the command from the fifo */
	  processing_command = 1;
		while (processing_command) {
			switch (get_command()) {
				case backlight_on:
					backlight(1);
					backlight_status = 1;
					break;
				case backlight_off:
					backlight(0);
					backlight_status = 0;
					break;
				case stop:
					stop_indicated = 1;
					break;
				case line1:
					process_line(0);
					break;
				case line2:
					process_line(1);
					break;
				case line3:
					process_line(2);
					break;
				case line4:
					process_line(3);
					break;
				case delay_time_cmd:
					process_delay_time();
					break;
				case char_delay_cmd:
					process_char_delay();
					break;
				case backlight_mgt_on:
					backlight_mgt = 1;
					break;
				case backlight_mgt_off:
					backlight_mgt = 0;
					break;
				default:
					processing_command = 0; /* no command to process */
					break;
			}
		}

#ifdef DEBUG
		printf("main:processed command\n");
#endif

		/* Refresh the display */
		for(i=0;i<4;i++) {
			/* If the line has data */
			if (lines[i] != NULL) {
				/* If we are supposed to time the line out */
				if (timeout[i] > 0) {
					/* If the line is old */
					if (difftime(now,refresh_time[i]) > timeout[i]) {
						/* Get rid of the line */
						free(lines[i]); /* Memory leaks are bad */
						lines[i] = NULL;
					}
				}
			}

			/* Write out the line to the display */
			wrtln(i,fill_line(i,0));
		}

		/* If there was no data to display */
		if (!data_to_display()) {
			/* Let's put the date/time on the display */
			display_date_time();

			/* If the backlight is on,
			 * and backight mgt is on,
			 * turn the backlight off */
			if ((backlight_status == 1)
			&&  (backlight_mgt == 1))	{
				backlight(0);
				backlight_status=0;
			}
		} else {
			/* There was data to display.
			 * If the backlight is off,
			 * and backlight mangement is on,
			 * turn the backlight on */
			if ((backlight_status == 0)
			&&  (backlight_mgt ==1))	{
				backlight(1);
				backlight_status=1;
			}
		}

		/* Pause for a bit */
  	sleep_us(delay_time);
	}

	/* Display a useful message on the Pertelian */
	wrtln(0,"                    ");
	wrtln(1,"pertd daemon stopped");
	wrtln(2,"                    ");
	wrtln(3,"                    ");

	/* Clean up the connect to the Pertelian */
	display_close();

	/* Done - clean up */
	close_fifo(fifo_name);

	return(0);
}