/*
 *  A sequence of test procedures for this JBIG implementation
 * 
 *  Run this test sequence after each modification on the JBIG library.
 *
 *  Markus Kuhn -- http://www.cl.cam.ac.uk/~mgk25/
 */

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

#include "jbig.h"

#define TESTBUF_SIZE 400000L
#define TESTPIC_SIZE 477995L

#define FAILED "F\bFA\bAI\bIL\bLE\bED\bD"
#define PASSED "PASSED"

unsigned char *testbuf;
unsigned char *testpic;

long testbuf_len;


static void *checkedmalloc(size_t n)
{
  void *p;
  
  if ((p = malloc(n)) == NULL) {
    fprintf(stderr, "Sorry, not enough memory available!\n");
    exit(1);
  }
  
  return p;
}

static void testbuf_write(int v, void *dummy)
{
  if (testbuf_len < TESTBUF_SIZE)
    testbuf[testbuf_len++] = v;
  (void) dummy;
  return;
}


static void testbuf_writel(unsigned char *start, size_t len, void *dummy)
{
  if (testbuf_len < TESTBUF_SIZE) {
    if (testbuf_len + len < TESTBUF_SIZE)
      memcpy(testbuf + testbuf_len, start, len);
    else
      memcpy(testbuf + testbuf_len, start, TESTBUF_SIZE - testbuf_len);
  }
  testbuf_len += len;

#ifdef DEBUG
  {
    unsigned char *p;
    unsigned sum = 0;
    
    for (p = start; p - start < (ptrdiff_t) len; sum = (sum ^ *p++) << 1);
    printf("  testbuf_writel: %4lu bytes, checksum %04x\n",
	   (unsigned long) len, sum & 0xffff);
  }
#endif

  (void) dummy;
  return;
}


/*
 * Store the artificial test image defined in T.82, clause 7.2.1 at
 * pic. The image requires 477995 bytes of memory, is 1960 x 1951 pixels
 * large and has one plane.
 */ 
static void testimage(unsigned char *pic)
{
  unsigned long i, j, sum;
  unsigned int prsg, repeat[8];
  unsigned char *p;
  
  memset(pic, 0, TESTPIC_SIZE);
  p = pic;
  prsg = 1;
  for (j = 0; j < 1951; j++)
    for (i = 0; i < 1960; i++) {
      if (j >= 192) {
	if (j < 1023 || ((i >> 3) & 3) == 0) {
	  sum = (prsg & 1) + ((prsg >> 2) & 1) + ((prsg >> 11) & 1) +
	    ((prsg >> 15) & 1);
	  prsg = (prsg << 1) + (sum & 1);
	  if ((prsg & 3) == 0) {
	    *p |= 1 << (7 - (i & 7));
	    repeat[i & 7] = 1;
	  } else {
	    repeat[i & 7] = 0;
	  }
	} else {
	  if (repeat[i & 7])
	    *p |= 1 << (7 - (i & 7));
	}
      }
      if ((i & 7) == 7) ++p;
    }

  /* verify test image */
  sum = 0;
  for (i = 0; i < TESTPIC_SIZE; i++)
    for (j = 0; j < 8; j++)
      sum += (pic[i] >> j) & 1;
  if (sum != 861965L)
    printf("WARNING: Artificial test image has %lu (not 861965) "
	   "foreground pixels!\n", sum);

  return;
}
  

/*
 * Perform a full test cycle with one set of parameters. Encode an image
 * and compare the length of the result with correct_length. Then decode
 * the image again both in one single chunk or byte by byte and compare
 * the results with the original input image.
 */
static int test_cycle(unsigned char **orig_image, int width, int height,
		      int options, int order, int layers, int planes,
		      unsigned long l0, int mx, long correct_length,
		      const char *test_id)
{
  struct jbg_enc_state sje;
  struct jbg_dec_state sjd;
  int trouble = 0;
  long l;
  size_t plane_size;
  int i, result;
  unsigned char **image;

  plane_size = ((width + 7) / 8) * height;
  image = (unsigned char **) checkedmalloc(planes * sizeof(unsigned char *));
  for (i = 0; i < planes; i++) {
    image[i] = (unsigned char *) checkedmalloc(plane_size);
    memcpy(image[i], orig_image[i], plane_size);
  }

  printf("\nTest %s.1: Encoding ...\n", test_id);
  testbuf_len = 0;
  jbg_enc_init(&sje, width, height, planes, image, testbuf_writel, NULL);
  jbg_enc_layers(&sje, layers);
  jbg_enc_options(&sje, order, options, l0, mx, 0);
  jbg_enc_out(&sje);
  jbg_enc_free(&sje);
  for (i = 0; i < planes; i++)
    free(image[i]);
  free(image);
  printf("Encoded BIE has %6ld bytes: ", testbuf_len);
  if (correct_length >= 0)
    if (testbuf_len == correct_length)
      puts(PASSED);
    else {
      trouble++;
      printf(FAILED ", correct would have been %ld\n", correct_length);
    }
  else
    puts("");
  
  printf("Test %s.2: Decoding whole chunk ...\n", test_id);
  jbg_dec_init(&sjd);
  result = jbg_dec_in(&sjd, testbuf, testbuf_len, NULL);
  if (result != JBG_EOK) {
    printf("Decoder complained with return value %d: " FAILED "\n"
	   "Cause: '%s'\n", result, jbg_strerror(result));
    trouble++;
  } else {
    printf("Image comparison: ");
    result = 1;
    for (i = 0; i < planes; i++) {
      if (memcmp(orig_image[i], sjd.lhp[layers & 1][i],
		 ((width + 7) / 8) * height)) {
	result = 0;
	trouble++;
	printf(FAILED " for plane %d\n", i);
      }
    }
    if (result)
      puts(PASSED);
  }
  jbg_dec_free(&sjd);

  printf("Test %s.3: Decoding with single-byte feed ...\n", test_id);
  jbg_dec_init(&sjd);
  result = JBG_EAGAIN;
  for (l = 0; l < testbuf_len; l++) {
    result = jbg_dec_in(&sjd, testbuf + l, 1, NULL);
    if (l < testbuf_len - 1 && result != JBG_EAGAIN) {
      printf("Decoder complained with return value %d at byte %ld: " FAILED
	     "\nCause: '%s'\n", result, l, jbg_strerror(result));
      trouble++;
      break;
    }
  }
  if (l == testbuf_len) {
    if (result != JBG_EOK) {
      printf("Decoder complained with return value %d at final byte: " FAILED
	     "\nCause: '%s'\n", result, jbg_strerror(result));
      trouble++;
    } else {
      printf("Image comparison: ");
      result = 1;
      for (i = 0; i < planes; i++) {
	if (memcmp(orig_image[i], sjd.lhp[layers & 1][i],
		   ((width + 7) / 8) * height)) {
	  result = 0;
	  trouble++;
	  printf(FAILED " for plane %d\n", i);
	}
      }
      if (result)
	puts(PASSED);
    }
  }

  jbg_dec_free(&sjd);
  puts("");
  
  return trouble != 0;
}


int main(int argc, char **argv)
{
  int trouble, problems = 0;
  struct jbg_arenc_state *se;
  struct jbg_ardec_state *sd;
  long i;
  int pix, order, layers;
  char test[10];
  size_t st;
  unsigned char *pp;
  unsigned char *ppp[4];

  int t82pix[16] = {
    0x05e0, 0x0000, 0x8b00, 0x01c4, 0x1700, 0x0034, 0x7fff, 0x1a3f,
    0x951b, 0x05d8, 0x1d17, 0xe770, 0x0000, 0x0000, 0x0656, 0x0e6a
  };
  int t82cx[16] = {
    0x0fe0, 0x0000, 0x0f00, 0x00f0, 0xff00, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
  };
  unsigned char t82sde[32] = {
    0x69, 0x89, 0x99, 0x5c, 0x32, 0xea, 0xfa, 0xa0,
    0xd5, 0xff, 0x00, 0x52, 0x7f, 0xff, 0x00, 0xff,
    0x00, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x3f,
    0xff, 0x00, 0x2d, 0x20, 0x82, 0x91, 0xff, 0x02
  };

  /* three 23x5 pixel test images with the letters JBIG */
  unsigned char jbig_normal[15*4] = {
    0x7c, 0xe2, 0x38, 0x04, 0x92, 0x40, 0x04, 0xe2, 0x5c, 0x44,
    0x92, 0x44, 0x38, 0xe2, 0x38,
    0x7c, 0xe2, 0x38, 0x04, 0x92, 0x40, 0x04, 0xe2, 0x5c, 0x44,
    0x92, 0x44, 0x38, 0xe2, 0x38,
    0x7c, 0xe2, 0x38, 0x04, 0x92, 0x40, 0x04, 0xe2, 0x5c, 0x44,
    0x92, 0x44, 0x38, 0xe2, 0x38,
    0x7c, 0xe2, 0x38, 0x04, 0x92, 0x40, 0x04, 0xe2, 0x5c, 0x44,
    0x92, 0x44, 0x38, 0xe2, 0x38
  };
  unsigned char jbig_upsidedown[15*4] = {
    0x38, 0xe2, 0x38, 0x44, 0x92, 0x44, 0x04, 0xe2, 0x5c, 0x04,
    0x92, 0x40, 0x7c, 0xe2, 0x38,
    0x38, 0xe2, 0x38, 0x44, 0x92, 0x44, 0x04, 0xe2, 0x5c, 0x04,
    0x92, 0x40, 0x7c, 0xe2, 0x38,
    0x38, 0xe2, 0x38, 0x44, 0x92, 0x44, 0x04, 0xe2, 0x5c, 0x04,
    0x92, 0x40, 0x7c, 0xe2, 0x38,
    0x38, 0xe2, 0x38, 0x44, 0x92, 0x44, 0x04, 0xe2, 0x5c, 0x04,
    0x92, 0x40, 0x7c, 0xe2, 0x38
  };
  unsigned char jbig_inverse[15*4] = {
    0xff^0x7c, 0xff^0xe2, 0xfe^0x38, 0xff^0x04, 0xff^0x92,
    0xfe^0x40, 0xff^0x04, 0xff^0xe2, 0xfe^0x5c, 0xff^0x44,
    0xff^0x92, 0xfe^0x44, 0xff^0x38, 0xff^0xe2, 0xfe^0x38,
    0xff^0x7c, 0xff^0xe2, 0xfe^0x38, 0xff^0x04, 0xff^0x92,
    0xfe^0x40, 0xff^0x04, 0xff^0xe2, 0xfe^0x5c, 0xff^0x44,
    0xff^0x92, 0xfe^0x44, 0xff^0x38, 0xff^0xe2, 0xfe^0x38,
    0xff^0x7c, 0xff^0xe2, 0xfe^0x38, 0xff^0x04, 0xff^0x92,
    0xfe^0x40, 0xff^0x04, 0xff^0xe2, 0xfe^0x5c, 0xff^0x44,
    0xff^0x92, 0xfe^0x44, 0xff^0x38, 0xff^0xe2, 0xfe^0x38,
    0xff^0x7c, 0xff^0xe2, 0xfe^0x38, 0xff^0x04, 0xff^0x92,
    0xfe^0x40, 0xff^0x04, 0xff^0xe2, 0xfe^0x5c, 0xff^0x44,
    0xff^0x92, 0xfe^0x44, 0xff^0x38, 0xff^0xe2, 0xfe^0x38
  };
  int orders[] = {
    0,
    JBG_ILEAVE,
    JBG_ILEAVE | JBG_SMID,
#if 0
    JBG_SEQ,
    JBG_SEQ | JBG_SMID,
    JBG_SEQ | JBG_ILEAVE,
    JBG_HITOLO,
    JBG_HITOLO | JBG_ILEAVE,
    JBG_HITOLO | JBG_ILEAVE | JBG_SMID,
    JBG_HITOLO | JBG_SEQ,
    JBG_HITOLO | JBG_SEQ | JBG_SMID,
    JBG_HITOLO | JBG_SEQ | JBG_ILEAVE
#endif
  };

  printf("\nAutomatic JBIG Compatibility Test Suite\n"
	 "---------------------------------------\n\n"
	 "JBIG-KIT Version " JBG_VERSION
	 " -- This test may take a few minutes.\n\n\n");

  /* allocate test buffer memory */
  testbuf = (unsigned char *) checkedmalloc(TESTBUF_SIZE);
  testpic = (unsigned char *) checkedmalloc(TESTPIC_SIZE);
  se = (struct jbg_arenc_state *) checkedmalloc(sizeof(struct jbg_arenc_state));
  sd = (struct jbg_ardec_state *) checkedmalloc(sizeof(struct jbg_ardec_state));

  /* test a few properties of the machine architecture */
  testbuf[0] = 42;
  testbuf[0x10000L] = 0x42;
  st = 1 << 16;
  testbuf[st]++;
  pp = testbuf + 0x4000;
  pp += 0x4000;
  pp += 0x4000;
  pp += 0x4000;
  if (testbuf[0] != 42 || *pp != 0x43) {
    printf("Porting error detected:\n\n"
	   "Pointer arithmetic with this compiler has not at least 32 bits!\n"
	   "Are you sure, you have not compiled this program on an 8-bit\n"
	   "or 16-bit architecture? This compiler mode can obviously not\n"
           "handle arrays with a size of more than 65536 bytes. With this\n"
           "memory model, JBIG-KIT can only handle very small images and\n"
           "not even this compatibility test suite will run. :-(\n\n");
    exit(1);
  }

  /* only supported command line option:
   * output file name for exporting test image */
  if (argc > 1) {
    FILE *f;

    puts("Generating test image ...");
    testimage(testpic);
    printf("Storing in '%s' ...\n", argv[1]);
    
    /* write out test image as PBM file */
    f = fopen(argv[1], "wb");
    if (!f) abort();
    fprintf(f, "P4\n");
#if 0
    fprintf(f, "# Test image as defined in ITU-T T.82, clause 7.2.1\n");
#endif
    fprintf(f, "%10lu\n%10lu\n", 1960LU, 1951LU);
    fwrite(testpic, 1, TESTPIC_SIZE, f);
    fclose(f);
    exit(0);
  }

#if 1
  puts("1) Arithmetic encoder test sequence from ITU-T T.82, clause 7.1\n"
       "---------------------------------------------------------------\n");
  arith_encode_init(se, 0);
  testbuf_len = 0;
  se->byte_out = testbuf_write;
  for (i = 0; i < 16 * 16; i++)
    arith_encode(se, (t82cx[i >> 4] >> ((15 - i) & 15)) & 1,
		 (t82pix[i >> 4] >> ((15 - i) & 15)) & 1);
  arith_encode_flush(se);
  printf("result of encoder:\n  ");
  for (i = 0; i < testbuf_len && i < TESTBUF_SIZE; i++)
    printf("%02x", testbuf[i]);
  printf("\nexpected result:\n  ");
  for (i = 0; i < 30; i++)
    printf("%02x", t82sde[i]);
  printf("\n\nTest 1: ");
  if (testbuf_len != 30 || memcmp(testbuf, t82sde, 30)) {
    problems++;
    printf(FAILED);
  } else
    printf(PASSED);
  printf("\n\n");


  puts("2) Arithmetic decoder test sequence from ITU-T T.82, clause 7.1\n"
       "---------------------------------------------------------------\n");
  printf("Test 2.1: Decoding whole chunk ...\n");
  arith_decode_init(sd, 0);
  sd->pscd_ptr = t82sde;
  sd->pscd_end = t82sde + 32;
  trouble = 0;
  for (i = 0; i < 16 * 16 && !trouble; i++) {
    pix = arith_decode(sd, (t82cx[i >> 4] >> ((15 - i) & 15)) & 1);
    if (pix < 0) {
      printf("Problem at pixel %ld, byte %ld.\n\n",
	     i+1, (long) (sd->pscd_ptr - sd->pscd_end));
      trouble++;
      break;
    }
    if (pix != ((t82pix[i >> 4] >> ((15 - i) & 15)) & 1)) {
      printf("Wrong PIX answer (%d) at pixel %ld.\n\n", pix, i+1);
      trouble++;
      break;
    }
  }
  if (!trouble && sd->pscd_ptr != sd->pscd_end - 2) {
    printf("%ld bytes left after decoder finished.\n\n",
	   (long) (sd->pscd_end - sd->pscd_ptr - 2));
    trouble++;
  }
  printf("Test result: ");
  if (trouble) {
    problems++;
    puts(FAILED);
  } else
    puts(PASSED);
  printf("\n");

  printf("Test 2.2: Decoding with single byte feed ...\n");
  arith_decode_init(sd, 0);
  pp = t82sde;
  sd->pscd_ptr = pp;
  sd->pscd_end = pp + 1;
  trouble = 0;
  for (i = 0; i < 16 * 16 && !trouble; i++) {
    pix = arith_decode(sd, (t82cx[i >> 4] >> ((15 - i) & 15)) & 1);
    while (pix < 0 && sd->pscd_end < t82sde + 32) {
      pp++;
      if (sd->pscd_ptr != pp - 1)
	sd->pscd_ptr = pp;
      sd->pscd_end = pp + 1;
      pix = arith_decode(sd, (t82cx[i >> 4] >> ((15 - i) & 15)) & 1);
    }
    if (pix < 0) {
      printf("Problem at pixel %ld, byte %ld.\n\n",
	     i+1, (long) (sd->pscd_ptr - sd->pscd_end));
      trouble++;
      break;
    }
    if (pix != ((t82pix[i >> 4] >> ((15 - i) & 15)) & 1)) {
      printf("Wrong PIX answer (%d) at pixel %ld.\n\n", pix, i+1);
      trouble++;
      break;
    }
  }
  if (!trouble && sd->pscd_ptr != sd->pscd_end - 2) {
    printf("%ld bytes left after decoder finished.\n\n",
	   (long) (sd->pscd_end - sd->pscd_ptr - 2));
    trouble++;
  }
  printf("Test result: ");
  if (trouble) {
    problems++;
    puts(FAILED);
  } else
    puts(PASSED);
  printf("\n");
  
  puts("3) Parametric algorithm test sequence from ITU-T T.82, clause 7.2\n"
       "-----------------------------------------------------------------\n");
  puts("Generating test image ...");
  testimage(testpic);
  putchar('\n');
  pp = testpic;

  puts("Test 3.1: TPBON=0, Mx=0, LRLTWO=0, L0=1951, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_DELAY_AT,
			 0, 0, 1, 1951, 0, 317384L, "3.1");
  puts("Test 3.2: TPBON=0, Mx=0, LRLTWO=1, L0=1951, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_DELAY_AT | JBG_LRLTWO,
			 0, 0, 1, 1951, 0, 317132L, "3.2");
  puts("Test 3.3: TPBON=1, Mx=8, LRLTWO=0, L0=128, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_DELAY_AT | JBG_TPBON,
			 0, 0, 1, 128, 8, 253653L, "3.3");
  puts("Test 3.4: TPBON=1, DPON=1, TPDON=1, Mx=8, LRLTWO=0, L0=2, 6 layers");
  problems += test_cycle(&pp, 1960, 1951,
			 JBG_DELAY_AT | JBG_TPBON | JBG_TPDON | JBG_DPON,
			 0, 6, 1, 2, 8, 279314L, "3.4");
  puts("Test 3.5: as Test 3.4 but with DPPRIV=1");
  problems += test_cycle(&pp, 1960, 1951,
			 JBG_DELAY_AT | JBG_TPBON | JBG_TPDON | JBG_DPON |
			 JBG_DPPRIV,
			 0, 6, 1, 2, 8, 279314L + 1728, "3.5");
#if 0 /* Note: option SEQ is currently not supported by the decoder */
  puts("Test 3.6: as Test 3.4 but with order bit SEQ set");
  problems += test_cycle(&pp, 1960, 1951,
			 JBG_DELAY_AT | JBG_TPBON | JBG_TPDON | JBG_DPON,
			 JBG_SEQ, 6, 1, 2, 8, 279314L, "3.6");
#endif
#endif

  puts("4) Same T.82 tests with SDRST instead of SDNORM\n"
       "-----------------------------------------------\n");

  puts("Test 4.0: TPBON=1, Mx=8, LRLTWO=0, L0=128, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_SDRST | JBG_TPBON,
			 0, 0, 1, 128, 8, -1, "4.0");

  puts("Test 4.1: TPBON=0, Mx=0, LRLTWO=0, L0=1951, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_SDRST,
			 0, 0, 1, 1951, 0, -1, "4.1");
  puts("Test 4.2: TPBON=0, Mx=0, LRLTWO=1, L0=1951, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_LRLTWO | JBG_SDRST,
			 0, 0, 1, 1951, 0, -1, "4.2");
  puts("Test 4.3: TPBON=1, Mx=8, LRLTWO=0, L0=128, 0 layers");
  problems += test_cycle(&pp, 1960, 1951, JBG_TPBON | JBG_SDRST,
			 0, 0, 1, 128, 8, -1, "4.3");
  puts("Test 4.4: TPBON=1, DPON=1, TPDON=1, Mx=8, LRLTWO=0, L0=2, 6 layers");
  problems += test_cycle(&pp, 1960, 1951,
			 JBG_TPBON | JBG_TPDON |
			 JBG_DPON | JBG_SDRST,
			 0, 6, 1, 2, 8, -1, "4.4");

  puts("5) Small test image, 0-3 layers, 4 planes, different orders\n"
       "-----------------------------------------------------------\n");
  
  /* test a simple multi-plane image */
  ppp[0] = jbig_normal;
  ppp[1] = jbig_upsidedown;
  ppp[2] = jbig_inverse;
  ppp[3] = jbig_inverse;

  i = 0;
  for (layers = 0; layers <= 3; layers++)
    for (order = 0; order < (int) (sizeof(orders)/sizeof(int)); order++) {
      sprintf(test, "5.%ld", ++i);
      printf("Test %s: order=%d, %d layers, 4 planes", test, orders[order],
	     layers);
      problems += test_cycle(ppp, 23, 5*4, JBG_TPBON | JBG_TPDON | JBG_DPON,
			     orders[order], layers, 4, 2, 8, -1, test);
    }


  printf("\nTest result summary: the library has %s the test suite.\n\n",
	 problems ? FAILED : PASSED);
  if (problems)
    puts("This is bad. If you cannot identify the problem yourself, please "
	 "send\nthis output plus a detailed description of your "
	 "compile environment\n(OS, compiler, version, options, etc.) to "
	 "Markus Kuhn\n<http://www.cl.cam.ac.uk/~mgk25/>.");
  else
    puts("Congratulations, everything is fine.\n");

  return problems != 0;
}
