/***********************************************

 This program is for a Pertelian display.  

 The first time through, or whenever a change is made, it will index a
 flat file, looking for lines that are 80 characters or less and can
 be broken evenly on 4 lines of 20 characters each.  Any lines that
 don't meet this criteria are ignored since they'll just print out
 screwed up anyway. The contents of the file can be anything.  

 On the first and all subsequent invocations, the program will use the
 index to randomly fetch something from the flat file to print on the
 Pertelian display.

 I wrote this program so that I could display a wonderful
 german/english word/phrase collection maintained by Paul Hemetsberger
 of www.dict.cc.  The collection is 30 Mb file, which explains why
 creating an index was necessary!

 Some of the code in this program was borrowed from Frans
 Meulenbroeks' Pertelian test.  He put in the effort to make the
 display function on a linux box.

 Kevin Dowd (dowd@atlantic.com)

 **********************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <time.h>

#define MAXLEN 512
#define ONELINE 20
#define NLINES 4
#define FALSE 0
#define TRUE 1
#define DEVICE "/dev/ttyUSB0"
#define DELAY 1000000

/* DELAY is constant used to generate some delay. 
 * You might want to tweak it for your hardware.
 * If the value is too small some or all of the data is going to be garbled
 * or the initialisation will fail.
 * If the value is too large you'll have to wait quite a while....
 */

const static unsigned char rowoffset[4] =
  { 0x80, 0x80+0x40, 0x80 + 0x14, 0x80 + 0x40 + 0x14 };
FILE *pertelian;

main(argc, argv)
int argc;
char *argv[];
{
  char *inputfilename;
  char *indexfilename;
  char lines[NLINES][ONELINE+1];
  int i;

/* Check for arguments.
*/
  if (argc == 1) {
    fprintf (stderr, "Usage: %s filename\n", argv[0]);
    exit (0);
  }

  inputfilename = argv[1];

/* Create filename for the index:
*/
  indexfilename = malloc (strlen(inputfilename)+5);
  sprintf (indexfilename, ".%s.dat",inputfilename);

/* Make an index file if necessary
*/
  makeindex(inputfilename, indexfilename);

/* Initialize the display
*/
  init (DEVICE);
  dooutput(inputfilename, indexfilename, lines);
  for (i=0; i < NLINES; i++) {
    preen (lines[i]);
    wrtln (i,lines[i]);
  }
}

preen (line)
char *line;
{
/* Go through the string and make any necessary substitutions:
*/
  char *p;
  for (p=line; *p != '\0'; p++) {
    if (*p == 0xffffffdc)
      *p = 0xf5; // capital u with umlaut
    if (*p == 0xfffffffc)
      *p = 0xf5; // u with umlaut
    if (*p == 0xffffffdf)
      *p = 0xe2; // s-set
    if (*p == 0xffffffdc)
      *p = 0xef; // capital o with umlaut
    if (*p == 0xfffffff6)
      *p = 0xef; // o with umlaut
    if (*p == 0xffffffd6)
      *p = 0xe1; // capital a with umlaut
    if (*p == 0xffffffe4)
      *p = 0xe1; // a with umlaut
    if (*p == 0xffffff16)
      *p = 0xa1; // degrees
  }
}

makeindex(inputfilename, indexfilename)
char *inputfilename;
char *indexfilename;
{
  char inputline[MAXLEN];
  FILE *inputfile;
  int indexfile;
  int fileindex;
  char *tokenindex[MAXLEN/2];
  int icurrent, ilast, i;
  int k, length, indexstat;
  char *p;
  struct stat statinput, statindex;
  
/* Check to see if the index doesn't exist or the index is older than the data:
*/
  if (stat(inputfilename, &statinput) == -1) {
    fprintf (stderr, "Couldn't find or read %s.\n",inputfilename);
    exit (1);
  }

  indexstat = stat(indexfilename, &statindex);

  if ((indexstat == -1) ||
      (statinput.st_mtime > statindex.st_mtime)) {
    fprintf (stderr, "Creating new index file.\n");
  }
  else
    return 0;

/* Open the input file:
*/
  if ((inputfile = fopen (inputfilename,"r")) == NULL) {
    fprintf (stderr, "Error on open to %s\n", inputfilename);
    exit (1);
  }

/* Open the output
*/
  if ((indexfile = creat (indexfilename, 0664)) == -1) {
    fprintf (stderr, "Error on open to %s\n", indexfilename);
    exit (1);
  }

/* Go through every line in the file:
*/
  fileindex = 0;

  while (fgets (inputline, MAXLEN, inputfile) != NULL) {

/* Grab tokens until reaching the newline.  They
   must fit into four lines.  This includes the 
   tokens (say there are K of them in a given 
   line) plus K-1 spaces.  Use strtok to populate
   an array of indexes and lengths.  After the
   tokens are gathered, do the line-math.
*/
    tokenindex[0] = p = strtok (inputline, " \t\n");

    for (i=1; p != NULL; i++)
      p = tokenindex[i] = strtok (NULL, " \t\n");

/* Now see if we can fit them all on four lines.
   There are i tokens.
*/
    ilast = icurrent = 0;
    i--;

    for (k=0; k<NLINES; k++) {

      for (length = 0; (length < ONELINE) && (icurrent < i);) {
	length += strlen (tokenindex[icurrent]);
	ilast   = icurrent;
	icurrent++;
      }

/* The last token didn't fit.  Go back one.
*/
      if (icurrent < i)
	icurrent = ilast;
    }

/* We know we were successful if icurrent == i.  Record the 
   location in the input file in the index.
*/
    if (icurrent == i)
      write (indexfile, &fileindex, sizeof(int));

/* Get the new index:
*/
      fileindex = ftell(inputfile);
  }
  close (indexfile);
  fclose (inputfile);
}
      
dooutput(inputfilename, indexfilename, outline)
char *inputfilename;
char *indexfilename;
char outline[][ONELINE+1];
{
/* Grab a random index from the index file.  Follow the data file to the data.
   Output four lines of 20 chars or less each.
*/
  struct stat statbuf;
  int indexfile;
  int index;
  int filelength;
  time_t t;
  FILE *inputfile;
  char inputline[MAXLEN];
  int findex;
  char *tokenindex[ONELINE];
  int icurrent, ilast, istart, i, m;
  int k, length, full;
  char *p;

  if ((indexfile = open (indexfilename, O_RDONLY)) == -1) {
    fprintf (stderr, "Error on open to %s\n", indexfilename);
    exit (1);
  }

/* Get the length of the index file:
*/
  fstat (indexfile, &statbuf);
  filelength = (int) statbuf.st_size;
  
/* Pick an index:
*/
  srand ((int) time(&t));
  index = (random() % filelength) & ~(sizeof (int) - 1);

/* Seek to the index and read it.
*/
  lseek (indexfile, (off_t) index, SEEK_SET);
  read (indexfile, &findex, sizeof (int));

/* Open the data file and seek to that spot.
*/
  if ((inputfile = fopen (inputfilename,"r")) == NULL) {
    fprintf (stderr, "Error on open to %s\n", inputfilename);
    exit (1);
  }

  fseek (inputfile, findex, SEEK_SET);

/* Grab a line of input:
*/
  fgets (inputline, MAXLEN, inputfile);

/* Output four lines:
*/
  tokenindex[0] = p = strtok (inputline, " \t\n");

  for (i=1; p != NULL; i++)
    p = tokenindex[i] = strtok (NULL, " \t\n");

/* Now see if we can fit them all on four lines.
   There are i tokens.
*/
  icurrent = 0;
  i--;

  for (k=0; k < NLINES; k++) {

    full = FALSE;
    *outline[k] = '\0';

    while (full == FALSE) {
      if ((icurrent < i) &&((strlen(outline[k]) + strlen(tokenindex[icurrent]) + 1) <= ONELINE)) {
	strcat (outline[k], tokenindex[icurrent]);
	strcat (outline[k], " ");
	icurrent++;
      }
      else
	full = TRUE;
    }
    //    fprintf (stderr, "%s\n", outline[k]);
  }
}

delay(int n)
{
  volatile int i;
  for (i = 0; i < n; i++);
}

/*
 * putcode
 * This function writes a code byte to the device
 */

putcode(char code)
{
  fputc(0xfe, pertelian);
  fputc(code, pertelian);
  fflush(pertelian);
  delay(DELAY);
}

/*
 * wrtch
 *     This function writes a character byte to the device
 */
wrtch(char c)
{
  fputc(c, pertelian);
  fflush(pertelian);
  delay(DELAY);
}

/*
 * wrt
 *     This function writes a character string to the device (0 terminated)
 *     it writes the data to whereever the cursor is pointing
 */
wrt(char *p)
{
  while(*p) {
    wrtch(*p);
    p++;
  }
}

/*
 * wrtln
 *     This function writes a character string to a specific row
 */
wrtln(int row, char *p)
{
  putcode(rowoffset[row]);
  wrt(p);
}

/*
 * init
 *     initialisation code
 */
init(char *device)
{
  pertelian = fopen(device, "wb");
  if (pertelian == NULL)
    {
      fprintf(stderr, "Cannot open device %s for writing !\n", device);
      exit(1);
    }
  putcode(0x38); /* initialise display (8 bit interface, shift to right */
  putcode(0x06); /* cursor move direction to the right, no automatic shift */
  putcode(0x10); /* move cursor on data write */
  putcode(0x0c); /* cursor off */
  putcode(0x01); /* clear display */
  putcode(0x03); /* backlight on (3 = on, 2 = off) */
}

