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

#define SIZE(x) (sizeof(x)/sizeof((x)[0]))

static char *progname = "";
static FILE *input;

static unsigned int pgm_width;

#define CODE_STARTA 103
#define CODE_STARTB 104
#define CODE_STARTC 105
#define CODE_STOP   106
unsigned char codetab[][6] = {
	{ 2, 1, 2, 2, 2, 2 },	/* 00 */
	{ 2, 2, 2, 1, 2, 2 },
	{ 2, 2, 2, 2, 2, 1 },
	{ 1, 2, 1, 2, 2, 3 },
	{ 1, 2, 1, 3, 2, 2 },
	{ 1, 3, 1, 2, 2, 2 },
	{ 1, 2, 2, 2, 1, 3 },
	{ 1, 2, 2, 3, 1, 2 },
	{ 1, 3, 2, 2, 1, 2 },
	{ 2, 2, 1, 2, 1, 3 },
	{ 2, 2, 1, 3, 1, 2 },
	{ 2, 3, 1, 2, 1, 2 },
	{ 1, 1, 2, 2, 3, 2 },
	{ 1, 2, 2, 1, 3, 2 },
	{ 1, 2, 2, 2, 3, 1 },
	{ 1, 1, 3, 2, 2, 2 },
	{ 1, 2, 3, 1, 2, 2 },
	{ 1, 2, 3, 2, 2, 1 },
	{ 2, 2, 3, 2, 1, 1 },
	{ 2, 2, 1, 1, 3, 2 },
	{ 2, 2, 1, 2, 3, 1 },
	{ 2, 1, 3, 2, 1, 2 },
	{ 2, 2, 3, 1, 1, 2 },
	{ 3, 1, 2, 1, 3, 1 },
	{ 3, 1, 1, 2, 2, 2 },
	{ 3, 2, 1, 1, 2, 2 },
	{ 3, 2, 1, 2, 2, 1 },
	{ 3, 1, 2, 2, 1, 2 },
	{ 3, 2, 2, 1, 1, 2 },
	{ 3, 2, 2, 2, 1, 1 },
	{ 2, 1, 2, 1, 2, 3 },
	{ 2, 1, 2, 3, 2, 1 },
	{ 2, 3, 2, 1, 2, 1 },
	{ 1, 1, 1, 3, 2, 3 },
	{ 1, 3, 1, 1, 2, 3 },
	{ 1, 3, 1, 3, 2, 1 },
	{ 1, 1, 2, 3, 1, 3 },
	{ 1, 3, 2, 1, 1, 3 },
	{ 1, 3, 2, 3, 1, 1 },
	{ 2, 1, 1, 3, 1, 3 },
	{ 2, 3, 1, 1, 1, 3 },
	{ 2, 3, 1, 3, 1, 1 },
	{ 1, 1, 2, 1, 3, 3 },
	{ 1, 1, 2, 3, 3, 1 },
	{ 1, 3, 2, 1, 3, 1 },
	{ 1, 1, 3, 1, 2, 3 },
	{ 1, 1, 3, 3, 2, 1 },
	{ 1, 3, 3, 1, 2, 1 },
	{ 3, 1, 3, 1, 2, 1 },
	{ 2, 1, 1, 3, 3, 1 },
	{ 2, 3, 1, 1, 3, 1 },
	{ 2, 1, 3, 1, 1, 3 },
	{ 2, 1, 3, 3, 1, 1 },
	{ 2, 1, 3, 1, 3, 1 },
	{ 3, 1, 1, 1, 2, 3 },
	{ 3, 1, 1, 3, 2, 1 },
	{ 3, 3, 1, 1, 2, 1 },
	{ 3, 1, 2, 1, 1, 3 },
	{ 3, 1, 2, 3, 1, 1 },
	{ 3, 3, 2, 1, 1, 1 },
	{ 3, 1, 4, 1, 1, 1 },
	{ 2, 2, 1, 4, 1, 1 },
	{ 4, 3, 1, 1, 1, 1 },
	{ 1, 1, 1, 2, 2, 4 },
	{ 1, 1, 1, 4, 2, 2 },
	{ 1, 2, 1, 1, 2, 4 },
	{ 1, 2, 1, 4, 2, 1 },
	{ 1, 4, 1, 1, 2, 2 },
	{ 1, 4, 1, 2, 2, 1 },
	{ 1, 1, 2, 2, 1, 4 },
	{ 1, 1, 2, 4, 1, 2 },
	{ 1, 2, 2, 1, 1, 4 },
	{ 1, 2, 2, 4, 1, 1 },
	{ 1, 4, 2, 1, 1, 2 },
	{ 1, 4, 2, 2, 1, 1 },
	{ 2, 4, 1, 2, 1, 1 },
	{ 2, 2, 1, 1, 1, 4 },
	{ 4, 1, 3, 1, 1, 1 },
	{ 2, 4, 1, 1, 1, 2 },
	{ 1, 3, 4, 1, 1, 1 },
	{ 1, 1, 1, 2, 4, 2 },
	{ 1, 2, 1, 1, 4, 2 },
	{ 1, 2, 1, 2, 4, 1 },
	{ 1, 1, 4, 2, 1, 2 },
	{ 1, 2, 4, 1, 1, 2 },
	{ 1, 2, 4, 2, 1, 1 },
	{ 4, 1, 1, 2, 1, 2 },
	{ 4, 2, 1, 1, 1, 2 },
	{ 4, 2, 1, 2, 1, 1 },
	{ 2, 1, 2, 1, 4, 1 },
	{ 2, 1, 4, 1, 2, 1 },
	{ 4, 1, 2, 1, 2, 1 },
	{ 1, 1, 1, 1, 4, 3 },
	{ 1, 1, 1, 3, 4, 1 },
	{ 1, 3, 1, 1, 4, 1 },
	{ 1, 1, 4, 1, 1, 3 },
	{ 1, 1, 4, 3, 1, 1 },
	{ 4, 1, 1, 1, 1, 3 },
	{ 4, 1, 1, 3, 1, 1 },
	{ 1, 1, 3, 1, 4, 1 },	/* 99 */
	{ 1, 1, 4, 1, 3, 1 },	/* 100: code B */
	{ 3, 1, 1, 1, 4, 1 },	/* 101: code A */
	{ 4, 1, 1, 1, 3, 1 },	/* 102: FNC 1 */
	{ 2, 1, 1, 4, 1, 2 },	/* 103: START code A */
	{ 2, 1, 1, 2, 1, 4 },	/* 104: START code B */
	{ 2, 1, 1, 2, 3, 2 },	/* 105: START code C */
	{ 2, 3, 3, 1, 1, 1 } 	/* 106: STOP + 2 */
};		

char charsets[][2] = {
	{ ' ', ' ' },
	{ '!', '!' },
	{ '"', '"' },
	{ '#', '#' },
	{ '$', '$' },
	{ '%', '%' },
	{ '&', '&' },
	{ '\'', '\'' },
	{ '(', '(' },
	{ ')', ')' },
	{ '*', '*' },
	{ '+', '+' },
	{ ',', ',' },
	{ '-', '-' },
	{ '.', '.' },
	{ '/', '/' },
	{ '0', '0' },
	{ '1', '1' },
	{ '2', '2' },
	{ '3', '3' },
	{ '4', '4' },
	{ '5', '5' },
	{ '6', '6' },
	{ '7', '7' },
	{ '8', '8' },
	{ '9', '9' },
	{ ':', ':' },
	{ ';', ';' },
	{ '<', '<' },
	{ '=', '=' },
	{ '>', '>' },
	{ '?', '?' },
	{ '@', '@' },
	{ 'A', 'A' },
	{ 'B', 'B' },
	{ 'C', 'C' },
	{ 'D', 'D' },
	{ 'E', 'E' },
	{ 'F', 'F' },
	{ 'G', 'G' },
	{ 'H', 'H' },
	{ 'I', 'I' },
	{ 'J', 'J' },
	{ 'K', 'K' },
	{ 'L', 'L' },
	{ 'M', 'M' },
	{ 'N', 'N' },
	{ 'O', 'O' },
	{ 'P', 'P' },
	{ 'Q', 'Q' },
	{ 'R', 'R' },
	{ 'S', 'S' },
	{ 'T', 'T' },
	{ 'U', 'U' },
	{ 'V', 'V' },
	{ 'W', 'W' },
	{ 'X', 'X' },
	{ 'Y', 'Y' },
	{ 'Z', 'Z' },
	{ '[', '[' },
	{ '\\', '\\' },
	{ ']', ']' },
	{ '^', '^' },
	{ '_', '_' },
	{ '?', '\'' },	/* 		NUL */
	{ '?', 'a' },	/* 		SOH */
	{ '?', 'b' },	/* 		STX */
	{ '?', 'c' },	/* 		ETX */
	{ '?', 'd' },	/* 		EOT */
	{ '?', 'e' },	/* 		ENQ */
	{ '?', 'f' },	/* 		ACK */
	{ '?', 'g' },	/* 		BEL */
	{ '?', 'h' },	/* 		BS */
	{ '?', 'i' },	/* 		HT */
	{ '?', 'j' },	/* 		LF */
	{ '?', 'k' },	/* 		VT */
	{ '?', 'l' },	/* 		FF */
	{ '?', 'm' },	/* 		CR */
	{ '?', 'n' },	/* 		SO */
	{ '?', 'o' },	/* 		SI */
	{ '?', 'p' },	/*		DLE */
	{ '?', 'q' },	/*		DC1 */
	{ '?', 'r' },	/*		DC2 */
	{ '?', 's' },	/*		DC3 */
	{ '?', 't' },	/*		DC4 */
	{ '?', 'u' },	/*		NAK */
	{ '?', 'v' },	/*		SYN */
	{ '?', 'w' },	/*		ETB */
	{ '?', 'x' },	/*		CAN */
	{ '?', 'y' },	/* 		EM */
	{ '?', 'z' },	/*		SUB */
	{ '?', '{' },	/*		ESC */
	{ '?', '|' },	/* 		FS */
	{ '?', '}' },	/* 		GS */
	{ '?', '~' },	/* 		RS */
	{ '?', '?' },	/* DEL US */
	{ '?', '?' },	/* FNC3 FNC3  */
	{ '?', '?' },	/* FNC2 FNC2  */
	{ '?', '?' },	/* SHIFT SHIFT  */
	{ '?', '?' },	/* CODEC CODEC  */
	{ '?', '?' },	/* CODEB FNC4 CODEB  */
	{ '?', '?' },	/* FNC4 CODEA CODEA  */
	{ '?', '?' }	/* FNC1 FNC1 FNC 1  */
};

#define WHITE 0
#define BLACK 1

struct Runs {
	unsigned char color;	/* 0=white, 1=black */
	double len;		/* Interpolated run length, 0=last run */
	double start, end;	/* Interpolated start and end of a run */
	int label;
};
struct Runs *runs = NULL;	/* Dynamically allocated array of Runs */
struct Runs *runsort = NULL;	/* Runs, sorted by length */
int runs_size;			/* Maximum entries in runs, may be enlargened */
int runsort_size;
int runs_num;			/* Actual number of used entries */

struct Barwidth {
	double min, max, mean;
	int cnt;
	int label;		/* Bar width 1,2,3,4 or something else=not bar */
};
struct Barwidth *barwidth = NULL;
int barwidth_size;
int barwidth_num;		/* Actual number of used enties in barwidth */

void error(char *msg, ...)
{
	va_list ap;
	va_start(ap, msg);
	fprintf(stderr, "%s: ", progname);
	vfprintf(stderr, msg, ap);
	fprintf(stderr, ".\n");
	exit(1);
}

void alloc_mem(void **ptr, int *ptrsize, int nelem, size_t elem_size) {
	size_t req_size = nelem*elem_size;
	size_t new_size;
	void *p;
	if (*ptr==NULL) *ptrsize = 0;
	if (req_size <= *ptrsize) return;
	new_size = 16*elem_size + 2*req_size;
	p = realloc(*ptr, new_size);
	if (p==NULL) error("Out of memory");
	*ptr = p;
	*ptrsize = new_size;
}

void pgm_nexttoken(void) {
	int c;
	do {
		c = getc(input); 
		if (c==EOF) error("unexpected end of file");
		if (c=='#') {
			do {
				c = getc(input);
				if (c==EOF) error("unexpected end of file");
			} while (c!='\n');
			c = ' ';
		}
	} while (isspace(c));
	ungetc(c,input);
}

unsigned int pgm_readnum(void) {
	unsigned int c, val = 0;
	pgm_nexttoken();
	do {
		c = getc(input);
		if (!isdigit(c)) break;
		val = val*10 + c - '0';		/* Assume ASCII */
	} while (1);
	if (!isspace(c)) error("PGM file error");
	ungetc(c,input);
	return val;
}

void pgm_init(void) {
	char c1,c2,c3;
	unsigned int height,maxval;
	
	/* Check signature */
	c1 = getc(input);
	c2 = getc(input);
	c3 = getc(input);
	if (c1!='P' || c2!='5' || !isspace(c3)) error("Not raw bits PGM file");
	ungetc(c3,input);
	
	/* Read dimensions */
	pgm_width = pgm_readnum();
	height = pgm_readnum();	/* Height, we ignore this (read until recognized or EOF) */
	maxval = pgm_readnum();	/* Max gray value, we ignore it */
	c1 = getc(input);
	if (!isspace(c1)) error("PGM file error");
}


/* Return true if is still scanlines to process, false otherwise */
int pgm_readscan(void *ptr) {
	size_t r;
	r = fread(ptr, pgm_width, 1, input);
	return r==1;
}

double compute_mean(unsigned char *ptr, unsigned int width) {
	unsigned int sum = 0;
	int i;
	for (i=0; i<width; i++) sum+=ptr[i];
	return (double)sum / width;
}

void compute_runs(unsigned char *slice, unsigned int pgm_width, double mean) {
	unsigned char color, cur_color;
	int i;
	double intersect;
	
	alloc_mem(&runs, &runs_size, 1, sizeof(*runs));
	cur_color = (slice[0]<mean) ? BLACK : WHITE;
	runs[0].color = cur_color;
	runs[0].start = 0.0;
	runs_num = 1;
	for (i=1; i<pgm_width; i++) {
		color = (slice[i]<mean) ? BLACK : WHITE;
		if (color != cur_color) {
			alloc_mem(&runs, &runs_size, runs_num+1, sizeof(*runs));
			intersect = (double)i - (slice[i]-mean)/(slice[i]-slice[i-1]);	/* linear interpolation */
			runs[runs_num].start = intersect;
			runs[runs_num-1].end = intersect;
			runs[runs_num-1].len = intersect - runs[runs_num-1].start;
			runs[runs_num].color = color;
			runs_num++;
			cur_color = color;
		}
	}
	runs[runs_num-1].end = i;
	runs[runs_num-1].len = i - runs[runs_num-1].start;
}

int runs_comp(const void *a, const void *b) {
	struct Runs *ar = (struct Runs *)a;
	struct Runs *br = (struct Runs *)b;
	if (ar->len == br->len) return 0;
	if (ar->len < br->len) return -1;
	return 1;
}

void sort_runs(void) {
	alloc_mem(&runsort, &runsort_size, runs_num, sizeof(*runsort));
	memcpy(runsort, runs, runs_num*sizeof(*runs));
	qsort(runsort, runs_num, sizeof(*runsort), runs_comp);
}

void classify_bars(void) {
	double rat, min, sumlen;
	int i, cnt;
	
	alloc_mem(&barwidth, &barwidth_size, 1, sizeof(*barwidth));
	barwidth_num = 0;
	min = runsort[0].len;
	cnt = 0;
	sumlen = 0.0;
	for (i=1; i<runs_num; i++) {
		cnt++;
		sumlen += runsort[i-1].len;
		rat = runsort[i-1].len / runsort[i].len;
		if (rat < 0.9) {
			alloc_mem(&barwidth, &barwidth_size, barwidth_num+2, sizeof(*barwidth));
			barwidth[barwidth_num].min = min;
			barwidth[barwidth_num].max = runsort[i-1].len;
			barwidth[barwidth_num].mean = sumlen / cnt;
			barwidth[barwidth_num].cnt = cnt;
			barwidth_num++;
			min = runsort[i].len;
			cnt = 0;
			sumlen = 0.0;
		}
	}
	cnt++;
	sumlen += runsort[i-1].len;
	barwidth[barwidth_num].min = min;
	barwidth[barwidth_num].max = runsort[i-1].len;
	barwidth[barwidth_num].mean = sumlen / cnt;
	barwidth[barwidth_num].cnt = cnt;
	barwidth_num++;
}

int barwidth_comp_cnt(const void *a, const void *b) {
	struct Barwidth *ar = (struct Barwidth *)a;
	struct Barwidth *br = (struct Barwidth *)b;
	return -(ar->cnt - br->cnt);
}

int barwidth_comp_mean(const void *a, const void *b) {
	struct Barwidth *ar = (struct Barwidth *)a;
	struct Barwidth *br = (struct Barwidth *)b;
	if (ar->mean == br->mean) return 0;
	if (ar->mean < br->mean) return -1;
	return 1;
}

/* Return 1 if not valid barcode */
int identify_bars(void) {
	int i,j;
	
	if (barwidth_num<4) return 1;
	qsort(barwidth, barwidth_num, sizeof(*barwidth), barwidth_comp_cnt);
	qsort(barwidth, 4, sizeof(*barwidth), barwidth_comp_mean);
	for (i=0; i<barwidth_num; i++) barwidth[i].label = i+1;
	
	for (i=0; i<runs_num; i++) {
		for (j=0; j<(barwidth_num-1); j++) {
			if ((runs[i].len >= barwidth[j].min) && (runs[i].len <= barwidth[j].max)) break;
		}
		runs[i].label = barwidth[j].label;
	}
	return 0;
}

char *decode_message(void) {
	char *msg;
	int *codewords;
	int charset,code,i,j;
	int checksum;
	int codewords_len;
	int msg_len;
	
	/* Find start of barcode in runs */
	for (i=0; i<(runs_num-6); i++) {
		for (charset=CODE_STARTA; charset<=CODE_STARTC; charset++) {
			for (j=0; j<6; j++) if (codetab[charset][j]!=runs[i+j].label) break;
			if (j==6) goto found_start;		/* Found start code */
		}
	}
	return NULL;	/* can not find start of barcode */

	found_start:
	codewords = malloc((runs_num/6+1)*sizeof(*codewords));
	msg = malloc(runs_num/3+1);
	if (msg==NULL || codewords==NULL) error("memory error");

	/* Extract codewords */
	codewords_len = 0;
	i += 6;
	while (i<runs_num) {
		for (code=0; code<SIZE(codetab); code++) {
			for (j=0; j<6; j++) if (codetab[code][j]!=runs[i+j].label) break;
			if (j==6) break;		/* Found code */
		}
		if (code==SIZE(codetab)) {
			fprintf(stderr,"invalid code\n");
			goto cleanup_exit;	/* Invalid code */
		}
		if (code==CODE_STOP) break;
		codewords[codewords_len++] = code;
		i += 6;
	}
	if (i>=runs_num) {
		fprintf(stderr,"out of bars\n");
		goto cleanup_exit;
	}
	
	/* Verify checksum */
	checksum = charset;
	for (i=0; i<codewords_len-1; i++) {
		checksum = (((i+1)*codewords[i]) + checksum) % 103;
	}
	if (checksum != codewords[codewords_len-1]) {
		fprintf(stderr, "checksum error\n");
		goto cleanup_exit; /* checksum error */
	}
	
	/* Decode character string */
	msg_len = 0;
	for (i=0; i<codewords_len-1; i++) {
		code = codewords[i];
		switch (charset) {
			case CODE_STARTA:
			case CODE_STARTB:
				if (code==99) {
					charset = CODE_STARTC;
					break;
				}
				if (charset==CODE_STARTA && code==100) {
					charset = CODE_STARTB;
					break;
				}
				if (charset==CODE_STARTB && code==101) {
					charset = CODE_STARTA;
					break;
				}
				msg[msg_len++] = charsets[code][charset==CODE_STARTA ? 0 : 1];
				break;
			case CODE_STARTC:
				if (code<100) {
					msg[msg_len++] = code/10 + '0';
					msg[msg_len++] = code%10 + '0';
				} else {
					switch (code) {
					case 100:
						charset = CODE_STARTB;
						break;
					case 101:
						charset = CODE_STARTA;
						break;
					default:
						error("unsupported character %i at %i", code, i);
					}
				}
				break;
			default:
				error("error");
		}
	}
	free(codewords);
	msg[msg_len] = 0;
	return msg;

	cleanup_exit:
	free(codewords);
	free(msg);
	return NULL;
}

int main(int argc, char *argv[]) {
	unsigned char *slice;
	double mean;
	char *msg;
	
	if (argc>=2) {
		input = fopen(argv[1], "rb");
		if (input==NULL) error("error opening %s", argv[1]);
	} else {
		input = stdin;
	}
	
	pgm_init();
	slice = malloc(pgm_width);
	if (slice==NULL) error("out of memory");
	do {
		if (!pgm_readscan(slice)) break;
		mean = compute_mean(slice, pgm_width);
		compute_runs(slice, pgm_width, mean);
		sort_runs();
		classify_bars();
		if (identify_bars()==0) {
			msg = decode_message();
			if (msg!=NULL) {
				printf("%s\n", msg);
				free(msg);
				break;
			}
		}
	} while (1);
	free(slice);
	free(runs);
	free(runsort);
	
	return 0;
}
