/* cookie.c - print a cookie at random from cookie-dough
 *
 * First version by Jim Blandy - Tue May  8 1990 - Oberlin College, OH
 * Modified by Karl Fogel      - Thu 29 Feb 1996 - Oberlin College, OH, USA.
 *
 * This program selects and prints a cookie from cookie-dough.
 * The cookie-dough file should be a text file containing the cookies,
 * separated by \ characters.  If you want to include a \ in the text,
 * you can double it.
 */

#define _POSIX_SOURCE

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

#define BOF EOF
#define SEPARATOR ('\\')

/* This just gets argv[0] in main; it's for error messages. */
char *progname;

/* Where the goodies are. */
char *cookie_jar;

/* If no cookie jar specified on command line, then use this one: */
#ifndef DEFAULT_COOKIE_JAR
#define DEFAULT_COOKIE_JAR "/usr/local/lib/cookie/cookie-dough"
#endif



size_t
file_size (char *file)
{
  size_t file_size;
  struct stat stat_buf;
  stat (file, &stat_buf);
  file_size = (long) stat_buf.st_size;
  return file_size;
}


/* `fp' should be stdin or stderr. */
void
usage (FILE *fp)
{
  char *msg =
    "Usage: \"%s [COOKIEFILE]\"\n"
    "Prints out a fortune cookie message.\n"
    "\n"
    "If no COOKIEFILE given, it checks environment variable COOKIE_JAR.\n"
    "Failing that, it tries " DEFAULT_COOKIE_JAR ".\n"
    "\n"
    "To read a cookiefile whose name starts with a hyphen, use the\n"
    "empty option (\"-\"), as in:\n" 
    "\n"
    "\"%s - [COOKIEFILE-WHOSE-NAME-STARTS-WITH-A-DASH]\"\n"
    "\n"
    "Run \"%s -help\" for this message.\n"
    ;
  
  fprintf (fp, msg, progname, progname, progname);
  exit ((fp == stderr)? 1 : 0);
}


FILE *
fopen_cookie_jar (int argc, char **argv)
{
  FILE *fp;

  if (argc < 2)
    {
      if ((cookie_jar = (char *) getenv ("COOKIE_JAR")) == NULL)
        cookie_jar = DEFAULT_COOKIE_JAR;
    }
  else /* argc >= 2 */
    {
      if (argv[1][0] == '-')
        {
          if ((strcmp (argv[1], "-h") == 0)
              || (strcmp (argv[1], "-u") == 0)
              || (strcmp (argv[1], "-help") == 0)
              || (strcmp (argv[1], "--help") == 0)
              || (strcmp (argv[1], "-usage") == 0)
              || (strcmp (argv[1], "--usage") == 0)
              || (strcmp (argv[1], "-?") == 0)
              )
            {
              usage (stdout);
            }
          else if ((strcmp (argv[1], "-") == 0)
                   || (strcmp (argv[1], "--") == 0))
            {
              /* The end of option parsing, next arg is a file no
                 matter what. */
              if (argc < 3)
                usage (stderr);
              if (argc > 3)
                usage (stderr);
              else /* argc == 3 */
                cookie_jar = argv[2];
            }
          else
            usage (stderr);
        }
      else if (argc > 2)
        usage (stderr);
      else
        cookie_jar = argv[1];
    }

  /* We now have a cookie_jar from whatever source.  Open it. */
  if ((fp = fopen (cookie_jar, "r")) == NULL)
    {
      fprintf (stderr, "%s: error: fopen %s failed\n",
               progname, cookie_jar);
      exit (1);
    }
  return fp;
}


void
reach_into_cookie_jar (FILE *fp)
{
  size_t size;
  long int pos;
  int rc;
  pid_t pid;

  pid = getpid ();
  srand (time (NULL) + (int) pid);
  size = file_size (cookie_jar);

  /* What the heck, it's not an error. */
  if (size == 0)
    exit (0);

  pos = (long) (rand () % size);

  /* Subtract 1 to avoid landing on the last char.  The reasoning
     behind this is arcane but secure. */
  if ((pos == size) && (pos > 0))
    pos--;

  rc = fseek (fp, pos, SEEK_SET);
  if (rc != 0)
    {
      fprintf (stderr, "%s: error: fseek %s[%ld] failed\n",
               progname, cookie_jar, pos);
      fclose (fp);
      exit (1);
    }
}



/* Get the character CH before "point", or BOF.
 * Assuming not BOF, Decrement file position so next call gets
 * character before CH.
 */
int
getc_backward (FILE *fp)
{
  int ch, rc;

  rc = fseek (fp, -1, SEEK_CUR);
  if (rc != 0)
    {
      if (ftell (fp) != 0)
        {
          fprintf (stderr, "%s: error: fseek failed\n", progname);
          exit (1);
        }
      else
        ch = BOF;
    }
  else
    {
      ch = getc (fp);
      rc = fseek (fp, -1, SEEK_CUR);
      if (rc != 0)
        {
          /* We just read a char thus can't be at pos 0, so error
             unconditionally. */
          fprintf (stderr, "%s: error: fseek failed\n", progname);
          exit (1);
        }
      if (rc != 0)
        {
          /* If not at pos 0, then something went wrong. */
          if (ftell (fp) != 0)
            {
              fprintf (stderr, "%s: error: fseek failed\n", progname);
              exit (1);
            }
        }
    }

  return ch;
}


void
grab_a_cookie (FILE *fp)
{
  int ch;
  int rc;
  int bs_count = 0;
  long int pos;
  
  /* We've landed somewhere in the file, probably smack in the middle
     of a cookie.  Find the start of this cookie. */
  
  /* Special-case the start of the file. */
  if (ftell (fp) == 0)
    return;

  /* If landed on a backslash, rewind until before backslashes or
     until start of file. */
  while ((ch = getc_backward (fp)) != BOF)
    {
      while (ch == SEPARATOR)
        {
          bs_count++;
          /* Maybe save the current position; if we read an odd number
             of backslashes, then we'll want to jump back here because
             it's the start of the cookie: */
          if (bs_count == 1)
            pos = ftell (fp) + 1;
          
          ch = getc_backward (fp);
          
          /* The start of the file is automatically the start of the
             first cookie. */
          if (ch == BOF)
            {
              /* We must have worked our way back here, because above
                 we special-cased the start of the file. */
              if ((bs_count == 1) || ((bs_count % 2) == 0))
                {
                  rc = fseek (fp, pos, SEEK_SET);
                  if (rc != 0)
                    {
                      fprintf (stderr,
                               "%s: error: fseek failed\n",
                               progname);
                      fclose (fp);
                      exit (1);
                    }
                }
              else /* (bs_count % 2) != 0 */
                {
                  rc = fseek (fp, pos, SEEK_SET);
                  if (rc != 0)
                    {
                      fprintf (stderr,
                               "%s: error: fseek failed\n",
                               progname);
                      fclose (fp);
                      exit (1);
                    }
                }
              return;
            }
        }

      if ((bs_count % 2) != 0)
        {
          rc = fseek (fp, pos, SEEK_SET);
          if (rc != 0)
            {
              fprintf (stderr, "%s: error: fseek failed\n", progname);
              fclose (fp);
              exit (1);
            }
          break;
        }
      else
        bs_count = 0;
    }
}


void
eat_the_cookie (FILE *fp)
{
  int ch;

  /* Now print out the cookie. */
  while ((ch = getc (fp)) != EOF)
    {
      if (ch == SEPARATOR)
        {
          int ch2;
          ch2 = getc (fp);
          if ((ch2 != SEPARATOR) || (ch2 == EOF))
            break;
        }
      printf ("%c", ch);
    }
}


void
main (int argc, char **argv)
{
  FILE *fp;

  /* Set up the program's name. */
  if (argc < 1) /* huh? */
    progname = "cookie";
  else
    progname = argv[0];

  fp = fopen_cookie_jar (argc, argv);

  reach_into_cookie_jar (fp);
  grab_a_cookie (fp);
  eat_the_cookie (fp);

  fclose (fp);
}


