#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include <qr/bitmap.h>

struct qr_bitmap * qr_bitmap_create(int width, int height, int masked)
{
        struct qr_bitmap * out;
        size_t size;

        out = malloc(sizeof(*out));
        if (!out)
                goto fail;

        out->width = width;
        out->height = height;
        out->stride = (width / CHAR_BIT) + (width % CHAR_BIT ? 1 : 0);

        size = out->stride * height;

        out->mask = 0;
        out->bits = malloc(size);
        if (!out->bits)
                goto fail;
        memset(out->bits, 0, size);

        if (masked) {
                out->mask = malloc(out->stride * width);
                if (!out->mask)
                        goto fail;
                memset(out->mask, 0xFF, size);
        }

        return out;

fail:
        qr_bitmap_destroy(out);
        return 0;
}

void qr_bitmap_destroy(struct qr_bitmap * bmp)
{
        if (bmp) {
                free(bmp->bits);
                free(bmp->mask);
                free(bmp);
        }
}

struct qr_bitmap * qr_bitmap_clone(const struct qr_bitmap * src)
{
        struct qr_bitmap * bmp;
        size_t size;

        bmp = qr_bitmap_create(src->width, src->height, !!src->mask);
        if (!bmp)
                return 0;

        assert(bmp->stride == src->stride);

        size = bmp->width * bmp->stride;
        memcpy(bmp->bits, src->bits, size);
        if (bmp->mask)
                memcpy(bmp->mask, src->mask, size);

        return bmp;
}

void qr_bitmap_merge(struct qr_bitmap * dest, const struct qr_bitmap * src)
{
        unsigned char * d, * s, * m;
        size_t n;

        assert(dest->stride == src->stride);
        assert(dest->height == src->height);
        assert(src->mask);

        n = dest->stride * dest->height;
        d = dest->bits;
        s = src->bits;
        m = src->mask;

        while (n--) {
                *d   &= ~*m;
                *d++ |= *s++ & *m++;
        }
}

/* CHAR_BIT | mod_bits  (multi-byte) */
static void render_line_1(unsigned char *       out,
                          const unsigned char * in,
                          size_t                mod_bits,
                          size_t                dim,
                          unsigned long         mark,
                          unsigned long         space)
{
        unsigned char in_mask;
        size_t n, b;

        in_mask = 1;
        n = dim;

        while (n-- > 0) {
                unsigned long v = (*in & in_mask) ? mark : space;

                if ((in_mask <<= 1) == 0) {
                        in_mask = 1;
                        ++in;
                }

                b = mod_bits / CHAR_BIT;
                while (b-- > 0) {
                        *out++ = (unsigned char) v;
                        v >>= CHAR_BIT;
                }
        }
}

/* mod_bits | CHAR_BIT  (packed) */
static void render_line_2(unsigned char *       out,
                          const unsigned char * in,
                          size_t                mod_bits,
                          size_t                dim,
                          unsigned long         mark,
                          unsigned long         space)
{
        unsigned char in_mask;
        size_t n, b, step, shift;

        in_mask = 1;
        step = CHAR_BIT / mod_bits;
        shift = CHAR_BIT - mod_bits;
        n = dim;

        while (n > 0) {
                unsigned char tmp = 0;

                b = step;
                while (b-- > 0) {
                        unsigned long v = (*in & in_mask) ? mark : space;

                        if ((in_mask <<= 1) == 0) {
                                in_mask = 1;
                                ++in;
                        }

                        tmp = (tmp >> mod_bits) | (v << shift);
                        if (--n == 0) {
                                tmp >>= b * mod_bits;
                                break;
                        }
                };

                *out++ = tmp;
        }
}

void qr_bitmap_render(const struct qr_bitmap * bmp,
                      void *                   buffer,
                      size_t                   mod_bits,
                      size_t                   line_stride,
                      size_t                   line_repeat,
                      unsigned long            mark,
                      unsigned long            space)
{
        unsigned char * out;
        const unsigned char * in;
        size_t n, dim;
        int pack;

        pack = (mod_bits < CHAR_BIT);
        assert(!pack || (CHAR_BIT % mod_bits == 0));
        assert( pack || (mod_bits % CHAR_BIT == 0));

        in = bmp->bits;
        out = buffer;
        dim = bmp->width;

        n = dim;
        while (n-- > 0) {
                size_t rpt;
                unsigned char * next;

                if (pack)
                        render_line_2(out, in, mod_bits, dim, mark, space);
                else
                        render_line_1(out, in, mod_bits, dim, mark, space);

                rpt = line_repeat;
                next = out + line_stride;
                while (rpt-- > 0) {
                        memcpy(next, out, line_stride);
                        next += line_stride;
                }

                in += bmp->stride;
                out = next;
        }
}