/*
 * SpanDSP - a series of DSP components for telephony
 *
 * t43.c - ITU T.43 JBIG for grey and colour FAX image processing
 *
 * Written by Steve Underwood <steveu@coppice.org>
 *
 * Copyright (C) 2011, 2013 Steve Underwood
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1,
 * as published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*! \file */

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <tiffio.h>
#if defined(HAVE_TGMATH_H)
#include <tgmath.h>
#endif
#if defined(HAVE_MATH_H)
#include <math.h>
#endif
#if defined(HAVE_STDBOOL_H)
#include <stdbool.h>
#else
#include "spandsp/stdbool.h"
#endif
#include <time.h>
#include "floating_fudge.h"
#include <setjmp.h>

#include "spandsp/telephony.h"
#include "spandsp/alloc.h"
#include "spandsp/logging.h"
#include "spandsp/async.h"
#include "spandsp/timezone.h"
#include "spandsp/t4_rx.h"
#include "spandsp/t4_tx.h"
#include "spandsp/t81_t82_arith_coding.h"
#include "spandsp/t85.h"
#include "spandsp/t42.h"
#include "spandsp/t43.h"

#include "spandsp/private/logging.h"
#include "spandsp/private/t81_t82_arith_coding.h"
#include "spandsp/private/t85.h"
#include "spandsp/private/t42.h"
#include "spandsp/private/t43.h"

#include "t43_gray_code_tables.h"
#include "t42_t43_local.h"

SPAN_DECLARE(const char *) t43_image_type_to_str(int type)
{
    switch (type)
    {
    case T43_IMAGE_TYPE_RGB_BILEVEL:
        return "1 bit/colour image (RGB primaries)";
    case T43_IMAGE_TYPE_CMY_BILEVEL:
        return "1 bit/colour image (CMY primaries)";
    case T43_IMAGE_TYPE_CMYK_BILEVEL:
        return "1 bit/colour image (CMYK primaries)";
    case T43_IMAGE_TYPE_8BIT_COLOUR_PALETTE:
        return "Palettized colour image (CIELAB 8 bits/component precision table)";
    case T43_IMAGE_TYPE_12BIT_COLOUR_PALETTE:
        return "Palettized colour image (CIELAB 12 bits/component precision table)";
    case T43_IMAGE_TYPE_GRAY:
        return "Gray-scale image (using L*)";
    case T43_IMAGE_TYPE_COLOUR:
        return "Continuous-tone colour image (CIELAB)";
    }
    return "???";
}
/*- End of function --------------------------------------------------------*/

static __inline__ uint16_t pack_16(const uint8_t *s)
{
    uint16_t value;

    value = ((uint16_t) s[0] << 8) | (uint16_t) s[1];
    return value;
}
/*- End of function --------------------------------------------------------*/

static __inline__ uint32_t pack_32(const uint8_t *s)
{
    uint32_t value;

    value = ((uint32_t) s[0] << 24) | ((uint32_t) s[1] << 16) | ((uint32_t) s[2] << 8) | (uint32_t) s[3];
    return value;
}
/*- End of function --------------------------------------------------------*/

#if 0
static __inline__ int unpack_16(uint8_t *s, uint16_t value)
{
    s[0] = (value >> 8) & 0xFF;
    s[1] = value & 0xFF;
    return sizeof(uint16_t);
}
/*- End of function --------------------------------------------------------*/

static __inline__ int unpack_32(uint8_t *s, uint32_t value)
{
    s[0] = (value >> 24) & 0xFF;
    s[1] = (value >> 16) & 0xFF;
    s[2] = (value >> 8) & 0xFF;
    s[3] = value & 0xFF;
    return sizeof(uint16_t);
}
/*- End of function --------------------------------------------------------*/

static int t43_create_header(t43_encode_state_t *s, uint8_t data[], size_t len)
{
    int pos;
    int val[6];
#if 0
    int bytes_per_entry;
#endif

    pos = 0;
    unpack_16(data, 0xFFA8);
    pos += 2;

    span_log(&s->logging, SPAN_LOG_FLOW, "Putting G3FAX0\n");
    unpack_16(&data[pos], 0xFFE1);
    pos += 2;
    unpack_16(&data[pos], 2 + 6 + 10);
    pos += 2;
    memcpy(&data[pos], "G3FAX\0", 6);
    pos += 6;
    unpack_16(&data[pos], 1997);
    pos += 2;
    unpack_16(&data[pos], s->spatial_resolution);
    pos += 2;
    /* JBIG coding method (0) is the only possible value here */
    data[pos] = 0;
    pos += 1;
    data[pos] = s->image_type;
    pos += 1;
    data[pos] = s->bit_planes[0];
    pos += 1;
    data[pos] = s->bit_planes[1];
    pos += 1;
    data[pos] = s->bit_planes[2];
    pos += 1;
    data[pos] = s->bit_planes[3];
    pos += 1;

    if (s->lab.offset_L != 0
        ||
        s->lab.range_L != 100
        ||
        s->lab.offset_a != 128
        ||
        s->lab.range_a != 170
        ||
        s->lab.offset_b != 96
        ||
        s->lab.range_b != 200)
    {
        span_log(&s->logging, SPAN_LOG_FLOW, "Putting G3FAX1\n");
        unpack_16(&data[pos], 0xFFE1);
        pos += 2;
        unpack_16(&data[pos], 2 + 6 + 12);
        pos += 2;
        memcpy(&data[pos], "G3FAX\1", 6);
        pos += 6;
        get_lab_gamut2(&s->lab, &val[0], &val[1], &val[2], &val[3], &val[4], &val[5]);
        unpack_16(&data[pos + 0], val[0]);
        unpack_16(&data[pos + 2], val[1]);
        unpack_16(&data[pos + 4], val[2]);
        unpack_16(&data[pos + 6], val[3]);
        unpack_16(&data[pos + 8], val[4]);
        unpack_16(&data[pos + 10], val[5]);
        pos += 12;
    }

    if (memcmp(s->illuminant_code, "\0\0\0\0", 4) != 0
        ||
        s->illuminant_colour_temperature > 0)
    {
        span_log(&s->logging, SPAN_LOG_FLOW, "Putting G3FAX2\n");
        unpack_16(&data[pos], 0xFFE1);
        pos += 2;
        unpack_16(&data[pos], 2 + 6 + 4);
        pos += 2;
        memcpy(&data[pos], "G3FAX\2", 6);
        pos += 6;
        if (memcmp(s->illuminant_code, "\0\0\0\0", 4) != 0)
        {
            memcpy(&data[pos], s->illuminant_code, 4);
        }
        else
        {
            memcpy(&data[pos], "CT", 2);
            unpack_16(&data[pos + 2], s->illuminant_colour_temperature);
        }
        pos += 4;
    }

#if 0
    if (s->colour_map)
    {
        span_log(&s->logging, SPAN_LOG_FLOW, "Putting G3FAX3\n");
        bytes_per_entry = (table_id == 0)  ?  1  :  2;
        unpack_16(&data[pos], 0xFFE3);
        pos += 2;
        unpack_32(&data[pos], 2 + 6 + 2 + 4 + 3*s->colour_map_entries*bytes_per_entry);
        pos += 4;
        memcpy(&data[pos], "G3FAX\3", 6);
        pos += 6;
        unpack_16(&data[pos], table_id);
        pos += 2;
        unpack_32(&data[pos], s->colour_map_entries);
        pos += 4;
        srgb_to_lab(&s->lab, &data[pos], s->colour_map, s->colour_map_entries);
        pos += 3*s->colour_map_entries*bytes_per_entry;
    }
#endif

    span_log(&s->logging, SPAN_LOG_FLOW, "Putting G3FAX-FF\n");
    unpack_16(&data[pos], 0xFFE1);
    pos += 2;
    unpack_16(&data[pos], 2 + 6);
    pos += 2;
    memcpy(&data[pos], "G3FAX\xFF", 6);
    pos += 6;
    return pos;
}
/*- End of function --------------------------------------------------------*/
#endif

SPAN_DECLARE(void) t43_encode_set_options(t43_encode_state_t *s,
                                          uint32_t l0,
                                          int mx,
                                          int options)
{
    t85_encode_set_options(&s->t85, l0, mx, options);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_set_image_width(t43_encode_state_t *s, uint32_t image_width)
{
    return t85_encode_set_image_width(&s->t85, image_width);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_set_image_length(t43_encode_state_t *s, uint32_t image_length)
{
    return t85_encode_set_image_length(&s->t85, image_length);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_set_image_type(t43_encode_state_t *s, int image_type)
{
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t43_encode_abort(t43_encode_state_t *s)
{
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t43_encode_comment(t43_encode_state_t *s, const uint8_t comment[], size_t len)
{
    t85_encode_comment(&s->t85, comment, len);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_image_complete(t43_encode_state_t *s)
{
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_get(t43_encode_state_t *s, uint8_t buf[], size_t max_len)
{
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(uint32_t) t43_encode_get_image_width(t43_encode_state_t *s)
{
    return t85_encode_get_image_width(&s->t85);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(uint32_t) t43_encode_get_image_length(t43_encode_state_t *s)
{
    return t85_encode_get_image_length(&s->t85);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_get_compressed_image_size(t43_encode_state_t *s)
{
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_set_row_read_handler(t43_encode_state_t *s,
                                                  t4_row_read_handler_t handler,
                                                  void *user_data)
{
    s->row_read_handler = handler;
    s->row_read_user_data = user_data;
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(logging_state_t *) t43_encode_get_logging_state(t43_encode_state_t *s)
{
    return &s->logging;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_restart(t43_encode_state_t *s, uint32_t image_width, uint32_t image_length)
{
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(t43_encode_state_t *) t43_encode_init(t43_encode_state_t *s,
                                                   uint32_t image_width,
                                                   uint32_t image_length,
                                                   t4_row_read_handler_t handler,
                                                   void *user_data)
{
    if (s == NULL)
    {
        if ((s = (t43_encode_state_t *) span_alloc(sizeof(*s))) == NULL)
            return NULL;
    }
    memset(s, 0, sizeof(*s));
    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
    span_log_set_protocol(&s->logging, "T.43");

    s->row_read_handler = handler;
    s->row_read_user_data = user_data;

    t85_encode_init(&s->t85,
                    image_width,
                    image_length,
                    handler,
                    user_data);

    s->image_type = T43_IMAGE_TYPE_8BIT_COLOUR_PALETTE;

    return s;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_release(t43_encode_state_t *s)
{
    t85_encode_release(&s->t85);
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_encode_free(t43_encode_state_t *s)
{
    int ret;

    t85_encode_release(&s->t85);
    ret = t43_encode_release(s);
    span_free(s);
    return ret;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t43_decode_rx_status(t43_decode_state_t *s, int status)
{
    span_log(&s->logging, SPAN_LOG_FLOW, "Signal status is %s (%d)\n", signal_status_to_str(status), status);
    switch (status)
    {
    case SIG_STATUS_TRAINING_IN_PROGRESS:
    case SIG_STATUS_TRAINING_FAILED:
    case SIG_STATUS_TRAINING_SUCCEEDED:
    case SIG_STATUS_CARRIER_UP:
        /* Ignore these */
        break;
    case SIG_STATUS_CARRIER_DOWN:
    case SIG_STATUS_END_OF_DATA:
        /* Finalise the image */
        t85_decode_put(&s->t85, NULL, 0);
        break;
    default:
        span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected rx status - %d!\n", status);
        break;
    }
}
/*- End of function --------------------------------------------------------*/

static void set_simple_colour_map(t43_decode_state_t *s, int code)
{
    int i;

    switch (code)
    {
    case T43_IMAGE_TYPE_RGB_BILEVEL:
        /* Table 3/T.43 1 bit/colour image (using RGB primaries) */
        memset(s->colour_map, 0, sizeof(s->colour_map));
        /* Black */
        /* Blue */
        s->colour_map[3*0x20 + 2] = 0xF0;
        /* Green */
        s->colour_map[3*0x40 + 1] = 0xF0;
        /* Green + Blue */
        s->colour_map[3*0x60 + 1] = 0xF0;
        s->colour_map[3*0x60 + 2] = 0xF0;
        /* Red */
        s->colour_map[3*0x80 + 0] = 0xF0;
        /* Red + Blue */
        s->colour_map[3*0xA0 + 0] = 0xF0;
        s->colour_map[3*0xA0 + 2] = 0xF0;
        /* Red + Green */
        s->colour_map[3*0xC0 + 0] = 0xF0;
        s->colour_map[3*0xC0 + 1] = 0xF0;
        /* White */
        s->colour_map[3*0xE0 + 0] = 0xF0;
        s->colour_map[3*0xE0 + 1] = 0xF0;
        s->colour_map[3*0xE0 + 2] = 0xF0;
        s->colour_map_entries = 256;
        break;
    case T43_IMAGE_TYPE_CMY_BILEVEL:
        /* Table 2/T.43 1 bit/colour image (using CMY primaries) */
        memset(s->colour_map, 0, sizeof(s->colour_map));
        /* White */
        s->colour_map[3*0x00 + 0] = 0xF0;
        s->colour_map[3*0x00 + 1] = 0xF0;
        s->colour_map[3*0x00 + 2] = 0xF0;
        /* Yellow */
        s->colour_map[3*0x20 + 0] = 0xF0;
        s->colour_map[3*0x20 + 1] = 0xF0;
        /* Magenta */
        s->colour_map[3*0x40 + 0] = 0xF0;
        s->colour_map[3*0x40 + 2] = 0xF0;
        /* Magenta + Yellow */
        s->colour_map[3*0x60 + 0] = 0xF0;
        /* Cyan */
        s->colour_map[3*0x80 + 1] = 0xF0;
        /* Cyan + Yellow */
        s->colour_map[3*0xA0 + 1] = 0xF0;
        /* Cyan + Magenta */
        s->colour_map[3*0xC0 + 2] = 0xF0;
        /* Black */
        s->colour_map_entries = 256;
        break;
    case T43_IMAGE_TYPE_CMYK_BILEVEL:
        /* Table 1/T.43 1 bit/colour image (using CMYK primaries) */
        memset(s->colour_map, 0, sizeof(s->colour_map));
        /* White */
        s->colour_map[3*0x00 + 0] = 0xF0;
        s->colour_map[3*0x00 + 1] = 0xF0;
        s->colour_map[3*0x00 + 2] = 0xF0;
        /* Yellow */
        s->colour_map[3*0x20 + 0] = 0xF0;
        s->colour_map[3*0x20 + 1] = 0xF0;
        /* Magenta */
        s->colour_map[3*0x40 + 0] = 0xF0;
        s->colour_map[3*0x40 + 2] = 0xF0;
        /* Magenta + Yellow */
        s->colour_map[3*0x60 + 0] = 0xF0;
        /* Cyan */
        s->colour_map[3*0x80 + 1] = 0xF0;
        /* Cyan + Yellow */
        s->colour_map[3*0xA0 + 1] = 0xF0;
        /* Cyan + Magenta */
        s->colour_map[3*0xC0 + 2] = 0xF0;
        /* Black */
        s->colour_map_entries = 256;
        break;
    case T43_IMAGE_TYPE_8BIT_COLOUR_PALETTE:
        /* Palettized colour image (using CIELAB 8 bits/component precision table) */
        for (i = 0;  i < 3*256;  i += 3)
        {
            s->colour_map[i + 0] = i;
            s->colour_map[i + 1] = i;
            s->colour_map[i + 2] = i;
        }
        s->colour_map_entries = 256;
        break;
    case T43_IMAGE_TYPE_12BIT_COLOUR_PALETTE:
        /* Palettized colour image (using CIELAB 12 bits/component precision table) */
        break;
    case T43_IMAGE_TYPE_GRAY:
        /* Gray-scale image (using L*) */
        for (i = 0;  i < 256;  i++)
            s->colour_map[i] = i;
        s->colour_map_entries = 256;
        break;
    case T43_IMAGE_TYPE_COLOUR:
        /* Continuous-tone colour image (using CIELAB) */
        break;
    }
}
/*- End of function --------------------------------------------------------*/

static int t43_analyse_header(t43_decode_state_t *s, const uint8_t data[], size_t len)
{
    int seg;
    int pos;
    int table_id;
    int val[6];
    uint8_t col[3];
    int i;

    pos = 0;
    if (pack_16(&data[pos]) != 0xFFA8)
        return 0;
    span_log(&s->logging, SPAN_LOG_FLOW, "Got BCIH (bit-plane colour image header)\n");
    pos += 2;
    for (;;)
    {
        if (pack_16(&data[pos]) == 0xFFE1)
        {
            pos += 2;
            seg = pack_16(&data[pos]);
            pos += 2;
            seg -= 2;
            if (seg >= 6  &&  strncmp((char *) &data[pos], "G3FAX", 5) == 0)
            {
                if (data[pos + 5] == 0xFF)
                {
                    span_log(&s->logging, SPAN_LOG_FLOW, "Got ECIH (end of colour image header)\n");
                    if (seg != 6)
                        span_log(&s->logging, SPAN_LOG_FLOW, "Got bad ECIH length - %d\n", seg);
                    pos += seg;
                    break;
                }
                switch (data[pos + 5])
                {
                case 0:
                    span_log(&s->logging, SPAN_LOG_FLOW, "Got G3FAX0\n");
                    if (seg < 6 + 10)
                    {
                        span_log(&s->logging, SPAN_LOG_FLOW, "Got bad G3FAX0 length - %d\n", seg);
                    }
                    else
                    {
                        val[0] = pack_16(&data[pos + 6 + 0]);
                        s->spatial_resolution = pack_16(&data[pos + 6 + 2]);
                        val[2] = data[pos + 6 + 4];
                        s->image_type = data[pos + 6 + 5];
                        s->bit_planes[0] = data[pos + 6 + 6];
                        s->bit_planes[1] = data[pos + 6 + 7];
                        s->bit_planes[2] = data[pos + 6 + 8];
                        s->bit_planes[3] = data[pos + 6 + 9];
                        if (s->image_type == T43_IMAGE_TYPE_GRAY)
                        {
                            s->samples_per_pixel = 1;
                        }
                        else if (s->image_type == T43_IMAGE_TYPE_CMYK_BILEVEL)
                        {
                            s->samples_per_pixel = 4;
                        }
                        else
                        {
                            s->samples_per_pixel = 3;
                        }
                        span_log(&s->logging,
                                 SPAN_LOG_FLOW,
                                 "Version %d, resolution %ddpi, coding method %d, type %s (%d), bit planes %d,%d,%d,%d\n",
                                 val[0],
                                 s->spatial_resolution,
                                 val[2],
                                 t43_image_type_to_str(s->image_type),
                                 s->image_type,
                                 s->bit_planes[0],
                                 s->bit_planes[1],
                                 s->bit_planes[2],
                                 s->bit_planes[3]);
                        set_simple_colour_map(s, s->image_type);
                    }
                    break;
                case 1:
                    span_log(&s->logging, SPAN_LOG_FLOW, "Set gamut\n");
                    if (seg < 6 + 12)
                    {
                        span_log(&s->logging, SPAN_LOG_FLOW, "Got bad G3FAX1 length - %d\n", seg);
                    }
                    else
                    {
                        set_gamut_from_code(&s->logging, &s->lab, &data[pos + 6]);
                    }
                    break;
                case 2:
                    span_log(&s->logging, SPAN_LOG_FLOW, "Set illuminant\n");
                    if (seg < 6 + 4)
                    {
                        span_log(&s->logging, SPAN_LOG_FLOW, "Got bad G3FAX2 length - %d\n", seg);
                    }
                    else
                    {
                        s->illuminant_colour_temperature = set_illuminant_from_code(&s->logging, &s->lab, &data[pos + 6]);
                    }
                    break;
                default:
                    span_log(&s->logging, SPAN_LOG_FLOW, "Got unexpected G3FAX%d length - %d\n", data[pos + 5], seg);
                    break;
                }
            }
            pos += seg;
        }
        else if (pack_16(&data[pos]) == 0xFFE3)
        {
            pos += 2;
            seg = pack_32(&data[pos]);
            pos += 4;
            seg -= 4;
            if (seg >= 6)
            {
                if (strncmp((char *) &data[pos], "G3FAX\3", 6) == 0)
                {
                    span_log(&s->logging, SPAN_LOG_FLOW, "Got G3FAX3\n");
                    table_id = pack_16(&data[pos + 6]);
                    span_log(&s->logging, SPAN_LOG_FLOW, "  Table ID %3d\n", table_id);
                    switch (table_id)
                    {
                    case 0:
                        /* 8 bit CIELAB */
                        s->colour_map_entries = pack_32(&data[pos + 8]);
                        span_log(&s->logging, SPAN_LOG_FLOW, "  Entries %6d (len %d)\n", s->colour_map_entries, seg);
                        if (seg >= 12 + s->colour_map_entries*3)
                        {
                            lab_to_srgb(&s->lab, s->colour_map, &data[pos + 12], s->colour_map_entries);
                        }
                        else
                        {
                            span_log(&s->logging, SPAN_LOG_FLOW, "Got bad G3FAX3 length - %d\n", seg);
                        }
                        break;
                    case 4:
                        /* 12 bit CIELAB */
                        s->colour_map_entries = pack_32(&data[pos + 8]);
                        span_log(&s->logging, SPAN_LOG_FLOW, "  Entries %6d\n", s->colour_map_entries);
                        /* TODO: implement 12bit stuff */
                        if (seg >= 12 + s->colour_map_entries*3*2)
                        {
                            for (i = 0;  i < s->colour_map_entries;  i++)
                            {
                                col[0] = pack_16(&data[pos + 12 + 6*i]) >> 4;
                                col[1] = pack_16(&data[pos + 12 + 6*i + 2]) >> 4;
                                col[2] = pack_16(&data[pos + 12 + 6*i + 4]) >> 4;
                                lab_to_srgb(&s->lab, &s->colour_map[3*i], col, 1);
                            }
                        }
                        else
                        {
                            span_log(&s->logging, SPAN_LOG_FLOW, "Got bad G3FAX3 length - %d\n", seg);
                        }
                        break;
                    default:
                        span_log(&s->logging, SPAN_LOG_FLOW, "Got bad G3FAX3 table ID - %d\n", table_id);
                        break;
                    }
                }
            }
            pos += seg;
        }
        else
        {
            break;
        }
    }
    return pos;
}
/*- End of function --------------------------------------------------------*/

static int t85_row_write_handler(void *user_data, const uint8_t buf[], size_t len)
{
    t43_decode_state_t *s;
    int i;
    int j;
    int image_size;
    uint8_t mask;

    /* Repack the bit packed T.85 image plane to a 3 x 8 bits per pixel colour image.
       We use the red entry now. This will be remapped to RGB later, as we apply the
       colour map. */
    s = (t43_decode_state_t *) user_data;

    if (s->buf == NULL)
    {
        image_size = s->samples_per_pixel*s->t85.xd*s->t85.yd;
        if ((s->buf = span_alloc(image_size)) == NULL)
            return -1;
        memset(s->buf, 0, image_size);
    }

    for (i = 0;  i < len;  i++)
    {
        mask = 0x80;
        if (s->samples_per_pixel == 1)
        {
            for (j = 0;  j < 8;  j += s->samples_per_pixel)
            {
                if ((buf[i] & mask))
                    s->buf[s->ptr + j] |= s->bit_plane_mask;
                mask >>= 1;
            }
        }
        else
        {
            for (j = 0;  j < s->samples_per_pixel*8;  j += s->samples_per_pixel)
            {
                if ((buf[i] & mask))
                    s->buf[s->ptr + j] |= s->bit_plane_mask;
                mask >>= 1;
            }
        }
        s->ptr += s->samples_per_pixel*8;
    }
    s->row++;
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_put(t43_decode_state_t *s, const uint8_t data[], size_t len)
{
    int i;
    int j;
    int plane_len;
    int total_len;
    int result;

    /* TODO: this isn't allowing for the header arriving in chunks */
    if (s->current_bit_plane < 0)
    {
        i = t43_analyse_header(s, data, len);
        data += i;
        len -= i;
        s->bit_plane_mask = 0x80;
        s->current_bit_plane++;

        /* There must be at least one bit plane. The real value for this will
           be filled in as the first plane is processed */
        s->t85.bit_planes = 1;
        s->ptr = 0;
        s->row = 0;
        s->buf = NULL;
        s->plane_ptr = 0;
        t85_decode_new_plane(&s->t85);
    }

    /* Now deal the bit-planes, one after another. */
    total_len = 0;
    result = 0;
    while (s->current_bit_plane < s->t85.bit_planes)
    {
        result = t85_decode_put(&s->t85, data, len);
        if (result != T4_DECODE_OK)
        {
            s->plane_ptr += len;
            return result;
        }
        plane_len = t85_decode_get_compressed_image_size(&s->t85);
        data += (plane_len/8 - s->plane_ptr);
        len -= (plane_len/8 - s->plane_ptr);
        total_len = s->ptr;

        /* Start the next plane */
        s->bit_plane_mask >>= 1;
        s->ptr = 0;
        s->row = 0;
        s->plane_ptr = 0;
        s->current_bit_plane++;
        t85_decode_new_plane(&s->t85);
    }
    /* Apply the colour map, and produce the RGB data from the collected bit-planes */
    if (s->samples_per_pixel == 1)
    {
        for (j = 0;  j < total_len;  j += s->samples_per_pixel)
            s->buf[j] = s->colour_map[s->buf[j]];
    }
    else
    {
        for (j = 0;  j < total_len;  j += s->samples_per_pixel)
        {
            i = s->buf[j];
            s->buf[j] = s->colour_map[3*i];
            s->buf[j + 1] = s->colour_map[3*i + 1];
            s->buf[j + 2] = s->colour_map[3*i + 2];
        }
    }
    for (j = 0;  j < s->t85.yd;  j++)
        s->row_write_handler(s->row_write_user_data, &s->buf[j*s->samples_per_pixel*s->t85.xd], s->samples_per_pixel*s->t85.xd);
    return result;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_set_row_write_handler(t43_decode_state_t *s,
                                                   t4_row_write_handler_t handler,
                                                   void *user_data)
{
    s->row_write_handler = handler;
    s->row_write_user_data = user_data;
    s->t85.row_write_handler = handler;
    s->t85.row_write_user_data = user_data;
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_set_comment_handler(t43_decode_state_t *s,
                                                 uint32_t max_comment_len,
                                                 t4_row_write_handler_t handler,
                                                 void *user_data)
{
    return t85_decode_set_comment_handler(&s->t85, max_comment_len, handler, user_data);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_set_image_size_constraints(t43_decode_state_t *s,
                                                        uint32_t max_xd,
                                                        uint32_t max_yd)
{
    return t85_decode_set_image_size_constraints(&s->t85, max_xd, max_yd);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(uint32_t) t43_decode_get_image_width(t43_decode_state_t *s)
{
    return t85_decode_get_image_width(&s->t85);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(uint32_t) t43_decode_get_image_length(t43_decode_state_t *s)
{
    return t85_decode_get_image_length(&s->t85);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_get_compressed_image_size(t43_decode_state_t *s)
{
    return t85_decode_get_compressed_image_size(&s->t85);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(logging_state_t *) t43_decode_get_logging_state(t43_decode_state_t *s)
{
    return &s->logging;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_restart(t43_decode_state_t *s)
{
    /* ITULAB */
    /* Illuminant D50 */
    //set_lab_illuminant(&s->lab, 96.422f, 100.000f,  82.521f);
    set_lab_illuminant(&s->lab, 100.0f, 100.0f, 100.0f);
    set_lab_gamut(&s->lab, 0, 100, -85, 85, -75, 125, false);

    s->t85.min_bit_planes = 1;
    s->t85.max_bit_planes = 8;
    s->bit_plane_mask = 0x80;
    s->current_bit_plane = -1;
    s->image_type = T43_IMAGE_TYPE_8BIT_COLOUR_PALETTE;

    return t85_decode_restart(&s->t85);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(t43_decode_state_t *) t43_decode_init(t43_decode_state_t *s,
                                                   t4_row_write_handler_t handler,
                                                   void *user_data)
{
    if (s == NULL)
    {
        if ((s = (t43_decode_state_t *) span_alloc(sizeof(*s))) == NULL)
            return NULL;
    }
    memset(s, 0, sizeof(*s));
    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
    span_log_set_protocol(&s->logging, "T.43");

    s->row_write_handler = handler;
    s->row_write_user_data = user_data;

    t85_decode_init(&s->t85, t85_row_write_handler, s);

    /* ITULAB */
    /* Illuminant D50 */
    //set_lab_illuminant(&s->lab, 96.422f, 100.000f,  82.521f);
    set_lab_illuminant(&s->lab, 100.0f, 100.0f, 100.0f);
    set_lab_gamut(&s->lab, 0, 100, -85, 85, -75, 125, false);

    s->t85.min_bit_planes = 1;
    s->t85.max_bit_planes = 8;
    s->bit_plane_mask = 0x80;
    s->current_bit_plane = -1;
    s->image_type = T43_IMAGE_TYPE_8BIT_COLOUR_PALETTE;

    return s;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_release(t43_decode_state_t *s)
{
    t85_decode_release(&s->t85);
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t43_decode_free(t43_decode_state_t *s)
{
    int ret;

    ret = t43_decode_release(s);
    t85_decode_free(&s->t85);
    span_free(s);
    return ret;
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/
