/*------------------------------------------------------------------------
 *  Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
 *
 *  This file is part of the ZBar Bar Code Reader.
 *
 *  The ZBar Bar Code Reader is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Lesser Public License as
 *  published by the Free Software Foundation; either version 2.1 of
 *  the License, or (at your option) any later version.
 *
 *  The ZBar Bar Code Reader is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser Public License
 *  along with the ZBar Bar Code Reader; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 *  Boston, MA  02110-1301  USA
 *
 *  http://sourceforge.net/projects/zbar
 *------------------------------------------------------------------------*/

#include "config.h"
#include <zbar.h>
#include <stdlib.h>
#include <stdio.h>

#ifdef DEBUG_DATABAR
#define DEBUG_LEVEL (DEBUG_DATABAR)
#endif
#include "debug.h"
#include "decoder.h"

#define GS ('\035')

enum
{
    SCH_NUM,
    SCH_ALNUM,
    SCH_ISO646
};

static const signed char finder_hash[0x20] = {
    0x16, 0x1f, 0x02, 0x00, 0x03, 0x00, 0x06, 0x0b, 0x1f, 0x0e, 0x17,
    0x0c, 0x0b, 0x14, 0x11, 0x0c, 0x1f, 0x03, 0x13, 0x08, 0x00, 0x0a,
    -1,	  0x16, 0x0c, 0x09, -1,	  0x1a, 0x1f, 0x1c, 0x00, -1,
};

/* DataBar character encoding groups */
struct group_s {
    unsigned short sum;
    unsigned char wmax;
    unsigned char todd;
    unsigned char teven;
} groups[] = {
    /* (17,4) DataBar Expanded character groups */
    { 0, 7, 87, 4 },
    { 348, 5, 52, 20 },
    { 1388, 4, 30, 52 },
    { 2948, 3, 10, 104 },
    { 3988, 1, 1, 204 },

    /* (16,4) DataBar outer character groups */
    { 0, 8, 161, 1 },
    { 161, 6, 80, 10 },
    { 961, 4, 31, 34 },
    { 2015, 3, 10, 70 },
    { 2715, 1, 1, 126 },

    /* (15,4) DataBar inner character groups */
    { 1516, 8, 81, 1 },
    { 1036, 6, 48, 10 },
    { 336, 4, 20, 35 },
    { 0, 2, 4, 84 },
};

static const unsigned char exp_sequences[] = {
    /* sequence Group 1 */
    0x01, 0x23, 0x25, 0x07, 0x29, 0x47, 0x29, 0x67, 0x0b, 0x29, 0x87, 0xab,
    /* sequence Group 2 */
    0x21, 0x43, 0x65, 0x07, 0x21, 0x43, 0x65, 0x89, 0x21, 0x43, 0x65, 0xa9,
    0x0b, 0x21, 0x43, 0x67, 0x89, 0xab
};

/* DataBar expanded checksum multipliers */
static const unsigned char exp_checksums[] = { 1,   189, 62, 113, 46,  43,
					       109, 134, 6,  79,  161, 45 };

static inline void append_check14(unsigned char *buf)
{
    unsigned char chk = 0, d;
    int i;
    for (i = 13; --i >= 0;) {
	d = *(buf++) - '0';
	chk += d;
	if (!(i & 1))
	    chk += d << 1;
    }
    chk %= 10;
    if (chk)
	chk = 10 - chk;
    *buf = chk + '0';
}

static inline void decode10(unsigned char *buf, unsigned long n, int i)
{
    buf += i;
    while (--i >= 0) {
	unsigned char d = n % 10;
	n /= 10;
	*--buf = '0' + d;
    }
}

#define VAR_MAX(l, i) ((((l)*12 + (i)) * 2 + 6) / 7)

#define FEED_BITS(b)                         \
    while (i < (b) && len) {                 \
	d = (d << 12) | (*(data++) & 0xfff); \
	i += 12;                             \
	len--;                               \
	dbprintf(2, " %03lx", d & 0xfff);    \
    }

#define PUSH_CHAR(c) *(buf++) = (c)

#define PUSH_CHAR4(c0, c1, c2, c3) \
    do {                           \
	PUSH_CHAR(c0);             \
	PUSH_CHAR(c1);             \
	PUSH_CHAR(c2);             \
	PUSH_CHAR(c3);             \
    } while (0);

static inline int databar_postprocess_exp(zbar_decoder_t *dcode, int *data)
{
    int i = 0, enc;
    unsigned n;
    unsigned char *buf;
    unsigned long d = *(data++);
    int len	    = d / 211 + 4, buflen;

    /* grok encodation method */
    d = *(data++);
    dbprintf(2, "\n    len=%d %03lx", len, d & 0xfff);
    n = (d >> 4) & 0x7f;
    if (n >= 0x40) {
	i      = 10;
	enc    = 1;
	buflen = 2 + 14 + VAR_MAX(len, 10 - 2 - 44 + 6) + 2;
    } else if (n >= 0x38) {
	i      = 4;
	enc    = 6 + (n & 7);
	buflen = 2 + 14 + 4 + 6 + 2 + 6 + 2;
    } else if (n >= 0x30) {
	i      = 6;
	enc    = 2 + ((n >> 2) & 1);
	buflen = 2 + 14 + 4 + 3 + VAR_MAX(len, 6 - 2 - 44 - 2 - 10) + 2;
    } else if (n >= 0x20) {
	i      = 7;
	enc    = 4 + ((n >> 3) & 1);
	buflen = 2 + 14 + 4 + 6;
    } else {
	i      = 9;
	enc    = 0;
	buflen = VAR_MAX(len, 9 - 2) + 2;
    }
    dbprintf(2, " buflen=%d enc=%d", buflen, enc);
    zassert(buflen > 2, -1, "buflen=%d\n", buflen);

    if (enc < 4) {
	/* grok variable length symbol bit field */
	if ((len ^ (d >> (--i))) & 1)
	    /* even/odd length mismatch */
	    return (-1);
	if (((d >> (--i)) & 1) != (len > 14))
	    /* size group mismatch */
	    return (-1);
    }
    len -= 2;
    dbprintf(2, " [%d+%d]", i, len);

    if (size_buf(dcode, buflen))
	return (-1);
    buf = dcode->buf;

    /* handle compressed fields */
    if (enc) {
	PUSH_CHAR('0');
	PUSH_CHAR('1');
    }

    if (enc == 1) {
	i -= 4;
	n = (d >> i) & 0xf;
	if (i >= 10)
	    return (-1);
	PUSH_CHAR('0' + n);
    } else if (enc)
	PUSH_CHAR('9');

    if (enc) {
	int j;
	for (j = 0; j < 4; j++) {
	    FEED_BITS(10);
	    i -= 10;
	    n = (d >> i) & 0x3ff;
	    if (n >= 1000)
		return (-1);
	    decode10(buf, n, 3);
	    buf += 3;
	}
	append_check14(buf - 13);
	buf++;
    }

    switch (enc) {
    case 2: /* 01100: AI 392x */
	FEED_BITS(2);
	i -= 2;
	n = (d >> i) & 0x3;
	PUSH_CHAR4('3', '9', '2', '0' + n);
	break;

    case 3: /* 01101: AI 393x */
	FEED_BITS(12);
	i -= 2;
	n = (d >> i) & 0x3;
	PUSH_CHAR4('3', '9', '3', '0' + n);
	i -= 10;
	n = (d >> i) & 0x3ff;
	if (n >= 1000)
	    return (-1);
	decode10(buf, n, 3);
	buf += 3;
	break;

    case 4: /* 0100: AI 3103 */
	FEED_BITS(15);
	i -= 15;
	n = (d >> i) & 0x7fff;
	PUSH_CHAR4('3', '1', '0', '3');
	decode10(buf, n, 6);
	buf += 6;
	break;

    case 5: /* 0101: AI 3202/3203 */
	FEED_BITS(15);
	i -= 15;
	n = (d >> i) & 0x7fff;
	dbprintf(2, " v=%d", n);
	PUSH_CHAR4('3', '2', '0', (n >= 10000) ? '3' : '2');
	if (n >= 10000)
	    n -= 10000;
	decode10(buf, n, 6);
	buf += 6;
	break;
    }
    if (enc >= 6) {
	/* 0111000 - 0111111: AI 310x/320x + AI 11/13/15/17 */
	PUSH_CHAR4('3', '1' + (enc & 1), '0', 'x');
	FEED_BITS(20);
	i -= 20;
	n = (d >> i) & 0xfffff;
	dbprintf(2, " [%d+%d] %d", i, len, n);
	if (n >= 1000000)
	    return (-1);
	decode10(buf, n, 6);
	*(buf - 1) = *buf;
	*buf	   = '0';
	buf += 6;

	FEED_BITS(16);
	i -= 16;
	n = (d >> i) & 0xffff;
	if (n < 38400) {
	    int dd, mm, yy;
	    dd = n % 32;
	    n /= 32;
	    mm = n % 12 + 1;
	    n /= 12;
	    yy = n;
	    PUSH_CHAR('1');
	    PUSH_CHAR('0' + ((enc - 6) | 1));
	    decode10(buf, yy, 2);
	    buf += 2;
	    decode10(buf, mm, 2);
	    buf += 2;
	    decode10(buf, dd, 2);
	    buf += 2;
	} else if (n > 38400)
	    return (-1);
    }

    if (enc < 4) {
	/* remainder is general-purpose data compaction */
	int scheme = SCH_NUM;
	while (i > 0 || len > 0) {
	    FEED_BITS(8);
	    dbprintf(2, " [%d+%d]", i, len);

	    if (scheme == SCH_NUM) {
		int n1;
		i -= 4;
		if (i < 0)
		    break;
		if (!((d >> i) & 0xf)) {
		    scheme = SCH_ALNUM;
		    dbprintf(2, ">A");
		    continue;
		}
		if (!len && i < 3) {
		    /* special case last digit */
		    n = ((d >> i) & 0xf) - 1;
		    if (n > 9)
			return (-1);
		    *(buf++) = '0' + n;
		    break;
		}
		i -= 3;
		zassert(i >= 0, -1, "\n");
		n  = ((d >> i) & 0x7f) - 8;
		n1 = n % 11;
		n  = n / 11;
		dbprintf(2, "N%d%d", n, n1);
		*(buf++) = (n < 10) ? '0' + n : GS;
		*(buf++) = (n1 < 10) ? '0' + n1 : GS;
	    } else {
		unsigned c = 0;
		i -= 3;
		if (i < 0)
		    break;
		if (!((d >> i) & 0x7)) {
		    scheme = SCH_NUM;
		    continue;
		}
		i -= 2;
		if (i < 0)
		    break;
		n = (d >> i) & 0x1f;
		if (n == 0x04) {
		    scheme ^= 0x3;
		    dbprintf(2, ">%d", scheme);
		} else if (n == 0x0f)
		    c = GS;
		else if (n < 0x0f)
		    c = 43 + n;
		else if (scheme == SCH_ALNUM) {
		    i--;
		    if (i < 0)
			return (-1);
		    n = (d >> i) & 0x1f;
		    if (n < 0x1a)
			c = 'A' + n;
		    else if (n == 0x1a)
			c = '*';
		    else if (n < 0x1f)
			c = ',' + n - 0x1b;
		    else
			return (-1);
		} else if (scheme == SCH_ISO646 && n < 0x1d) {
		    i -= 2;
		    if (i < 0)
			return (-1);
		    n = (d >> i) & 0x3f;
		    if (n < 0x1a)
			c = 'A' + n;
		    else if (n < 0x34)
			c = 'a' + n - 0x1a;
		    else
			return (-1);
		} else if (scheme == SCH_ISO646) {
		    i -= 3;
		    if (i < 0)
			return (-1);
		    n = ((d >> i) & 0x1f);
		    dbprintf(2, "(%02x)", n);
		    if (n < 0xa)
			c = '!' + n - 8;
		    else if (n < 0x15)
			c = '%' + n - 0xa;
		    else if (n < 0x1b)
			c = ':' + n - 0x15;
		    else if (n == 0x1b)
			c = '_';
		    else if (n == 0x1c)
			c = ' ';
		    else
			return (-1);
		} else
		    return (-1);

		if (c) {
		    dbprintf(2, "%d%c", scheme, c);
		    *(buf++) = c;
		}
	    }
	}
	/* FIXME check pad? */
    }

    i = buf - dcode->buf;
    zassert(i < dcode->buf_alloc, -1, "i=%02x %s\n", i,
	    _zbar_decoder_buf_dump(dcode->buf, i));

    *buf	  = 0;
    dcode->buflen = i;
    if (i && *--buf == GS) {
	*buf = 0;
	dcode->buflen--;
    }

    dbprintf(2, "\n    %s", _zbar_decoder_buf_dump(dcode->buf, dcode->buflen));
    return (0);
}
#undef FEED_BITS

/* convert from heterogeneous base {1597,2841}
 * to base 10 character representation
 */
static inline void databar_postprocess(zbar_decoder_t *dcode, unsigned d[4])
{
    unsigned long r;
    databar_decoder_t *db = &dcode->databar;
    int i;
    unsigned c, chk = 0;
    unsigned char *buf = dcode->buf;
    *(buf++)	       = '0';
    *(buf++)	       = '1';
    buf += 15;
    *--buf = '\0';
    *--buf = '\0';

    dbprintf(2, "\n    d={%d,%d,%d,%d}", d[0], d[1], d[2], d[3]);
    r	 = d[0] * 1597 + d[1];
    d[1] = r / 10000;
    r %= 10000;
    r	 = r * 2841 + d[2];
    d[2] = r / 10000;
    r %= 10000;
    r	 = r * 1597 + d[3];
    d[3] = r / 10000;
    dbprintf(2, " r=%ld", r);

    for (i = 4; --i >= 0;) {
	c = r % 10;
	chk += c;
	if (i & 1)
	    chk += c << 1;
	*--buf = c + '0';
	if (i)
	    r /= 10;
    }

    dbprintf(2, " d={%d,%d,%d}", d[1], d[2], d[3]);
    r	 = d[1] * 2841 + d[2];
    d[2] = r / 10000;
    r %= 10000;
    r	 = r * 1597 + d[3];
    d[3] = r / 10000;
    dbprintf(2, " r=%ld", r);

    for (i = 4; --i >= 0;) {
	c = r % 10;
	chk += c;
	if (i & 1)
	    chk += c << 1;
	*--buf = c + '0';
	if (i)
	    r /= 10;
    }

    r = d[2] * 1597 + d[3];
    dbprintf(2, " d={%d,%d} r=%ld", d[2], d[3], r);

    for (i = 5; --i >= 0;) {
	c = r % 10;
	chk += c;
	if (!(i & 1))
	    chk += c << 1;
	*--buf = c + '0';
	if (i)
	    r /= 10;
    }

    /* NB linkage flag not supported */
    if (TEST_CFG(db->config, ZBAR_CFG_EMIT_CHECK)) {
	chk %= 10;
	if (chk)
	    chk = 10 - chk;
	buf[13]	      = chk + '0';
	dcode->buflen = buf - dcode->buf + 14;
    } else
	dcode->buflen = buf - dcode->buf + 13;

    dbprintf(2, "\n    %s", _zbar_decoder_buf_dump(dcode->buf, 16));
}

static inline int check_width(unsigned wf, unsigned wd, unsigned n)
{
    unsigned dwf = wf * 3;
    wd *= 14;
    wf *= n;
    return (wf - dwf <= wd && wd <= wf + dwf);
}

static inline void merge_segment(databar_decoder_t *db, databar_segment_t *seg)
{
    unsigned csegs = db->csegs;
    int i;
    for (i = 0; i < csegs; i++) {
	databar_segment_t *s = db->segs + i;
	if (s != seg && s->finder == seg->finder && s->exp == seg->exp &&
	    s->color == seg->color && s->side == seg->side &&
	    s->data == seg->data && s->check == seg->check &&
	    check_width(seg->width, s->width, 14)) {
	    /* merge with existing segment */
	    unsigned cnt = s->count;
	    if (cnt < 0x7f)
		cnt++;
	    seg->count = cnt;
	    seg->partial &= s->partial;
	    seg->width = (3 * seg->width + s->width + 2) / 4;
	    s->finder  = -1;
	    dbprintf(2, " dup@%d(%d,%d)", i, cnt,
		     (db->epoch - seg->epoch) & 0xff);
	} else if (s->finder >= 0) {
	    unsigned age = (db->epoch - s->epoch) & 0xff;
	    if (age >= 248 || (age >= 128 && s->count < 2))
		s->finder = -1;
	}
    }
}

static inline zbar_symbol_type_t match_segment(zbar_decoder_t *dcode,
					       databar_segment_t *seg)
{
    databar_decoder_t *db = &dcode->databar;
    unsigned csegs = db->csegs, maxage = 0xfff;
    int i0, i1, i2, maxcnt = 0;
    databar_segment_t *smax[3] = {
	NULL,
    };
    unsigned d[4];

    if (seg->partial && seg->count < 4)
	return (ZBAR_PARTIAL);

    for (i0 = 0; i0 < csegs; i0++) {
	databar_segment_t *s0 = db->segs + i0;
	if (s0 == seg || s0->finder != seg->finder || s0->exp ||
	    s0->color != seg->color || s0->side == seg->side ||
	    (s0->partial && s0->count < 4) ||
	    !check_width(seg->width, s0->width, 14))
	    continue;

	for (i1 = 0; i1 < csegs; i1++) {
	    databar_segment_t *s1 = db->segs + i1;
	    int chkf, chks, chk;
	    unsigned age1;
	    if (i1 == i0 || s1->finder < 0 || s1->exp ||
		s1->color == seg->color || (s1->partial && s1->count < 4) ||
		!check_width(seg->width, s1->width, 14))
		continue;
	    dbprintf(2, "\n\t[%d,%d] f=%d(0%xx)/%d(%x%x%x)", i0, i1,
		     seg->finder, seg->color, s1->finder, s1->exp, s1->color,
		     s1->side);

	    if (seg->color)
		chkf = seg->finder + s1->finder * 9;
	    else
		chkf = s1->finder + seg->finder * 9;
	    if (chkf > 72)
		chkf--;
	    if (chkf > 8)
		chkf--;

	    chks = (seg->check + s0->check + s1->check) % 79;

	    if (chkf >= chks)
		chk = chkf - chks;
	    else
		chk = 79 + chkf - chks;

	    dbprintf(2, " chk=(%d,%d) => %d", chkf, chks, chk);
	    age1 = (((db->epoch - s0->epoch) & 0xff) +
		    ((db->epoch - s1->epoch) & 0xff));

	    for (i2 = i1 + 1; i2 < csegs; i2++) {
		databar_segment_t *s2 = db->segs + i2;
		unsigned cnt, age2, age;
		if (i2 == i0 || s2->finder != s1->finder || s2->exp ||
		    s2->color != s1->color || s2->side == s1->side ||
		    s2->check != chk || (s2->partial && s2->count < 4) ||
		    !check_width(seg->width, s2->width, 14))
		    continue;
		age2 = (db->epoch - s2->epoch) & 0xff;
		age  = age1 + age2;
		cnt  = s0->count + s1->count + s2->count;
		dbprintf(2, " [%d] MATCH cnt=%d age=%d", i2, cnt, age);
		if (maxcnt < cnt || (maxcnt == cnt && maxage > age)) {
		    maxcnt  = cnt;
		    maxage  = age;
		    smax[0] = s0;
		    smax[1] = s1;
		    smax[2] = s2;
		}
	    }
	}
    }

    if (!smax[0])
	return (ZBAR_PARTIAL);

    d[(seg->color << 1) | seg->side] = seg->data;
    for (i0 = 0; i0 < 3; i0++) {
	d[(smax[i0]->color << 1) | smax[i0]->side] = smax[i0]->data;
	if (!--(smax[i0]->count))
	    smax[i0]->finder = -1;
    }
    seg->finder = -1;

    if (size_buf(dcode, 18))
	return (ZBAR_PARTIAL);

    if (acquire_lock(dcode, ZBAR_DATABAR))
	return (ZBAR_PARTIAL);

    databar_postprocess(dcode, d);
    dcode->modifiers = MOD(ZBAR_MOD_GS1);
    dcode->direction = 1 - 2 * (seg->side ^ seg->color ^ 1);
    return (ZBAR_DATABAR);
}

static inline signed lookup_sequence(databar_segment_t *seg, int fixed,
				       int seq[22], const size_t maxsize)
{
    unsigned n = seg->data / 211, i;
    const unsigned char *p;
    i = (n + 1) / 2 + 1;
    n += 4;
    i = (i * i) / 4;
    dbprintf(2, " {%d,%d:", i, n);
    p = exp_sequences + i;

	if (n >= maxsize-1) {
	// The loop below checks i<n and increments i by one within the loop
	// when accessing seq[22]. For this to be safe, n needs to be < 21.
	// See CVE-2023-40890.
	  return -1;
    }

    fixed >>= 1;
    seq[0] = 0;
    seq[1] = 1;
    for (i = 2; i < n;) {
	int s = *p;
	if (!(i & 2)) {
	    p++;
	    s >>= 4;
	} else
	    s &= 0xf;
	if (s == fixed)
	    fixed = -1;
	s <<= 1;
	dbprintf(2, "%x", s);
	seq[i++] = s++;
	seq[i++] = s;
    }
    dbprintf(2, "}");
    seq[n] = -1;
    return (fixed < 1);
}

#define IDX(s) \
    (((s)->finder << 2) | ((s)->color << 1) | ((s)->color ^ (s)->side))

static inline zbar_symbol_type_t
match_segment_exp(zbar_decoder_t *dcode, databar_segment_t *seg, int dir)
{
    databar_decoder_t *db = &dcode->databar;
    int bestsegs[22], i = 0, segs[22], seq[22];
    int ifixed = seg - db->segs, fixed = IDX(seg), maxcnt = 0;
    int iseg[DATABAR_MAX_SEGMENTS];
    unsigned csegs = db->csegs, width = seg->width, maxage = 0x7fff;

    bestsegs[0] = segs[0] = seq[1] = -1;
    seq[0]			   = 0;

    dbprintf(2, "\n    fixed=%d@%d: ", fixed, ifixed);
    for (i = csegs, seg = db->segs + csegs - 1; --i >= 0; seg--) {
	if (seg->exp && seg->finder >= 0 && (!seg->partial || seg->count >= 4))
	    iseg[i] = IDX(seg);
	else
	    iseg[i] = -1;
	dbprintf(2, " %d", iseg[i]);
    }

    for (i = 0;; i--) {
	unsigned cnt, chk, age;
	unsigned data0, chk0;
	if (!i)
	    dbprintf(2, "\n   ");
	for (; i >= 0 && seq[i] >= 0; i--) {
	    int j;
	    dbprintf(2, " [%d]%d", i, seq[i]);

	    if (seq[i] == fixed) {
		seg = db->segs + ifixed;
		if (segs[i] < 0 && check_width(width, seg->width, 14)) {
		    dbprintf(2, "*");
		    j = ifixed;
		} else
		    continue;
	    } else {
		for (j = segs[i] + 1; j < csegs; j++) {
		    if (iseg[j] == seq[i] &&
			(!i || check_width(width, db->segs[j].width, 14))) {
			seg = db->segs + j;
			break;
		    }
		}
		if (j == csegs)
		    continue;
	    }

	    if (!i) {
			signed int lu = lookup_sequence(seg, fixed, seq, sizeof(seq)/sizeof(seq[0]));
			if(!lu) {
			   dbprintf(2, "[nf]");
			   continue;
			}
			if(lu < 0) {
				dbprintf(1, " [aborted]\n");
				goto abort;
			}
			width = seg->width;
			dbprintf(2, " A00@%d", j);
	    } else {
		width = (width + seg->width) / 2;
		dbprintf(2, " %c%x%x@%d", 'A' + seg->finder, seg->color,
			 seg->side, j);
	    }
	    segs[i++] = j;
	    segs[i++] = -1;
	}
	if (i < 0)
	    break;

	seg = db->segs + segs[0];
	cnt = 0, chk = 0, age = (db->epoch - seg->epoch) & 0xff;
	for (i = 1; segs[i] >= 0; i++) {
	    seg = db->segs + segs[i];
	    chk += seg->check;
	    cnt += seg->count;
	    age += (db->epoch - seg->epoch) & 0xff;
	}

	data0 = db->segs[segs[0]].data;
	chk0  = data0 % 211;
	chk %= 211;

	dbprintf(2, " chk=%d ?= %d", chk, chk0);
	if (chk != chk0)
	    continue;

	dbprintf(2, " cnt=%d age=%d", cnt, age);
	if (maxcnt > cnt || (maxcnt == cnt && maxage <= age))
	    continue;

	dbprintf(2, " !");
	maxcnt = cnt;
	maxage = age;
	for (i = 0; segs[i] >= 0; i++)
	    bestsegs[i] = segs[i];
	bestsegs[i] = -1;
    }

    if (bestsegs[0] < 0)
	return (ZBAR_PARTIAL);

    if (acquire_lock(dcode, ZBAR_DATABAR_EXP))
	return (ZBAR_PARTIAL);

    for (i = 0; bestsegs[i] >= 0; i++)
	segs[i] = db->segs[bestsegs[i]].data;

    if (databar_postprocess_exp(dcode, segs)) {
	release_lock(dcode, ZBAR_DATABAR_EXP);
	return (ZBAR_PARTIAL);
    }

    for (i = 0; bestsegs[i] >= 0; i++)
	if (bestsegs[i] != ifixed) {
	    seg = db->segs + bestsegs[i];
	    if (!--seg->count)
		seg->finder = -1;
	}

    /* FIXME stacked rows are frequently reversed,
     * so direction is impossible to determine at this level
     */
    dcode->direction = (1 - 2 * (seg->side ^ seg->color)) * dir;
    dcode->modifiers = MOD(ZBAR_MOD_GS1);
    return (ZBAR_DATABAR_EXP);
abort:
    return (ZBAR_NONE);
}
#undef IDX

static inline unsigned calc_check(unsigned sig0, unsigned sig1, unsigned side,
				  unsigned mod)
{
    unsigned chk = 0;
    int i;
    for (i = 4; --i >= 0;) {
	chk = (chk * 3 + (sig1 & 0xf) + 1) * 3 + (sig0 & 0xf) + 1;
	sig1 >>= 4;
	sig0 >>= 4;
	if (!(i & 1))
	    chk %= mod;
    }
    dbprintf(2, " chk=%d", chk);

    if (side)
	chk = (chk * (6561 % mod)) % mod;
    return (chk);
}

static inline int calc_value4(unsigned sig, unsigned n, unsigned wmax,
			      unsigned nonarrow)
{
    unsigned w0, w1, w2, w3;
    unsigned v = 0;
    n--;

    w0 = (sig >> 12) & 0xf;
    if (w0 > 1) {
	unsigned n0, sk20, sk21;
	if (w0 > wmax)
	    return (-1);
	n0   = n - w0;
	sk20 = (n - 1) * n * (2 * n - 1);
	sk21 = n0 * (n0 + 1) * (2 * n0 + 1);
	v    = sk20 - sk21 - 3 * (w0 - 1) * (2 * n - w0);

	if (!nonarrow && w0 > 2 && n > 4) {
	    unsigned k = (n - 2) * (n - 1) * (2 * n - 3) - sk21;
	    k -= 3 * (w0 - 2) * (14 * n - 7 * w0 - 31);
	    v -= k;
	}

	if (n - 2 > wmax) {
	    unsigned wm20 = 2 * wmax * (wmax + 1);
	    unsigned wm21 = (2 * wmax + 1);
	    unsigned k	  = sk20;
	    if (n0 > wmax) {
		k -= sk21;
		k += 3 * (w0 - 1) * (wm20 - wm21 * (2 * n - w0));
	    } else {
		k -= (wmax + 1) * (wmax + 2) * (2 * wmax + 3);
		k += 3 * (n - wmax - 2) * (wm20 - wm21 * (n + wmax + 1));
	    }
	    k *= 3;
	    v -= k;
	}
	v /= 12;
    } else
	nonarrow = 1;
    n -= w0;

    w1 = (sig >> 8) & 0xf;
    if (w1 > 1) {
	if (w1 > wmax)
	    return (-1);
	v += (2 * n - w1) * (w1 - 1) / 2;
	if (!nonarrow && w1 > 2 && n > 3)
	    v -= (2 * n - w1 - 5) * (w1 - 2) / 2;
	if (n - 1 > wmax) {
	    if (n - w1 > wmax)
		v -= (w1 - 1) * (2 * n - w1 - 2 * wmax);
	    else
		v -= (n - wmax) * (n - wmax - 1);
	}
    } else
	nonarrow = 1;
    n -= w1;

    w2 = (sig >> 4) & 0xf;
    if (w2 > 1) {
	if (w2 > wmax)
	    return (-1);
	v += w2 - 1;
	if (!nonarrow && w2 > 2 && n > 2)
	    v -= n - 2;
	if (n > wmax)
	    v -= n - wmax;
    } else
	nonarrow = 1;

    w3 = sig & 0xf;
    if (w3 == 1)
	nonarrow = 1;
    else if (w3 > wmax)
	return (-1);

    if (!nonarrow)
	return (-1);

    return (v);
}

static inline zbar_symbol_type_t
decode_char(zbar_decoder_t *dcode, databar_segment_t *seg, int off, int dir)
{
    databar_decoder_t *db = &dcode->databar;
    unsigned s		  = calc_s(dcode, (dir > 0) ? off : off - 6, 8);
    int n, i, emin[2] = { 0, }, sum = 0;
    unsigned sig0 = 0, sig1 = 0;
    int diff, vodd, veven, v;
    unsigned sum0, sum1, chk;
    struct group_s *g;

    if (seg->exp)
	n = 17;
    else if (seg->side)
	n = 15;
    else
	n = 16;
    emin[1] = -n;

    dbprintf(2,
	     "\n        char[%c%d]: n=%d s=%d w=%d sig=", (dir < 0) ? '>' : '<',
	     off, n, s, seg->width);
    if (s < 13 || !check_width(seg->width, s, n))
	return (ZBAR_NONE);

    for (i = 4; --i >= 0;) {
	int e = decode_e(pair_width(dcode, off), s, n);
	if (e < 0)
	    return (ZBAR_NONE);
	dbprintf(2, "%d", e);
	sum = e - sum;
	off += dir;
	sig1 <<= 4;
	if (emin[1] < -sum)
	    emin[1] = -sum;
	sig1 += sum;
	if (!i)
	    break;

	e = decode_e(pair_width(dcode, off), s, n);
	if (e < 0)
	    return (ZBAR_NONE);
	dbprintf(2, "%d", e);
	sum = e - sum;
	off += dir;
	sig0 <<= 4;
	if (emin[0] > sum)
	    emin[0] = sum;
	sig0 += sum;
    }

    diff = emin[~n & 1];
    diff = diff + (diff << 4);
    diff = diff + (diff << 8);

    sig0 -= diff;
    sig1 += diff;

    dbprintf(2, " emin=%d,%d el=%04x/%04x", emin[0], emin[1], sig0, sig1);

    sum0 = sig0 + (sig0 >> 8);
    sum1 = sig1 + (sig1 >> 8);
    sum0 += sum0 >> 4;
    sum1 += sum1 >> 4;
    sum0 &= 0xf;
    sum1 &= 0xf;

    dbprintf(2, " sum=%d/%d", sum0, sum1);

    if (sum0 + sum1 + 8 != n) {
	dbprintf(2, " [SUM]");
	return (ZBAR_NONE);
    }

    if (((sum0 ^ (n >> 1)) | (sum1 ^ (n >> 1) ^ n)) & 1) {
	dbprintf(2, " [ODD]");
	return (ZBAR_NONE);
    }

    i = ((n & 0x3) ^ 1) * 5 + (sum1 >> 1);
    zassert(i < sizeof(groups) / sizeof(*groups), -1,
	    "n=%d sum=%d/%d sig=%04x/%04x g=%d", n, sum0, sum1, sig0, sig1, i);
    g = groups + i;
    dbprintf(2, "\n            g=%d(%d,%d,%d/%d)", i, g->sum, g->wmax, g->todd,
	     g->teven);

    vodd = calc_value4(sig0 + 0x1111, sum0 + 4, g->wmax, ~n & 1);
    dbprintf(2, " v=%d", vodd);
    if (vodd < 0 || vodd > g->todd)
	return (ZBAR_NONE);

    veven = calc_value4(sig1 + 0x1111, sum1 + 4, 9 - g->wmax, n & 1);
    dbprintf(2, "/%d", veven);
    if (veven < 0 || veven > g->teven)
	return (ZBAR_NONE);

    v = g->sum;
    if (n & 2)
	v += vodd + veven * g->todd;
    else
	v += veven + vodd * g->teven;

    dbprintf(2, " f=%d(%x%x%x)", seg->finder, seg->exp, seg->color, seg->side);

    chk = 0;
    if (seg->exp) {
	unsigned side = seg->color ^ seg->side ^ 1;
	if (v >= 4096)
	    return (ZBAR_NONE);
	/* skip A1 left */
	chk = calc_check(sig0, sig1, side, 211);
	if (seg->finder || seg->color || seg->side) {
	    i = (seg->finder << 1) - side + seg->color;
	    zassert(i >= 0 && i < 12, ZBAR_NONE, "f=%d(%x%x%x) side=%d i=%d\n",
		    seg->finder, seg->exp, seg->color, seg->side, side, i);
	    chk = (chk * exp_checksums[i]) % 211;
	} else if (v >= 4009)
	    return (ZBAR_NONE);
	else
	    chk = 0;
    } else {
	chk = calc_check(sig0, sig1, seg->side, 79);
	if (seg->color)
	    chk = (chk * 16) % 79;
    }
    dbprintf(2, " => %d val=%d", chk, v);

    seg->check = chk;
    seg->data  = v;

    merge_segment(db, seg);

    if (seg->exp)
	return (match_segment_exp(dcode, seg, dir));
    else if (dir > 0)
	return (match_segment(dcode, seg));
    return (ZBAR_PARTIAL);
}

static inline int alloc_segment(databar_decoder_t *db)
{
    unsigned maxage = 0, csegs = db->csegs;
    int i, old		       = -1;
    for (i = 0; i < csegs; i++) {
	databar_segment_t *seg = db->segs + i;
	unsigned age;
	if (seg->finder < 0) {
	    dbprintf(2, " free@%d", i);
	    return (i);
	}
	age = (db->epoch - seg->epoch) & 0xff;
	if (age >= 128 && seg->count < 2) {
	    seg->finder = -1;
	    dbprintf(2, " stale@%d (%d - %d = %d)", i, db->epoch, seg->epoch,
		     age);
	    return (i);
	}

	/* score based on both age and count */
	if (age > seg->count)
	    age = age - seg->count + 1;
	else
	    age = 1;

	if (maxage < age) {
	    maxage = age;
	    old	   = i;
	    dbprintf(2, " old@%d(%u)", i, age);
	}
    }

    if (csegs < DATABAR_MAX_SEGMENTS) {
	dbprintf(2, " new@%d", i);
	i = csegs;
	csegs *= 2;
	if (csegs > DATABAR_MAX_SEGMENTS)
	    csegs = DATABAR_MAX_SEGMENTS;
	if (csegs != db->csegs) {
	    databar_segment_t *seg;
	    db->segs  = realloc(db->segs, csegs * sizeof(*db->segs));
	    db->csegs = csegs;
	    seg	      = db->segs + csegs;
	    while (--seg, --csegs >= i) {
		seg->finder  = -1;
		seg->exp     = 0;
		seg->color   = 0;
		seg->side    = 0;
		seg->partial = 0;
		seg->count   = 0;
		seg->epoch   = 0;
		seg->check   = 0;
	    }
	    return (i);
	}
    }
    zassert(old >= 0, -1, "\n");

    db->segs[old].finder = -1;
    return (old);
}

static inline zbar_symbol_type_t decode_finder(zbar_decoder_t *dcode)
{
    databar_decoder_t *db = &dcode->databar;
    databar_segment_t *seg;
    unsigned e0 = pair_width(dcode, 1);
    unsigned e2 = pair_width(dcode, 3);
    unsigned e1, e3, s, finder, dir;
    int sig, iseg;
    int rc, i;

    dbprintf(2, "      databar: e0=%d e2=%d", e0, e2);
    if (e0 < e2) {
	unsigned e = e2 * 4;
	if (e < 15 * e0 || e > 34 * e0)
	    return (ZBAR_NONE);
	dir = 0;
	e3  = pair_width(dcode, 4);
    } else {
	unsigned e = e0 * 4;
	if (e < 15 * e2 || e > 34 * e2)
	    return (ZBAR_NONE);
	dir = 1;
	e2  = e0;
	e3  = pair_width(dcode, 0);
    }
    e1 = pair_width(dcode, 2);

    s = e1 + e3;
    dbprintf(2, " e1=%d e3=%d dir=%d s=%d", e1, e3, dir, s);
    if (s < 12)
	return (ZBAR_NONE);

    sig = ((decode_e(e3, s, 14) << 8) | (decode_e(e2, s, 14) << 4) |
	   decode_e(e1, s, 14));
    dbprintf(2, " sig=%04x", sig & 0xfff);
    if (sig < 0 || ((sig >> 4) & 0xf) < 8 || ((sig >> 4) & 0xf) > 10 ||
	(sig & 0xf) >= 10 || ((sig >> 8) & 0xf) >= 10 ||
	(((sig >> 8) + sig) & 0xf) != 10)
	return (ZBAR_NONE);

    finder = (finder_hash[(sig - (sig >> 5)) & 0x1f] +
	      finder_hash[(sig >> 1) & 0x1f]) &
	     0x1f;
    dbprintf(2, " finder=%d", finder);
    if (finder == 0x1f ||
	!TEST_CFG((finder < 9) ? db->config : db->config_exp, ZBAR_CFG_ENABLE))
	return (ZBAR_NONE);

    zassert(finder >= 0, ZBAR_NONE, "dir=%d sig=%04x f=%d\n", dir, sig & 0xfff,
	    finder);

    iseg = alloc_segment(db);
    if (iseg < 0)
	return (ZBAR_NONE);

    seg		 = db->segs + iseg;
    seg->finder	 = (finder >= 9) ? finder - 9 : finder;
    seg->exp	 = (finder >= 9);
    seg->color	 = get_color(dcode) ^ dir ^ 1;
    seg->side	 = dir;
    seg->partial = 0;
    seg->count	 = 1;
    seg->width	 = s;
    seg->epoch	 = db->epoch;

    rc = decode_char(dcode, seg, 12 - dir, -1);
    if (!rc)
	seg->partial = 1;
    else
	db->epoch++;

    i = (dcode->idx + 8 + dir) & 0xf;
    zassert(db->chars[i] == -1, ZBAR_NONE, "\n");
    db->chars[i] = iseg;
    return (rc);
}

zbar_symbol_type_t _zbar_decode_databar(zbar_decoder_t *dcode)
{
    databar_decoder_t *db = &dcode->databar;
    databar_segment_t *seg, *pair;
    zbar_symbol_type_t sym;
    int iseg, i = dcode->idx & 0xf;

    sym = decode_finder(dcode);
    dbprintf(2, "\n");

    iseg = db->chars[i];
    if (iseg < 0)
	return (sym);

    db->chars[i] = -1;
    seg		 = db->segs + iseg;
    dbprintf(2, "        databar: i=%d part=%d f=%d(%x%x%x)", iseg,
	     seg->partial, seg->finder, seg->exp, seg->color, seg->side);
    zassert(seg->finder >= 0, ZBAR_NONE, "i=%d f=%d(%x%x%x) part=%x\n", iseg,
	    seg->finder, seg->exp, seg->color, seg->side, seg->partial);

    if (seg->partial) {
	pair	  = NULL;
	seg->side = !seg->side;
    } else {
	int jseg     = alloc_segment(db);
	pair	     = db->segs + iseg;
	seg	     = db->segs + jseg;
	seg->finder  = pair->finder;
	seg->exp     = pair->exp;
	seg->color   = pair->color;
	seg->side    = !pair->side;
	seg->partial = 0;
	seg->count   = 1;
	seg->width   = pair->width;
	seg->epoch   = db->epoch;
    }

    sym = decode_char(dcode, seg, 1, 1);
    if (!sym) {
	seg->finder = -1;
	if (pair)
	    pair->partial = 1;
    } else
	db->epoch++;
    dbprintf(2, "\n");

    return (sym);
}
