/*------------------------------------------------------------------------
 *  Copyright 2007-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>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_INTTYPES_H
# include <inttypes.h>
#endif
#ifdef HAVE_DBUS
#include <dbus/dbus.h>
#endif

#include <stdlib.h>     /* malloc, free */
#include <string.h>     /* memcmp, memset, memcpy */
#include <assert.h>

#include <zbar.h>
#include "error.h"
#include "image.h"
#include "timer.h"
#if ENABLE_QRCODE == 1
# include "qrcode.h"
#endif
#if ENABLE_SQCODE == 1
# include "sqcode.h"
#endif
#include "img_scanner.h"
#include "svg.h"

#if 1
# define ASSERT_POS \
    assert(p == data + x + y * (intptr_t)w)
#else
# define ASSERT_POS
#endif

/* FIXME cache setting configurability */

/* time interval for which two images are considered "nearby"
 */
#define CACHE_PROXIMITY   1000 /* ms */

/* time that a result must *not* be detected before
 * it will be reported again
 */
#define CACHE_HYSTERESIS  2000 /* ms */

/* time after which cache entries are invalidated
 */
#define CACHE_TIMEOUT     (CACHE_HYSTERESIS * 2) /* ms */

#define NUM_SCN_CFGS (ZBAR_CFG_Y_DENSITY - ZBAR_CFG_X_DENSITY + 1)

#define CFG(iscn, cfg) ((iscn)->configs[(cfg) - ZBAR_CFG_X_DENSITY])
#define TEST_CFG(iscn, cfg) (((iscn)->config >> ((cfg) - ZBAR_CFG_POSITION)) & 1)

#ifndef NO_STATS
# define STAT(x) iscn->stat_##x++
#else
# define STAT(...)
# define dump_stats(...)
#endif

#define RECYCLE_BUCKETS     5

typedef struct recycle_bucket_s {
    int nsyms;
    zbar_symbol_t *head;
} recycle_bucket_t;

/* image scanner state */
struct zbar_image_scanner_s {
    zbar_scanner_t *scn;        /* associated linear intensity scanner */
    zbar_decoder_t *dcode;      /* associated symbol decoder */
#if ENABLE_QRCODE == 1
    qr_reader *qr;              /* QR Code 2D reader */
#endif
#if ENABLE_SQCODE == 1
    sq_reader *sq;              /* SQ Code 2D reader */
#endif

    const void *userdata;       /* application data */
    /* user result callback */
    zbar_image_data_handler_t *handler;

    unsigned long time;         /* scan start time */
    zbar_image_t *img;          /* currently scanning image *root* */
    int dx, dy, du, umin, v;    /* current scan direction */
    zbar_symbol_set_t *syms;    /* previous decode results */
    /* recycled symbols in 4^n size buckets */
    recycle_bucket_t recycle[RECYCLE_BUCKETS];

    int enable_cache;           /* current result cache state */
    zbar_symbol_t *cache;       /* inter-image result cache entries */

    /* configuration settings */
    unsigned config;            /* config flags */
    unsigned ean_config;
    int configs[NUM_SCN_CFGS];  /* int valued configurations */
    int sym_configs[1][NUM_SYMS]; /* per-symbology configurations */

#ifndef NO_STATS
    int stat_syms_new;
    int stat_iscn_syms_inuse, stat_iscn_syms_recycle;
    int stat_img_syms_inuse, stat_img_syms_recycle;
    int stat_sym_new;
    int stat_sym_recycle[RECYCLE_BUCKETS];
#endif

#ifdef HAVE_DBUS
    int is_dbus_enabled;        /* dbus enabled flag */
#endif
};

void _zbar_image_scanner_recycle_syms (zbar_image_scanner_t *iscn,
                                       zbar_symbol_t *sym)
{
    zbar_symbol_t *next = NULL;
    for(; sym; sym = next) {
        next = sym->next;
        if(sym->refcnt && _zbar_refcnt(&sym->refcnt, -1)) {
            /* unlink referenced symbol */
            /* FIXME handle outstanding component refs (currently unsupported)
             */
            assert(sym->data_alloc);
            sym->next = NULL;
        }
        else {
            int i;
            recycle_bucket_t *bucket;
            /* recycle unreferenced symbol */
            if(!sym->data_alloc) {
                sym->data = NULL;
                sym->datalen = 0;
            }
            if(sym->syms) {
                if(_zbar_refcnt(&sym->syms->refcnt, -1))
                    assert(0);
                _zbar_image_scanner_recycle_syms(iscn, sym->syms->head);
                sym->syms->head = NULL;
                _zbar_symbol_set_free(sym->syms);
                sym->syms = NULL;
            }
            for(i = 0; i < RECYCLE_BUCKETS; i++)
                if(sym->data_alloc < 1 << (i * 2))
                    break;
            if(i == RECYCLE_BUCKETS) {
                assert(sym->data);
                free(sym->data);
                sym->data = NULL;
                sym->data_alloc = 0;
                i = 0;
            }
            bucket = &iscn->recycle[i];
            /* FIXME cap bucket fill */
            bucket->nsyms++;
            sym->next = bucket->head;
            bucket->head = sym;
        }
    }
}

static inline int recycle_syms (zbar_image_scanner_t *iscn,
                                zbar_symbol_set_t *syms)
{
    if(_zbar_refcnt(&syms->refcnt, -1))
        return(1);

    _zbar_image_scanner_recycle_syms(iscn, syms->head);
    syms->head = syms->tail = NULL;
    syms->nsyms = 0;
    return(0);
}

inline void zbar_image_scanner_recycle_image (zbar_image_scanner_t *iscn,
                                              zbar_image_t *img)
{
    zbar_symbol_set_t *syms = iscn->syms;
    if(syms && syms->refcnt) {
        if(recycle_syms(iscn, syms)) {
            STAT(iscn_syms_inuse);
            iscn->syms = NULL;
        }
        else
            STAT(iscn_syms_recycle);
    }

    syms = img->syms;
    img->syms = NULL;
    if(syms && recycle_syms(iscn, syms))
        STAT(img_syms_inuse);
    else if(syms) {
        STAT(img_syms_recycle);

        /* select one set to resurrect, destroy the other */
        if(iscn->syms)
            _zbar_symbol_set_free(syms);
        else
            iscn->syms = syms;
    }
}

inline zbar_symbol_t*
_zbar_image_scanner_alloc_sym (zbar_image_scanner_t *iscn,
                               zbar_symbol_type_t type,
                               int datalen)
{
    /* recycle old or alloc new symbol */
    zbar_symbol_t *sym = NULL;
    int i;
    for(i = 0; i < RECYCLE_BUCKETS - 1; i++)
        if(datalen <= 1 << (i * 2))
            break;

    for(; i > 0; i--)
        if((sym = iscn->recycle[i].head)) {
            STAT(sym_recycle[i]);
            break;
        }

    if(sym) {
        iscn->recycle[i].head = sym->next;
        sym->next = NULL;
        assert(iscn->recycle[i].nsyms);
        iscn->recycle[i].nsyms--;
    }
    else {
        sym = calloc(1, sizeof(zbar_symbol_t));
        STAT(sym_new);
    }

    /* init new symbol */
    sym->type = type;
    sym->quality = 1;
    sym->npts = 0;
    sym->orient = ZBAR_ORIENT_UNKNOWN;
    sym->cache_count = 0;
    sym->time = iscn->time;
    assert(!sym->syms);

    if(datalen > 0) {
        sym->datalen = datalen - 1;
        if(sym->data_alloc < datalen) {
            if(sym->data)
                free(sym->data);
            sym->data_alloc = datalen;
            sym->data = malloc(datalen);
        }
    }
    else {
        if(sym->data)
            free(sym->data);
        sym->data = NULL;
        sym->datalen = sym->data_alloc = 0;
    }
    return(sym);
}

static inline zbar_symbol_t *cache_lookup (zbar_image_scanner_t *iscn,
                                           zbar_symbol_t *sym)
{
    /* search for matching entry in cache */
    zbar_symbol_t **entry = &iscn->cache;
    while(*entry) {
        if((*entry)->type == sym->type &&
           (*entry)->datalen == sym->datalen &&
           !memcmp((*entry)->data, sym->data, sym->datalen))
            break;
        if((sym->time - (*entry)->time) > CACHE_TIMEOUT) {
            /* recycle stale cache entry */
            zbar_symbol_t *next = (*entry)->next;
            (*entry)->next = NULL;
            _zbar_image_scanner_recycle_syms(iscn, *entry);
            *entry = next;
        }
        else
            entry = &(*entry)->next;
    }
    return(*entry);
}

static inline void cache_sym (zbar_image_scanner_t *iscn,
                              zbar_symbol_t *sym)
{
    if(iscn->enable_cache) {
        uint32_t age, near_thresh, far_thresh, dup;
        zbar_symbol_t *entry = cache_lookup(iscn, sym);
        if(!entry) {
            /* FIXME reuse sym */
            entry = _zbar_image_scanner_alloc_sym(iscn, sym->type,
                                                  sym->datalen + 1);
            entry->configs = sym->configs;
            entry->modifiers = sym->modifiers;
            memcpy(entry->data, sym->data, sym->datalen);
            entry->time = sym->time - CACHE_HYSTERESIS;
            entry->cache_count = 0;
            /* add to cache */
            entry->next = iscn->cache;
            iscn->cache = entry;
        }

        /* consistency check and hysteresis */
        age = sym->time - entry->time;
        entry->time = sym->time;
        near_thresh = (age < CACHE_PROXIMITY);
        far_thresh = (age >= CACHE_HYSTERESIS);
        dup = (entry->cache_count >= 0);
        if((!dup && !near_thresh) || far_thresh) {
            int type = sym->type;
            int h = _zbar_get_symbol_hash(type);
            entry->cache_count = -iscn->sym_configs[0][h];
        }
        else if(dup || near_thresh)
            entry->cache_count++;

        sym->cache_count = entry->cache_count;
    }
    else
        sym->cache_count = 0;
}

void _zbar_image_scanner_add_sym(zbar_image_scanner_t *iscn,
                                 zbar_symbol_t *sym)
{
    zbar_symbol_set_t *syms;
    cache_sym(iscn, sym);

    syms = iscn->syms;
    if(sym->cache_count || !syms->tail) {
        sym->next = syms->head;
        syms->head = sym;
    }
    else {
        sym->next = syms->tail->next;
        syms->tail->next = sym;
    }

    if(!sym->cache_count)
        syms->nsyms++;
    else if(!syms->tail)
        syms->tail = sym;

    _zbar_symbol_refcnt(sym, 1);
}

#if ENABLE_QRCODE == 1
extern qr_finder_line *_zbar_decoder_get_qr_finder_line(zbar_decoder_t*);

# define QR_FIXED(v, rnd) ((((v) << 1) + (rnd)) << (QR_FINDER_SUBPREC - 1))
# define PRINT_FIXED(val, prec) \
    ((val) >> (prec)),         \
        (1000 * ((val) & ((1 << (prec)) - 1)) / (1 << (prec)))

static inline void qr_handler (zbar_image_scanner_t *iscn)
{
    unsigned u;
    int vert;
    qr_finder_line *line = _zbar_decoder_get_qr_finder_line(iscn->dcode);
    assert(line);
    u = zbar_scanner_get_edge(iscn->scn, line->pos[0],
                              QR_FINDER_SUBPREC);
    line->boffs = u - zbar_scanner_get_edge(iscn->scn, line->boffs,
                                            QR_FINDER_SUBPREC);
    line->len = zbar_scanner_get_edge(iscn->scn, line->len,
                                      QR_FINDER_SUBPREC);
    line->eoffs = zbar_scanner_get_edge(iscn->scn, line->eoffs,
                                        QR_FINDER_SUBPREC) - line->len;
    line->len -= u;

    u = QR_FIXED(iscn->umin, 0) + iscn->du * u;
    if(iscn->du < 0) {
        int tmp = line->boffs;
        line->boffs = line->eoffs;
        line->eoffs = tmp;
        u -= line->len;
    }
    vert = !iscn->dx;
    line->pos[vert] = u;
    line->pos[!vert] = QR_FIXED(iscn->v, 1);

    _zbar_qr_found_line(iscn->qr, vert, line);
}
#endif

#if ENABLE_SQCODE == 1
extern unsigned _zbar_decoder_get_sq_finder_config(zbar_decoder_t*);

static void sq_handler (zbar_image_scanner_t *iscn)
{
    unsigned config = _zbar_decoder_get_sq_finder_config(iscn->dcode);
    _zbar_sq_new_config(iscn->sq, config);
}
#endif

static void symbol_handler (zbar_decoder_t *dcode)
{
    zbar_image_scanner_t *iscn = zbar_decoder_get_userdata(dcode);
    zbar_symbol_type_t type = zbar_decoder_get_type(dcode);
    int x = 0, y = 0, dir;
    const char *data;
    unsigned datalen;
    zbar_symbol_t *sym;

#if ENABLE_QRCODE == 1
    if(type == ZBAR_QRCODE) {
        qr_handler(iscn);
        return;
    }
#else
    assert(type != ZBAR_QRCODE);
#endif

    if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) {
        /* tmp position fixup */
        int w = zbar_scanner_get_width(iscn->scn);
        int u = iscn->umin + iscn->du * zbar_scanner_get_edge(iscn->scn, w, 0);
        if(iscn->dx) {
            x = u;
            y = iscn->v;
        }
        else {
            x = iscn->v;
            y = u;
        }
    }

    /* FIXME debug flag to save/display all PARTIALs */
    if(type <= ZBAR_PARTIAL) {
        zprintf(256, "partial symbol @(%d,%d)\n", x, y);
        return;
    }

    data = zbar_decoder_get_data(dcode);
    datalen = zbar_decoder_get_data_length(dcode);

    /* FIXME need better symbol matching */
    for(sym = iscn->syms->head; sym; sym = sym->next)
        if(sym->type == type &&
           sym->datalen == datalen &&
           !memcmp(sym->data, data, datalen)) {
            sym->quality++;
            zprintf(224, "dup symbol @(%d,%d): dup %s: %.20s\n",
                    x, y, zbar_get_symbol_name(type), data);
            if(TEST_CFG(iscn, ZBAR_CFG_POSITION))
                /* add new point to existing set */
                /* FIXME should be polygon */
                sym_add_point(sym, x, y);
            return;
        }

    sym = _zbar_image_scanner_alloc_sym(iscn, type, datalen + 1);
    sym->configs = zbar_decoder_get_configs(dcode, type);
    sym->modifiers = zbar_decoder_get_modifiers(dcode);
    /* FIXME grab decoder buffer */
    memcpy(sym->data, data, datalen + 1);

    /* initialize first point */
    if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) {
        zprintf(192, "new symbol @(%d,%d): %s: %.20s\n",
                x, y, zbar_get_symbol_name(type), data);
        sym_add_point(sym, x, y);
    }

    dir = zbar_decoder_get_direction(dcode);
    if(dir)
        sym->orient = (iscn->dy != 0) + ((iscn->du ^ dir) & 2);

    _zbar_image_scanner_add_sym(iscn, sym);
}

zbar_image_scanner_t *zbar_image_scanner_create ()
{
    zbar_image_scanner_t *iscn = calloc(1, sizeof(zbar_image_scanner_t));
    if(!iscn)
        return(NULL);
    iscn->dcode = zbar_decoder_create();
    iscn->scn = zbar_scanner_create(iscn->dcode);
    if(!iscn->dcode || !iscn->scn) {
        zbar_image_scanner_destroy(iscn);
        return(NULL);
    }
    zbar_decoder_set_userdata(iscn->dcode, iscn);
    zbar_decoder_set_handler(iscn->dcode, symbol_handler);

#if ENABLE_QRCODE == 1
    iscn->qr = _zbar_qr_create();
#endif

#if ENABLE_SQCODE == 1
    iscn->sq = _zbar_sq_create();
#endif

    /* apply default configuration */
    CFG(iscn, ZBAR_CFG_X_DENSITY) = 1;
    CFG(iscn, ZBAR_CFG_Y_DENSITY) = 1;
    zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_POSITION, 1);
    zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_UNCERTAINTY, 2);
    zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_TEST_INVERTED, 0);
    zbar_image_scanner_set_config(iscn, ZBAR_QRCODE, ZBAR_CFG_UNCERTAINTY, 0);
    zbar_image_scanner_set_config(iscn, ZBAR_CODE128, ZBAR_CFG_UNCERTAINTY, 0);
    zbar_image_scanner_set_config(iscn, ZBAR_CODE93, ZBAR_CFG_UNCERTAINTY, 0);
    zbar_image_scanner_set_config(iscn, ZBAR_CODE39, ZBAR_CFG_UNCERTAINTY, 0);
    zbar_image_scanner_set_config(iscn, ZBAR_CODABAR, ZBAR_CFG_UNCERTAINTY, 1);
    zbar_image_scanner_set_config(iscn, ZBAR_COMPOSITE, ZBAR_CFG_UNCERTAINTY, 0);
    return(iscn);
}

#ifndef NO_STATS
static inline void dump_stats (const zbar_image_scanner_t *iscn)
{
    int i;
    zprintf(1, "symbol sets allocated   = %-4d\n", iscn->stat_syms_new);
    zprintf(1, "    scanner syms in use = %-4d\trecycled  = %-4d\n",
            iscn->stat_iscn_syms_inuse, iscn->stat_iscn_syms_recycle);
    zprintf(1, "    image syms in use   = %-4d\trecycled  = %-4d\n",
            iscn->stat_img_syms_inuse, iscn->stat_img_syms_recycle);
    zprintf(1, "symbols allocated       = %-4d\n", iscn->stat_sym_new);
    for(i = 0; i < RECYCLE_BUCKETS; i++)
        zprintf(1, "     recycled[%d]        = %-4d\n",
                i, iscn->stat_sym_recycle[i]);
}
#endif

void zbar_image_scanner_destroy (zbar_image_scanner_t *iscn)
{
    int i;
    dump_stats(iscn);
    if(iscn->syms) {
        if(iscn->syms->refcnt)
            zbar_symbol_set_ref(iscn->syms, -1);
        else
            _zbar_symbol_set_free(iscn->syms);
        iscn->syms = NULL;
    }
    if(iscn->scn)
        zbar_scanner_destroy(iscn->scn);
    iscn->scn = NULL;
    if(iscn->dcode)
        zbar_decoder_destroy(iscn->dcode);
    iscn->dcode = NULL;
    for(i = 0; i < RECYCLE_BUCKETS; i++) {
        zbar_symbol_t *sym, *next;
        for(sym = iscn->recycle[i].head; sym; sym = next) {
            next = sym->next;
            _zbar_symbol_free(sym);
        }
    }
#if ENABLE_QRCODE == 1
    if(iscn->qr) {
        _zbar_qr_destroy(iscn->qr);
        iscn->qr = NULL;
    }
#endif
#if ENABLE_SQCODE == 1
    if(iscn->sq) {
        _zbar_sq_destroy(iscn->sq);
        iscn->sq = NULL;
    }
#endif
    free(iscn);
}

zbar_image_data_handler_t*
zbar_image_scanner_set_data_handler (zbar_image_scanner_t *iscn,
                                     zbar_image_data_handler_t *handler,
                                     const void *userdata)
{
    zbar_image_data_handler_t *result = iscn->handler;
    iscn->handler = handler;
    iscn->userdata = userdata;
    return(result);
}

int zbar_image_scanner_set_config (zbar_image_scanner_t *iscn,
                                   zbar_symbol_type_t sym,
                                   zbar_config_t cfg,
                                   int val)
{
    if((sym == 0 || sym == ZBAR_COMPOSITE) && cfg == ZBAR_CFG_ENABLE) {
        iscn->ean_config = !!val;
        if(sym)
            return(0);
    }

    if(cfg < ZBAR_CFG_UNCERTAINTY)
        return(zbar_decoder_set_config(iscn->dcode, sym, cfg, val));

    if(cfg < ZBAR_CFG_POSITION) {
        int c, i;
        if(cfg > ZBAR_CFG_UNCERTAINTY)
            return(1);
        c = cfg - ZBAR_CFG_UNCERTAINTY;
        if(sym > ZBAR_PARTIAL) {
            i = _zbar_get_symbol_hash(sym);
            iscn->sym_configs[c][i] = val;
        }
        else
            for(i = 0; i < NUM_SYMS; i++)
                iscn->sym_configs[c][i] = val;
        return(0);
    }

    /* Image scanner parameters apply only to ZBAR_PARTIAL */
    if(sym > ZBAR_PARTIAL)
        return(1);

    if(cfg >= ZBAR_CFG_X_DENSITY && cfg <= ZBAR_CFG_Y_DENSITY) {
        CFG(iscn, cfg) = val;
        return(0);
    }

    cfg -= ZBAR_CFG_POSITION;

    if(!val)
        iscn->config &= ~(1 << cfg);
    else if(val == 1)
        iscn->config |= (1 << cfg);
    else
        return(1);

    return(0);
}

int zbar_image_scanner_get_config(zbar_image_scanner_t *iscn,
                                  zbar_symbol_type_t sym,
                                  zbar_config_t cfg,
                                  int *val)
{
    /* Return error if symbol doesn't have config */
    if(sym < ZBAR_PARTIAL || sym > ZBAR_CODE128 || sym == ZBAR_COMPOSITE)
        return 1;

    if (cfg < ZBAR_CFG_UNCERTAINTY)
        return zbar_decoder_get_config(iscn->dcode, sym, cfg, val);

    if(cfg < ZBAR_CFG_POSITION) {
        if(sym == ZBAR_PARTIAL)
            return(1);

        int i = _zbar_get_symbol_hash(sym);

        *val = iscn->sym_configs[cfg - ZBAR_CFG_UNCERTAINTY][i];
        return 0;
    }

    /* Image scanner parameters apply only to ZBAR_PARTIAL */
    if(sym > ZBAR_PARTIAL)
        return(1);

    if(cfg < ZBAR_CFG_X_DENSITY) {
        *val = (iscn->config & (1 << (cfg - ZBAR_CFG_POSITION))) != 0;
        return 0;
    }

    if(cfg <= ZBAR_CFG_Y_DENSITY) {
        *val = CFG(iscn, cfg);
        return 0;
    }

    return 1;
}

void zbar_image_scanner_enable_cache (zbar_image_scanner_t *iscn,
                                      int enable)
{
    if(iscn->cache) {
        /* recycle all cached syms */
        _zbar_image_scanner_recycle_syms(iscn, iscn->cache);
        iscn->cache = NULL;
    }
    iscn->enable_cache = (enable) ? 1 : 0;
}

const zbar_symbol_set_t *
zbar_image_scanner_get_results (const zbar_image_scanner_t *iscn)
{
    return(iscn->syms);
}

static inline void quiet_border (zbar_image_scanner_t *iscn)
{
    /* flush scanner pipeline */
    zbar_scanner_t *scn = iscn->scn;
    zbar_scanner_flush(scn);
    zbar_scanner_flush(scn);
    zbar_scanner_new_scan(scn);
}


#ifdef HAVE_DBUS
static int dict_add_property (DBusMessageIter *property,
                              const char *key,
                              const char *value)
{

    DBusMessageIter dict_entry, dict_val;
    DBusError err;
    dbus_error_init(&err);
    dbus_message_iter_open_container(property, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry);
    if (!dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key)){
        fprintf(stderr, "Key Error\n");
        dbus_message_iter_close_container(property, &dict_entry);
        goto error;
    }
    dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &dict_val);
    if (!dbus_message_iter_append_basic(&dict_val, DBUS_TYPE_STRING, &value)){
        fprintf(stderr, "Value Error\n");
        dbus_message_iter_close_container(&dict_entry, &dict_val);
        dbus_message_iter_close_container(property, &dict_entry);
        goto error;
    }
    dbus_message_iter_close_container(&dict_entry, &dict_val);
    dbus_message_iter_close_container(property, &dict_entry);
    return(1);

error:
    if (dbus_error_is_set(&err)) {
        fprintf(stderr, "Name Error (%s)\n", err.message);
        dbus_error_free(&err);
    }
    return(0);
}

static void zbar_send_dbus(int type, const char* sigvalue)
{
    DBusMessage* msg;
    DBusMessageIter args, dict;
    DBusConnection* conn;
    const char *type_name;
    DBusError err;
    int ret;
    dbus_uint32_t serial = 0;

    // initialise the error value
    dbus_error_init(&err);

    // connect to the DBUS system bus, and check for errors
    conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
    if (dbus_error_is_set(&err)) {
        fprintf(stderr, "Connection Error (%s)\n", err.message);
        dbus_error_free(&err);
    }
    if (NULL == conn) {
        fprintf(stderr, "Connection Null\n");
        return;
    }

    // register our name on the bus, and check for errors
    ret = dbus_bus_request_name(conn, "org.linuxtv.Zbar", DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
    if (dbus_error_is_set(&err)) {
        fprintf(stderr, "Name Error (%s)\n", err.message);
        dbus_error_free(&err);
    }
    if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
        return;
    }

    // create a signal & check for errors
    msg = dbus_message_new_signal("/org/linuxtv/Zbar1/Code", // object name of the signal
                                 "org.linuxtv.Zbar1.Code", // interface name of the signal
                                 "Code"); // name of the signal
    if (NULL == msg)
    {
        fprintf(stderr, "Message Null\n");
        return;
    }

    // append arguments onto signal
    dbus_message_iter_init_append(msg, &args);
    if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &dict)) {
        fprintf(stderr, "Out Of Dict Container Memory!\n");
        dbus_message_unref(msg);
        return;
    }

    type_name = zbar_get_symbol_name(type);
    if (!dict_add_property(&dict, "Type", type_name)) {
        fprintf(stderr, "Out Of Property Memory!\n");
        dbus_message_unref(msg);
        return;
    }

    if (!dict_add_property(&dict, "Data", sigvalue)) {
        fprintf(stderr, "Out Of Property Memory!\n");
        dbus_message_unref(msg);
        return;
    }

    dbus_message_iter_close_container(&args, &dict);

    // send the message and flush the connection
    if (!dbus_connection_send(conn, msg, &serial)) {
        fprintf(stderr, "Out Of Memory!\n");
        dbus_message_unref(msg);
        return;
    }

    dbus_connection_flush(conn);
    dbus_bus_release_name(conn, "org.linuxtv.Zbar", &err);
    if (dbus_error_is_set(&err)) {
        fprintf(stderr, "Name Release Error (%s)\n", err.message);
        dbus_error_free(&err);
    }

    // free the message
    dbus_message_unref(msg);
}

static void zbar_send_code_via_dbus(zbar_image_t *img)
{
    const zbar_symbol_t *sym = zbar_image_first_symbol(img);

    if (!sym)
        return;
    for(; sym; sym = zbar_symbol_next(sym)) {
        if(zbar_symbol_get_count(sym))
            continue;

        zbar_symbol_type_t type = zbar_symbol_get_type(sym);
        if(type == ZBAR_PARTIAL)
            continue;

        zbar_send_dbus(type, zbar_symbol_get_data(sym));
    }
}
#endif

#define movedelta(dx, dy) do {                  \
        x += (dx);                              \
        y += (dy);                              \
        p += (dx) + ((uintptr_t)(dy) * w);       \
    } while(0);

static void *_zbar_scan_image(zbar_image_scanner_t *iscn,
                              zbar_image_t *img)
{
    zbar_symbol_set_t *syms;
    const uint8_t *data;
    zbar_scanner_t *scn = iscn->scn;
    unsigned w, h, cx1, cy1;
    int density;

    /* timestamp image
     * FIXME prefer video timestamp
     */
    iscn->time = _zbar_timer_now();

#if ENABLE_QRCODE == 1
    _zbar_qr_reset(iscn->qr);
#endif

#if ENABLE_SQCODE == 1
    _zbar_sq_reset(iscn->sq);
#endif

    /* image must be in grayscale format */
    if(img->format != fourcc('Y','8','0','0') &&
       img->format != fourcc('G','R','E','Y'))
        return NULL;
    iscn->img = img;

    /* recycle previous scanner and image results */
    zbar_image_scanner_recycle_image(iscn, img);
    syms = iscn->syms;
    if(!syms) {
        syms = iscn->syms = _zbar_symbol_set_create();
        STAT(syms_new);
        zbar_symbol_set_ref(syms, 1);
    }
    else
        zbar_symbol_set_ref(syms, 2);
    img->syms = syms;

    w = img->width;
    h = img->height;
    cx1 = img->crop_x + img->crop_w;
    assert(cx1 <= w);
    cy1 = img->crop_y + img->crop_h;
    assert(cy1 <= h);
    data = img->data;

    zbar_image_write_png(img, "debug.png");
    svg_open("debug.svg", 0, 0, w, h);
    svg_image("debug.png", w, h);

    zbar_scanner_new_scan(scn);

    density = CFG(iscn, ZBAR_CFG_Y_DENSITY);
    if(density > 0) {
        const uint8_t *p = data;
        int x = 0, y = 0;

        int border = (((img->crop_h - 1) % density) + 1) / 2;
        if(border > img->crop_h / 2)
            border = img->crop_h / 2;
        border += img->crop_y;
        assert(border <= h);
        svg_group_start("scanner", 0, 1, 1, 0, 0);
        iscn->dy = 0;

        movedelta(img->crop_x, border);
        iscn->v = y;

        while(y < cy1) {
            int cx0 = img->crop_x;;
            zprintf(128, "img_x+: %04d,%04d @%p\n", x, y, p);
            svg_path_start("vedge", 1. / 32, 0, y + 0.5);
            iscn->dx = iscn->du = 1;
            iscn->umin = cx0;
            while(x < cx1) {
                uint8_t d = *p;
                movedelta(1, 0);
                zbar_scan_y(scn, d);
            }
            ASSERT_POS;
            quiet_border(iscn);
            svg_path_end();

            movedelta(-1, density);
            iscn->v = y;
            if(y >= cy1)
                break;

            zprintf(128, "img_x-: %04d,%04d @%p\n", x, y, p);
            svg_path_start("vedge", -1. / 32, w, y + 0.5);
            iscn->dx = iscn->du = -1;
            iscn->umin = cx1;
            while(x >= cx0) {
                uint8_t d = *p;
                movedelta(-1, 0);
                zbar_scan_y(scn, d);
            }
            ASSERT_POS;
            quiet_border(iscn);
            svg_path_end();

            movedelta(1, density);
            iscn->v = y;
        }
        svg_group_end();
    }
    iscn->dx = 0;

    density = CFG(iscn, ZBAR_CFG_X_DENSITY);
    if(density > 0) {
        const uint8_t *p = data;
        int x = 0, y = 0;

        int border = (((img->crop_w - 1) % density) + 1) / 2;
        if(border > img->crop_w / 2)
            border = img->crop_w / 2;
        border += img->crop_x;
        assert(border <= w);
        svg_group_start("scanner", 90, 1, -1, 0, 0);
        movedelta(border, img->crop_y);
        iscn->v = x;

        while(x < cx1) {
            int cy0 = img->crop_y;
            zprintf(128, "img_y+: %04d,%04d @%p\n", x, y, p);
            svg_path_start("vedge", 1. / 32, 0, x + 0.5);
            iscn->dy = iscn->du = 1;
            iscn->umin = cy0;
            while(y < cy1) {
                uint8_t d = *p;
                movedelta(0, 1);
                zbar_scan_y(scn, d);
            }
            ASSERT_POS;
            quiet_border(iscn);
            svg_path_end();

            movedelta(density, -1);
            iscn->v = x;
            if(x >= cx1)
                break;

            zprintf(128, "img_y-: %04d,%04d @%p\n", x, y, p);
            svg_path_start("vedge", -1. / 32, h, x + 0.5);
            iscn->dy = iscn->du = -1;
            iscn->umin = cy1;
            while(y >= cy0) {
                uint8_t d = *p;
                movedelta(0, -1);
                zbar_scan_y(scn, d);
            }
            ASSERT_POS;
            quiet_border(iscn);
            svg_path_end();

            movedelta(density, 1);
            iscn->v = x;
        }
        svg_group_end();
    }
    iscn->dy = 0;
    iscn->img = NULL;

#if ENABLE_QRCODE == 1
    _zbar_qr_decode(iscn->qr, iscn, img);
#endif

#if ENABLE_SQCODE == 1
    sq_handler(iscn);
    _zbar_sq_decode(iscn->sq, iscn, img);
#endif

    /* FIXME tmp hack to filter bad EAN results */
    /* FIXME tmp hack to merge simple case EAN add-ons */
    char filter = (!iscn->enable_cache &&
                   (density == 1 || CFG(iscn, ZBAR_CFG_Y_DENSITY) == 1));
    int nean = 0, naddon = 0;
    if(syms->nsyms) {
        zbar_symbol_t **symp;
        for(symp = &syms->head; *symp; ) {
            zbar_symbol_t *sym = *symp;
            if(sym->cache_count <= 0 &&
               ((sym->type < ZBAR_COMPOSITE && sym->type > ZBAR_PARTIAL) ||
                sym->type == ZBAR_DATABAR ||
                sym->type == ZBAR_DATABAR_EXP ||
                sym->type == ZBAR_CODABAR))
            {
	        if((sym->type == ZBAR_CODABAR || filter) && sym->quality < 4) {
                    if(iscn->enable_cache) {
                        /* revert cache update */
                        zbar_symbol_t *entry = cache_lookup(iscn, sym);
                        if(entry)
                            entry->cache_count--;
                        else
                            assert(0);
                    }

                    /* recycle */
                    *symp = sym->next;
                    syms->nsyms--;
                    sym->next = NULL;
                    _zbar_image_scanner_recycle_syms(iscn, sym);
                    continue;
                }
                else if(sym->type < ZBAR_COMPOSITE &&
                        sym->type != ZBAR_ISBN10)
                {
                    if(sym->type > ZBAR_EAN5)
                        nean++;
                    else
                        naddon++;
                }
            }
            symp = &sym->next;
        }

        if(nean == 1 && naddon == 1 && iscn->ean_config) {
            /* create container symbol for composite result */
            zbar_symbol_t *ean = NULL, *addon = NULL;
            for(symp = &syms->head; *symp; ) {
                zbar_symbol_t *sym = *symp;
                if(sym->type < ZBAR_COMPOSITE && sym->type > ZBAR_PARTIAL) {
                    /* move to composite */
                    *symp = sym->next;
                    syms->nsyms--;
                    sym->next = NULL;
                    if(sym->type <= ZBAR_EAN5)
                        addon = sym;
                    else
                        ean = sym;
                }
                else
                    symp = &sym->next;
            }
            assert(ean);
            assert(addon);

            int datalen = ean->datalen + addon->datalen + 1;
            zbar_symbol_t *ean_sym =
                _zbar_image_scanner_alloc_sym(iscn, ZBAR_COMPOSITE, datalen);
            ean_sym->orient = ean->orient;
            ean_sym->syms = _zbar_symbol_set_create();
            memcpy(ean_sym->data, ean->data, ean->datalen);
            memcpy(ean_sym->data + ean->datalen,
                   addon->data, addon->datalen + 1);
            ean_sym->syms->head = ean;
            ean->next = addon;
            ean_sym->syms->nsyms = 2;
            _zbar_image_scanner_add_sym(iscn, ean_sym);
        }
    }
    return syms;
}

int zbar_scan_image(zbar_image_scanner_t *iscn,
                    zbar_image_t *img)
{
    zbar_symbol_set_t *syms;
    zbar_image_t *inv = NULL;

    syms = _zbar_scan_image(iscn, img);
    if (!syms)
        return -1;

    if(!syms->nsyms && TEST_CFG(iscn, ZBAR_CFG_TEST_INVERTED)) {
        inv = _zbar_image_copy (img, 1);
        if (inv) {
            if(iscn->cache) {
                /* recycle all cached syms, if any */
                _zbar_image_scanner_recycle_syms(iscn, iscn->cache);
                iscn->cache = NULL;
            }
            syms = _zbar_scan_image(iscn, inv);
            _zbar_image_swap_symbols(img, inv);
        }
    }

    if(syms->nsyms && iscn->handler)
        iscn->handler(img, iscn->userdata);
#ifdef HAVE_DBUS
    if(iscn->is_dbus_enabled)
        zbar_send_code_via_dbus(img);
#endif

    svg_close();

    if (inv)
        zbar_image_destroy(inv);

    return(syms->nsyms);
}

int zbar_image_scanner_request_dbus(zbar_image_scanner_t *scanner,
                                    int req_dbus_enabled)
{
#ifdef HAVE_DBUS
    scanner->is_dbus_enabled = req_dbus_enabled;
    return 0;
#else
    return 1;
#endif
}

#ifdef DEBUG_SVG
/* FIXME lame...*/
# include "svg.c"
#endif
