/* ungetc.c (emx+gcc) */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ref
{
  int size;
  int loc;
  int sp;
  int stack_size;
  int binary;
  unsigned char *contents;
  long *positions;
  unsigned char *pushback;
};

static FILE *f;
static struct ref *r;
static int max_ungetc_buffered;
static int max_ungetc_unbuffered;
static int random_loops;
static int buffer_size;
static enum {TS_NOTEST, TS_BEFORE_UNDO, TS_AFTER_UNDO} test_seek = TS_NOTEST;
static int g_buffered;
static int g_update;
static int g_ungetc_first;
static int g_binary;
static int g_read_method;


static void fail (int n)
{
  fprintf (stderr, "Failure, case %d.%d.%d.%d.%d.%d\n",
           g_buffered, g_update, g_ungetc_first, g_binary, g_read_method, n);
  exit (1);
}


static void check_char (int expected, int n)
{
  int c;
  char buf;

  switch (g_read_method)
    {
    case 0:
      c = fgetc (f);
      break;
    case 1:
      c = getc (f);
      break;
    case 2:
      if (fread (&buf, 1, 1, f) == 1)
        c = (unsigned char)buf;
      else
        c = EOF;
      break;
    default:
      abort ();
    }
  if (c != expected)
    fail (n);
}


static void check_pos (long expected, int n)
{
  long pos;

  pos = ftell (f);
  if (pos != expected) fail (n);
}


static void check_ungetc (int c, int n)
{
  int r;

  r = ungetc (c, f);
  if (r != c) fail (n);
}


static void ref_char (int n)
{
  int c;

  if (r->sp > 0)
    c = r->pushback[--r->sp];
  else if (r->loc < r->size)
    c = r->contents[r->loc++];
  else
    c = EOF;
  check_char (c, n);
}


static long ref_pos (int n)
{
  long pos;

  if ((r->binary || r->sp == 0) && r->loc >= r->sp)
    {
      pos = r->loc - r->sp;
      if (pos >= 0 && pos <= r->size)
        pos = r->positions[pos];
      else
        pos = r->positions[r->size] + pos - r->size;
      if (pos >= 0)
        check_pos (pos, n);
      return pos;
    }
  else
    return -1;
}


static void ref_ungetc (int c, int n)
{
  int ret;

  if (c == EOF)
    ret = EOF;
  else if (r->sp >= r->stack_size)
    ret = EOF;
  else
    {
      r->pushback[r->sp++] = c;
      ret = (unsigned char)c;
    }
  check_ungetc (ret, n);
}


static void ref_seek (long offset, int origin, int n)
{
  long pos;

  switch (origin)
    {
    case SEEK_SET:
      pos = 0;
      break;
    case SEEK_CUR:
      if (test_seek == TS_BEFORE_UNDO)
        {
          if (r->loc < r->sp)
            return;
          pos = r->loc - r->sp;
        }
      else
        pos = r->loc;
      break;
    case SEEK_END:
      pos = r->size;
      break;
    default:
      abort ();
    }
  pos += offset;
  if (pos >= 0 && pos < r->size)
    {
      r->loc = pos;
      r->sp = 0;
      if (fseek (f, offset, origin) != 0)
        fail (n);
    }
}


static void ref_flush (void)
{
  r->sp = 0;
}


static void test (int buffered, int update, int ungetc_first, int binary,
                  int read_method)
{
  int c, i, j, k, m, max_ungetc;
  long pos;
  const char *mode;

  g_buffered     = buffered;
  g_update       = update;
  g_ungetc_first = ungetc_first;
  g_binary       = binary;
  g_read_method  = read_method;

  max_ungetc = (buffered ? max_ungetc_buffered : max_ungetc_unbuffered);

  if (update)
    mode = (binary ? "r+b" : "r+");
  else
    mode = (binary ? "rb" : "r");

  f = fopen ("ungetc.inp", mode);
  if (f == NULL)
    {
      perror ("ungetc.inp");
      exit (2);
    }
  if (!buffered)
    setvbuf (f, NULL, _IONBF, 0);
  else if (buffer_size > 0)
    setvbuf (f, NULL, _IOFBF, buffer_size);

  check_pos (0, 1);

  if (ungetc_first)
    {
      check_ungetc ('A', 2);

      /* Note: The result of ftell() is undefined now, even in binary
         mode. */

      check_char ('A', 3);
      check_pos (0, 4);
    }

  check_char ('a', 5);
  check_pos (1, 6);

  check_char ('b', 7);
  check_pos (2, 8);

  check_ungetc ('B', 9);

  if (binary)
    check_pos (1, 10);

  check_char ('B', 11);
  check_pos (2, 12);

  check_char ('c', 13);
  check_pos (3, 14);

  check_ungetc (EOF, 15);
  check_char ('d', 16);
  check_char ('e', 17);
  check_char ('f', 18);
  check_char ('g', 19);
  check_char ('h', 20);
  check_char ('i', 21);

  /* -------------------- Test fflush() -------------------- */

  if (fseek (f, 12, SEEK_SET) != 0) fail (100);
  check_pos (12, 101);

  check_char ('m', 102);
  check_pos (13, 103);

  check_ungetc ('C', 104);

  if (binary)
    check_pos (12, 105);

  if (fflush (f) != 0) fail (106);

  check_pos (13, 107);

  check_ungetc ('D', 108);
  check_char ('D', 109);
  check_pos (13, 110);

  check_char ('n', 111);
  check_pos (14, 112);

  /* Check undoing ungetc() if ungetc() was called on empty buffer. */

  if (fflush (f) != 0) fail (113);
  check_ungetc ('O', 114);
  if (fflush (f) != 0) fail (115);
  check_pos (14, 116);
  check_char ('o', 117);

  /* -------------------- Test fseek() -------------------- */

  if (fseek (f, 6, SEEK_SET) != 0) fail (200);
  check_pos (6, 201);

  check_char ('g', 202);
  check_pos (7, 203);

  check_ungetc ('E', 204);

  if (binary)
    check_pos (6, 205);

  if (fseek (f, 7, SEEK_SET) != 0) fail (206);
  check_pos (7, 207);

  check_char ('h', 208);
  check_pos (8, 209);

  /* Check that characters overwritten by ungetc() are reread from the
     underlying file (if the implementation of ungetc() overwrites at
     all). */

  check_ungetc ('F', 210);
  if (fseek (f, 7, SEEK_SET) != 0) fail (211);
  check_char ('h', 212);
  check_pos (8, 213);
  check_char ('i', 214);
  check_pos (9, 215);

  /* Check ungetc() at the beginning of the buffer. */

  if (fseek (f, 0, SEEK_SET) != 0) fail (216);
  check_ungetc ('P', 217);
  check_char ('P', 218);
  check_pos (0, 219);
  check_char ('a', 220);

  /* Prepare for the next test. */

  fseek (f, 8, SEEK_SET);
  check_char ('i', 220);

  /* -------------------- Test newlines ------------------- */

  /* Check that the file position indicator is not confused by
     overwriting a character with a newline character (if the
     implementation of ungetc() overwrites at all). */

  check_ungetc ('\n', 300);
  check_char ('\n', 301);
  check_pos (9, 302);
  check_char ('j', 303);

  /* Check that the file position indicator is not confused by
     overwriting a newline character (if the implementation of
     ungetc() overwrites at all). */

  do
    {
      c = fgetc (f);
    } while (c != EOF && c != '\n');
  if (c == EOF) fail (304);
  pos = ftell (f);
  check_ungetc ('G', 305);
  check_char ('G', 306);
  check_pos (pos, 307);

  /* -------------------- Test at eof ------------------- */

  do
    {
      c = fgetc (f);
    } while (c != EOF);
  pos = ftell (f);
  check_ungetc ('G', 400);
  check_char ('G', 401);
  check_pos (pos, 402);
  check_char (EOF, 403);

  /* -------------- Test maximum number of ungetc() -------------- */

  if (max_ungetc != 0)
    {
      /* Perform this test with an empty buffer first. */

      fflush (f);
      fseek (f, 0, SEEK_SET);
      i = 0;
      for (;;)
        {
          c = ungetc (128 + (i & 127), f);
          if (c != 128 + (i & 127))
            break;
          ++i;
        }
      for (j = i - 1; j >= 0; --j)
        check_char (128 + (j & 127), 500);
      if (i != max_ungetc) fail (501);
      check_char ('a', 502);

      /* And now with a non-empty buffer. */

      fflush (f);
      fseek (f, 8, SEEK_SET);
      check_char ('i', 503);
      i = 0;
      for (;;)
        {
          c = ungetc (128 + (i & 127), f);
          if (c != 128 + (i & 127))
            break;
          ++i;
        }
      for (j = i - 1; j >= 0; --j)
        check_char (128 + (j & 127), 504);
      if (i != max_ungetc) fail (505);
      check_char ('j', 506);
      check_pos (10, 507);
    }

  /* -------------------- Test multiple ungetc() calls -------------------- */

  if (max_ungetc >= 2)
    {
      fseek (f, 20, SEEK_SET);
      check_char ('u', 600);
      check_ungetc ('H', 601);
      if (binary) check_pos (20, 602);
      check_ungetc ('I', 603);
      if (binary) check_pos (19, 604);
      check_char ('I', 605);
      if (binary) check_pos (20, 606);
      check_ungetc ('J', 607);
      if (binary) check_pos (19, 608);
      check_char ('J', 609);
      if (binary) check_pos (20, 610);
      check_char ('H', 611);
      if (binary) check_pos (21, 612);
      check_char ('v', 613);
      check_pos (22, 608);
    }

  /* ------------ Test newlines with multiple ungetc() calls ----------- */

  if (max_ungetc >= 2)
    {
      /* Check that the file position indicator is not confused by
         overwriting a character with a newline character (if the
         implementation of ungetc() overwrites at all). */

      fseek (f, 9, SEEK_SET);

      check_ungetc ('\n', 701);
      check_ungetc ('K', 702);
      check_char ('K', 703);
      check_char ('\n', 704);
      check_pos (9, 705);
      check_char ('j', 706);

      /* Check overwritten characters (if the implementation of
         ungetc() overwrites at all). */

      fseek (f, 7, SEEK_SET);
      check_char ('h', 707);
      check_char ('i', 708);
      check_char ('j', 709);

      /* Check that the file position indicator is not confused by
         overwriting a newline character (if the implementation of
         ungetc() overwrites at all). */

      do
        {
          c = fgetc (f);
        } while (c != EOF && c != '\n');
      if (c == EOF) fail (710);
      pos = ftell (f);
      check_ungetc ('L', 711);
      check_ungetc ('M', 712);
      check_char ('M', 713);
      check_char ('L', 714);
      check_pos (pos, 715);
    }

  /* -------------------- Test fseek (..., SEEK_CUR) -------------------- */

  if (binary && test_seek != TS_NOTEST)
    {
      fseek (f, 10, SEEK_SET);
      check_char ('k', 800);
      check_pos (11, 801);
      fseek (f, -1, SEEK_CUR);
      check_pos (10, 802);
      check_char ('k', 803);
      check_pos (11, 804);
      check_ungetc ('N', 805);
      if (fseek (f, 0, SEEK_CUR) != 0) fail (806);
      check_pos (test_seek == TS_BEFORE_UNDO ? 10 : 11, 807);
    }

  /* -------------------- random operations -------------------- */

  if (random_loops > 0)
    {
      struct ref r0;
      int op;

      r = &r0;
      r->size = 0;
      r->loc = 0;
      r->stack_size = max_ungetc;
      if (r->stack_size == 0)
        r->stack_size = 1;
      r->pushback = malloc (r->stack_size);
      r->contents = NULL;
      r->positions = NULL;
      r->sp = 0;
      r->binary = binary;

      rewind (f);
      for (;;)
        {
          pos = ftell (f);
          c = fgetc (f);
          if (c == EOF)
            break;
          ++r->size;
          r->contents = realloc (r->contents, r->size);
          r->positions = realloc (r->positions, r->size * sizeof (long));
          r->contents[r->size-1] = (char)c;
          r->positions[r->size-1] = pos;
        }
      r->positions = realloc (r->positions, (r->size + 1) * sizeof (long));
      r->positions[r->size] = ftell (f);
      rewind (f);

      j = 1000; pos = -1;
      for (i = 0; i < random_loops; ++i)
        {
          op = rand () & 7;
          switch (op)
            {
            case 0:
              ref_flush ();
              fflush (f);
              break;
            case 1:
            case 5:
            case 6:
              ref_char (j++);
              ref_pos (j++);
              break;
            case 2:
            case 3:
            case 7:
              if (max_ungetc != 0 || r->sp == 0)
                {
                  k = rand () & 0xff;
                  ref_ungetc (k, j++);
                  ref_pos (j++);
                }
              break;
            case 4:
              if (binary)
                {
                  k = rand () & 3;
                  m = rand () % r->size;
                  switch (k)
                    {
                    case 0:
                      ref_seek (m, SEEK_SET, j++);
                      break;
                    case 1:
                      if (test_seek != TS_NOTEST)
                        ref_seek (m, SEEK_CUR, j++);
                      break;
                    case 2:
                      if (test_seek != TS_NOTEST)
                        ref_seek (-m, SEEK_CUR, j++);
                      break;
                    case 3:
                      ref_seek (-m, SEEK_END, j++);
                      break;
                    }
                  ref_pos (j++);
                }
              else if (pos >= 0)
                {
                  ref_seek (pos, SEEK_SET, j++);
                  ref_pos (j++);
                }
              break;
            }
        }
    }

  fclose (f);

  /* -------------------- pipe -------------------- */

#ifdef __EMX__

  if (_osmode == OS2_MODE)
    {
      f = popen ("type ungetc.inp", (binary ? "rb" : "r"));
      if (f == NULL)
        {
          perror ("type ungetc.inp");
          exit (2);
        }
      if (!buffered)
        setvbuf (f, NULL, _IONBF, 0);
      else if (buffer_size > 0)
        setvbuf (f, NULL, _IOFBF, buffer_size);

      if (ungetc_first)
        {
          check_ungetc ('A', 900);
          if (max_ungetc > 1)
            {
              check_ungetc ('B', 901);
              check_char ('B', 902);
              check_ungetc ('C', 903);
              check_char ('C', 904);
            }
          check_char ('A', 905);
        }
      check_char ('a', 906);
      check_char ('b', 907);

      check_ungetc ('D', 908);
      if (max_ungetc > 1)
        {
          check_ungetc ('E', 909);
          check_char ('E', 910);
          check_ungetc ('F', 911);
          check_char ('F', 912);
        }
      check_char ('D', 913);
      check_char ('c', 914);

      if (max_ungetc != 0)
        {
          i = 0;
          for (;;)
            {
              c = ungetc (128 + (i & 127), f);
              if (c != 128 + (i & 127))
                break;
              ++i;
            }
          for (j = i - 1; j >= 0; --j)
            check_char (128 + (j & 127), 915);
          if (buffered)
            {
              if (i < 1 || i > max_ungetc) fail (917);
            }
          else
            {
              if (i != max_ungetc) fail (916);
            }
        }
      check_char ('d', 918);
      pclose (f);
    }

#endif

}


static void usage (void)
{
  puts ("Usage: ungetc [-v<size>] [-b<count>] [-u<count>] [-r<count>] [-sa] [-sb]\n");
  puts ("Options:");
  puts ("  -v<size>    Set buffer size to size");
  puts ("  -b<count>   Max number of successive ungetc() calls for buffered streams");
  puts ("  -u<count>   Max number of successive ungetc() calls for unbuffered streams");
  puts ("  -r<count>   Number of random loops");
  puts ("  -sa         fseek(...,SEEK_CUR) uses current position after undoing ungetc()");
  puts ("  -sb         fseek(...,SEEK_CUR) uses current position before undoing ungetc()");
  exit (1);
}


int main (int argc, char *argv[])
{
  int i1, i2, i3, i4, i5;
  int i;

  i = 1;
  while (i < argc)
    if (strcmp (argv[i], "-sa") == 0)
      test_seek = TS_AFTER_UNDO, ++i;
    else if (strcmp (argv[i], "-sb") == 0)
      test_seek = TS_BEFORE_UNDO, ++i;
    else if (strncmp (argv[i], "-v", 2) == 0)
      {
        buffer_size = atoi (argv[i]+2);
        if (buffer_size < 1)
          usage ();
        ++i;
      }
    else if (strncmp (argv[i], "-b", 2) == 0)
      {
        max_ungetc_buffered = atoi (argv[i]+2);
        if (max_ungetc_buffered < 1)
          usage ();
        ++i;
      }
    else if (strncmp (argv[i], "-u", 2) == 0)
      {
        max_ungetc_unbuffered = atoi (argv[i]+2);
        if (max_ungetc_unbuffered < 1)
          usage ();
        ++i;
      }
    else if (strncmp (argv[i], "-r", 2) == 0)
      {
        random_loops = atoi (argv[i]+2);
        if (random_loops < 1)
          usage ();
        ++i;
      }
    else
      usage ();

  for (i1 = 0; i1 <= 1; ++i1)
    for (i2 = 0; i2 <= 1; ++i2)
      for (i3 = 0; i3 <= 1; ++i3)
        for (i4 = 0; i4 <= 1; ++i4)
          for (i5 = 0; i5 <= 2; ++i5)
            test (i1, i2, i3, i4, i5);
  return 0;
}
