/*
  csetu.c  --  character-set utility program.

  Contains three general-purpose routines, plus a main program to demonstrate
  their use.

    invert():   Given a translation table, compute its inverse.
    compose():  Given two translation tables, compute their composition.
    display():  Print a translation table in the desired radix.

  F. da Cruz, Columbia University, January 1992
*/
#include <stdio.h>

char **xargv;				/* Global copy of argv */

/* IBM CP437 to CP850, from CDRA */
int
x4385[] = {
  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
 16,  17,  18,  19, 244, 245,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
 32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
 48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
 64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
 80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
 96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 189, 156, 190, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 155, 157, 191,
192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
240, 241, 242, 243,  20,  21, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
};

/* IBM CP850 to Latin Alphabet 1, from CDRA */
int
x85l1[] = {
  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
 16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  28,  27, 127,  29,  30,  31,
 32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
 48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
 64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
 80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
 96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,  26,
199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, 239, 238, 236, 196, 197,
201, 230, 198, 244, 246, 242, 251, 249, 255, 214, 220, 248, 163, 216, 215, 159,
225, 237, 243, 250, 241, 209, 170, 186, 191, 174, 172, 189, 188, 161, 171, 187,
155, 156, 157, 144, 151, 193, 194, 192, 169, 135, 128, 131, 133, 162, 165, 147,
148, 153, 152, 150, 145, 154, 227, 195, 132, 130, 137, 136, 134, 129, 138, 164,
240, 208, 202, 203, 200, 158, 205, 206, 207, 149, 146, 141, 140, 166, 204, 139,
211, 223, 212, 210, 245, 213, 181, 254, 222, 218, 219, 217, 253, 221, 175, 180,
173, 177, 143, 190, 182, 167, 247, 184, 176, 168, 183, 185, 179, 178, 142, 160
};

int xl185[256];				/* Latin-1 to CP850 (to be computed) */
int xl143[256];				/* Latin-1 to CP437 (to be computed) */
int x43l1[256];				/* CP437 to Latin-1 (to be computed) */

/*
  Compute the inverse of a 256-byte translation table, array c1.
  If the table is not invertible, print a message and exit.
  If it is invertible, put the invertible table in array c2.
*/
void
invert(c1, c2) int c1[], c2[]; {
    int i;				/* Loop index */
    int err = 0;			/* Error counter */

    for (i = 0; i < 256; i++)		/* Initialize the output array */
      c2[i] = -1;			/*  to all -1's. */

    for (i = 0; i < 256; i++) {		/* Compute the inverse. */
	if (c1[i] < 0 || c1[i] > 255) {	/* Check for illegal input values */
	    fprintf(stderr,
		    "Table contains illegal value %d\n",c1[i]);
	    exit(1);
	} else if (c2[c1[i]] == -1) {	/* Check for duplicates. */
	    c2[c1[i]] = i & 0xff;	/* This one is ok. */
	} else {			/* Duplicate or missing values mean */
	    if (++err == 1) {		/* character set is not invertible. */
		fprintf(stderr,
			"Character set not invertible.\n");
		fprintf(stderr,
			"The following characters appear more than once:\n");
	    }
	    fprintf(stderr, " %3d\n", i);
	}
    }
    if (err > 0) {			/* Display missing entries, if any */
	fprintf(stderr,
		"The following characters are missing:\n");
	for (i = 0; i < 256; i++)
	  if (c2[i] == -1) fprintf(stderr," %3d\n", i);
	exit(1);
    }
}

/*
  Given c1, the translation table from character set X to character set Y,
  and c2, the translation table from character set Y to character set Z,
  compute c3, the translation table from character set X to character set Z,
  i.e. the composition of tables c1 and c2.
*/
void
compose(c1, c2, c3) int c1[], c2[], c3[]; {
    int i;
    for (i = 0; i < 256; i++) {		/* Check for illegal input values */
	if (c1[i] < 0 || c1[i] > 255 ||
	    c2[i] < 0 || c2[i] > 255) {
	    fprintf(stderr,"Table contains illegal value %d\n",c1[i]);
	    exit(1);
	} else c3[i] = c2[c1[i]];	/* Translate c1 -> c2 -> c3 */
    }
}

/*
  Display character set c in the given radix (8, 10, or 16),
  labeled with the given name.
*/
void
display(c,radix,name) int c[], radix; char *name; {
    int i, j, k;			/* Loop indices */
    char buf[10];			/* Output string buffer */
    printf("%s\n",name);		/* Print the table name */
    for (i = 0; i < 16; i++)		/* Print the table, each row */
      for (j = 0; j < 16; j++) {	/* and each element in the row */
	  k = 16 * i + j;		/* Table index of this item */
	  switch (radix) {		/* Use the desired radix */
	    case 8:  sprintf(buf,  "%3o", c[k]); break;	/* Octal */
	    case 10: sprintf(buf,  "%3d", c[k]); break;	/* Decimal */
	    case 16: sprintf(buf," %02X", c[k]); break;	/* Hexadecimal */
	    default:
	      fprintf(stderr,"display: bad radix\n");
	      exit(1);
	  }
	  if (j == 15)			/* Final item in row */
	    printf(k == 255 ?
		   "%s\n" :		/* Final item in table */
		   "%s,\n", buf);
	  else
	    printf("%s, ",buf);		/* Item within row */
      }
    printf("\n");			/* Blank line at end */
}

void					/* Usage message */
usage(s1,s2) char *s1, *s2; {
    fprintf(stderr,"%s: %s '%s'\n",xargv[0],s1,s2);
    fprintf(stderr,"usage: %s [ -radix ]\n",xargv[0]);    
    fprintf(stderr,"optional argument is the output radix\n");
    fprintf(stderr,"radix may be 8, 10, or 16, 10 by default\n");
    exit(1);
}

void
main(argc,argv) int argc; char *argv[]; {
    int i;				/* Worker */
    int r = 10;				/* Radix for output, default 10 */

    xargv = argv;			/* Make global copy */

    for (i = 1; i < argc; i++) {	/* Read radix from command line */
	if (*argv[i] != '-') usage("bad option",argv[i]);
	r = atoi(argv[i]+1);
	if (r != 8 && r != 10 && r != 16) usage("bad radix",argv[i]+1);
    }
    if ((i = sizeof(x4385)) != 256 * sizeof (int)) {
	fprintf(stderr,"size of x4385 = %d, should be 256\n",i);
	exit(1);
    }
    if ((i = sizeof(x85l1)) != 256 * sizeof (int)) {
	fprintf(stderr,"size of x85l1 = %d, should be 256\n",i);
	exit(1);
    }
    display(x4385,r,"CP437 to CP850");	/* These are given. */
    display(x85l1,r,"CP850 to Latin-1"); /* Print them. */

    invert(x85l1, xl185);		/* Compute Latin-1 to cp850 */
    display(xl185,r,"Latin-1 to CP850"); /* Print it. */

    compose(x4385, x85l1, x43l1);	/* Compute cp437-to-latin1 */
    display(x43l1,r,"CP437 to Latin-1"); /* Print it. */

    invert(x43l1, xl143);		/* Compute Latin1 to CP437 */
    display(xl143,r,"Latin-1 to CP437"); /* Print it. */
}
