/*
    jbig2dec
    
    Copyright (C) 2002-2005 Artifex Software, Inc.
    
    This software is provided AS-IS with no warranty,
    either express or implied.
                                                                                
    This software is distributed under license and may not
    be copied, modified or distributed except as expressly
    authorized under the terms of the license contained in
    the file LICENSE in this distribution.
                                                                                
    For information on commercial licensing, go to
    http://www.artifex.com/licensing/ or contact
    Artifex Software, Inc.,  101 Lucas Valley Road #110,
    San Rafael, CA  94903, U.S.A., +1(415)492-9861.
                                                                                
    $Id: jbig2_generic.c 8320 2007-10-25 22:14:22Z giles $
*/

/**
 * Generic region handlers.
 **/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif 
#include "os_types.h"

#include <stddef.h>
#include <string.h> /* memcpy(), memset() */

#ifdef OUTPUT_PBM
#include <stdio.h>
#endif

#include "jbig2.h"
#include "jbig2_priv.h"
#include "jbig2_image.h"
#include "jbig2_arith.h"
#include "jbig2_generic.h"
#include "jbig2_mmr.h"

/* return the appropriate context size for the given template */
int
jbig2_generic_stats_size(Jbig2Ctx *ctx, int template)
{
  int stats_size = template == 0 ? 1 << 16 :
        template == 1 ? 1 << 1 << 13 : 1 << 10;
  return stats_size;
}


static int
jbig2_decode_generic_template0(Jbig2Ctx *ctx,
			       Jbig2Segment *segment,
			       const Jbig2GenericRegionParams *params,
			       Jbig2ArithState *as,
			       Jbig2Image *image,
			       Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  const int rowstride = image->stride;
  int x, y;
  byte *gbreg_line = (byte *)image->data;

  /* todo: currently we only handle the nominal gbat location */

#ifdef OUTPUT_PBM
  printf("P4\n%d %d\n", GBW, GBH);
#endif

  for (y = 0; y < GBH; y++)
    {
      uint32_t CONTEXT;
      uint32_t line_m1;
      uint32_t line_m2;
      int padded_width = (GBW + 7) & -8;

      line_m1 = (y >= 1) ? gbreg_line[-rowstride] : 0;
      line_m2 = (y >= 2) ? gbreg_line[-(rowstride << 1)] << 6 : 0;
      CONTEXT = (line_m1 & 0x7f0) | (line_m2 & 0xf800);

      /* 6.2.5.7 3d */
      for (x = 0; x < padded_width; x += 8)
	{
	  byte result = 0;
	  int x_minor;
	  int minor_width = GBW - x > 8 ? 8 : GBW - x;

	  if (y >= 1)
	    line_m1 = (line_m1 << 8) |
	      (x + 8 < GBW ? gbreg_line[-rowstride + (x >> 3) + 1] : 0);

	  if (y >= 2)
	    line_m2 = (line_m2 << 8) |
	      (x + 8 < GBW ? gbreg_line[-(rowstride << 1) + (x >> 3) + 1] << 6: 0);

	  /* This is the speed-critical inner loop. */
	  for (x_minor = 0; x_minor < minor_width; x_minor++)
	    {
	      bool bit;

	      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
	      result |= bit << (7 - x_minor);
	      CONTEXT = ((CONTEXT & 0x7bf7) << 1) | bit |
		((line_m1 >> (7 - x_minor)) & 0x10) |
		((line_m2 >> (7 - x_minor)) & 0x800);
	    }
	  gbreg_line[x >> 3] = result;
	}
#ifdef OUTPUT_PBM
      fwrite(gbreg_line, 1, rowstride, stdout);
#endif
      gbreg_line += rowstride;
    }

  return 0;
}

static int
jbig2_decode_generic_template0_unopt(Jbig2Ctx *ctx,
                               Jbig2Segment *segment,
                               const Jbig2GenericRegionParams *params,
                               Jbig2ArithState *as,
                               Jbig2Image *image,
                               Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  uint32_t CONTEXT;
  int x,y;
  bool bit;

  /* this version is generic and easy to understand, but very slow */

  for (y = 0; y < GBH; y++) {
    for (x = 0; x < GBW; x++) {
      CONTEXT = 0;
      CONTEXT |= jbig2_image_get_pixel(image, x - 1, y) << 0;
      CONTEXT |= jbig2_image_get_pixel(image, x - 2, y) << 1;
      CONTEXT |= jbig2_image_get_pixel(image, x - 3, y) << 2;
      CONTEXT |= jbig2_image_get_pixel(image, x - 4, y) << 3;
      CONTEXT |= jbig2_image_get_pixel(image, x + params->gbat[0],
	y + params->gbat[1]) << 4;
      CONTEXT |= jbig2_image_get_pixel(image, x + 2, y - 1) << 5;
      CONTEXT |= jbig2_image_get_pixel(image, x + 1, y - 1) << 6;
      CONTEXT |= jbig2_image_get_pixel(image, x + 0, y - 1) << 7;
      CONTEXT |= jbig2_image_get_pixel(image, x - 1, y - 1) << 8;
      CONTEXT |= jbig2_image_get_pixel(image, x - 2, y - 1) << 9;
      CONTEXT |= jbig2_image_get_pixel(image, x + params->gbat[2],
	y + params->gbat[3]) << 10;
      CONTEXT |= jbig2_image_get_pixel(image, x + params->gbat[4],
	y + params->gbat[5]) << 11;
      CONTEXT |= jbig2_image_get_pixel(image, x + 1, y - 2) << 12;
      CONTEXT |= jbig2_image_get_pixel(image, x + 0, y - 2) << 13;
      CONTEXT |= jbig2_image_get_pixel(image, x - 1, y - 2) << 14;
      CONTEXT |= jbig2_image_get_pixel(image, x + params->gbat[6],
	y + params->gbat[7]) << 15;
      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
      jbig2_image_set_pixel(image, x, y, bit);
    }
  }
  return 0;
}

static int
jbig2_decode_generic_template1(Jbig2Ctx *ctx,
			       Jbig2Segment *segment,
			       const Jbig2GenericRegionParams *params,
			       Jbig2ArithState *as,
			       Jbig2Image *image,
			       Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  const int rowstride = image->stride;
  int x, y;
  byte *gbreg_line = (byte *)image->data;

  /* todo: currently we only handle the nominal gbat location */

#ifdef OUTPUT_PBM
  printf("P4\n%d %d\n", GBW, GBH);
#endif

  for (y = 0; y < GBH; y++)
    {
      uint32_t CONTEXT;
      uint32_t line_m1;
      uint32_t line_m2;
      int padded_width = (GBW + 7) & -8;

      line_m1 = (y >= 1) ? gbreg_line[-rowstride] : 0;
      line_m2 = (y >= 2) ? gbreg_line[-(rowstride << 1)] << 5 : 0;
      CONTEXT = ((line_m1 >> 1) & 0x1f8) | ((line_m2 >> 1) & 0x1e00);

      /* 6.2.5.7 3d */
      for (x = 0; x < padded_width; x += 8)
	{
	  byte result = 0;
	  int x_minor;
	  int minor_width = GBW - x > 8 ? 8 : GBW - x;

	  if (y >= 1)
	    line_m1 = (line_m1 << 8) |
	      (x + 8 < GBW ? gbreg_line[-rowstride + (x >> 3) + 1] : 0);

	  if (y >= 2)
	    line_m2 = (line_m2 << 8) |
	      (x + 8 < GBW ? gbreg_line[-(rowstride << 1) + (x >> 3) + 1] << 5: 0);

	  /* This is the speed-critical inner loop. */
	  for (x_minor = 0; x_minor < minor_width; x_minor++)
	    {
	      bool bit;

	      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
	      result |= bit << (7 - x_minor);
	      CONTEXT = ((CONTEXT & 0xefb) << 1) | bit |
		((line_m1 >> (8 - x_minor)) & 0x8) |
		((line_m2 >> (8 - x_minor)) & 0x200);
	    }
	  gbreg_line[x >> 3] = result;
	}
#ifdef OUTPUT_PBM
      fwrite(gbreg_line, 1, rowstride, stdout);
#endif
      gbreg_line += rowstride;
    }

  return 0;
}

static int
jbig2_decode_generic_template2(Jbig2Ctx *ctx,
			       Jbig2Segment *segment,
			       const Jbig2GenericRegionParams *params,
			       Jbig2ArithState *as,
			       Jbig2Image *image,
			       Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  const int rowstride = image->stride;
  int x, y;
  byte *gbreg_line = (byte *)image->data;

  /* todo: currently we only handle the nominal gbat location */

#ifdef OUTPUT_PBM
  printf("P4\n%d %d\n", GBW, GBH);
#endif

  for (y = 0; y < GBH; y++)
    {
      uint32_t CONTEXT;
      uint32_t line_m1;
      uint32_t line_m2;
      int padded_width = (GBW + 7) & -8;

      line_m1 = (y >= 1) ? gbreg_line[-rowstride] : 0;
      line_m2 = (y >= 2) ? gbreg_line[-(rowstride << 1)] << 4 : 0;
      CONTEXT = ((line_m1 >> 3) & 0x7c) | ((line_m2 >> 3) & 0x380);

      /* 6.2.5.7 3d */
      for (x = 0; x < padded_width; x += 8)
	{
	  byte result = 0;
	  int x_minor;
	  int minor_width = GBW - x > 8 ? 8 : GBW - x;

	  if (y >= 1)
	    line_m1 = (line_m1 << 8) |
	      (x + 8 < GBW ? gbreg_line[-rowstride + (x >> 3) + 1] : 0);

	  if (y >= 2)
	    line_m2 = (line_m2 << 8) |
	      (x + 8 < GBW ? gbreg_line[-(rowstride << 1) + (x >> 3) + 1] << 4: 0);

	  /* This is the speed-critical inner loop. */
	  for (x_minor = 0; x_minor < minor_width; x_minor++)
	    {
	      bool bit;

	      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
	      result |= bit << (7 - x_minor);
	      CONTEXT = ((CONTEXT & 0x1bd) << 1) | bit |
		((line_m1 >> (10 - x_minor)) & 0x4) |
		((line_m2 >> (10 - x_minor)) & 0x80);
	    }
	  gbreg_line[x >> 3] = result;
	}
#ifdef OUTPUT_PBM
      fwrite(gbreg_line, 1, rowstride, stdout);
#endif
      gbreg_line += rowstride;
    }

  return 0;
}

static int
jbig2_decode_generic_template2a(Jbig2Ctx *ctx,
			       Jbig2Segment *segment,
			       const Jbig2GenericRegionParams *params,
			       Jbig2ArithState *as,
			       Jbig2Image *image,
			       Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  const int rowstride = image->stride;
  int x, y;
  byte *gbreg_line = (byte *)image->data;

  /* This is a special case for GBATX1 = 3, GBATY1 = -1 */

#ifdef OUTPUT_PBM
  printf("P4\n%d %d\n", GBW, GBH);
#endif

  for (y = 0; y < GBH; y++)
    {
      uint32_t CONTEXT;
      uint32_t line_m1;
      uint32_t line_m2;
      int padded_width = (GBW + 7) & -8;

      line_m1 = (y >= 1) ? gbreg_line[-rowstride] : 0;
      line_m2 = (y >= 2) ? gbreg_line[-(rowstride << 1)] << 4 : 0;
      CONTEXT = ((line_m1 >> 3) & 0x78) | ((line_m1 >> 2) & 0x4) | ((line_m2 >> 3) & 0x380);

      /* 6.2.5.7 3d */
      for (x = 0; x < padded_width; x += 8)
	{
	  byte result = 0;
	  int x_minor;
	  int minor_width = GBW - x > 8 ? 8 : GBW - x;

	  if (y >= 1)
	    line_m1 = (line_m1 << 8) |
	      (x + 8 < GBW ? gbreg_line[-rowstride + (x >> 3) + 1] : 0);

	  if (y >= 2)
	    line_m2 = (line_m2 << 8) |
	      (x + 8 < GBW ? gbreg_line[-(rowstride << 1) + (x >> 3) + 1] << 4: 0);

	  /* This is the speed-critical inner loop. */
	  for (x_minor = 0; x_minor < minor_width; x_minor++)
	    {
	      bool bit;

	      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
	      result |= bit << (7 - x_minor);
	      CONTEXT = ((CONTEXT & 0x1b9) << 1) | bit |
		((line_m1 >> (10 - x_minor)) & 0x8) |
		((line_m1 >> (9 - x_minor)) & 0x4) |
		((line_m2 >> (10 - x_minor)) & 0x80);
	    }
	  gbreg_line[x >> 3] = result;
	}
#ifdef OUTPUT_PBM
      fwrite(gbreg_line, 1, rowstride, stdout);
#endif
      gbreg_line += rowstride;
    }

  return 0;
}

static int
jbig2_decode_generic_template3(Jbig2Ctx *ctx,
			       Jbig2Segment *segment,
			       const Jbig2GenericRegionParams *params,
			       Jbig2ArithState *as,
			       Jbig2Image *image,
			       Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  const int rowstride = image->stride;
  byte *gbreg_line = (byte *)image->data;
  int x, y;

  /* this routine only handles the nominal AT location */

#ifdef OUTPUT_PBM
  printf("P4\n%d %d\n", GBW, GBH);
#endif

  for (y = 0; y < GBH; y++)
    {
      uint32_t CONTEXT;
      uint32_t line_m1;
      int padded_width = (GBW + 7) & -8;

      line_m1 = (y >= 1) ? gbreg_line[-rowstride] : 0;
      CONTEXT = (line_m1 >> 1) & 0x3f0;

      /* 6.2.5.7 3d */
      for (x = 0; x < padded_width; x += 8)
	{
	  byte result = 0;
	  int x_minor;
	  int minor_width = GBW - x > 8 ? 8 : GBW - x;

	  if (y >= 1)
	    line_m1 = (line_m1 << 8) |
	      (x + 8 < GBW ? gbreg_line[-rowstride + (x >> 3) + 1] : 0);

	  /* This is the speed-critical inner loop. */
	  for (x_minor = 0; x_minor < minor_width; x_minor++)
	    {
	      bool bit;

	      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
	      result |= bit << (7 - x_minor);
	      CONTEXT = ((CONTEXT & 0x1f7) << 1) | bit |
		((line_m1 >> (10 - x_minor)) & 0x010);
	    }
	  gbreg_line[x >> 3] = result;
	}
#ifdef OUTPUT_PBM
      fwrite(gbreg_line, 1, rowstride, stdout);
#endif
      gbreg_line += rowstride;
    }

  return 0;
}

static int
jbig2_decode_generic_template3_unopt(Jbig2Ctx *ctx,
                               Jbig2Segment *segment,
                               const Jbig2GenericRegionParams *params,
                               Jbig2ArithState *as,
                               Jbig2Image *image,
                               Jbig2ArithCx *GB_stats)
{
  const int GBW = image->width;
  const int GBH = image->height;
  uint32_t CONTEXT;
  int x,y;
  bool bit;

  /* this version is generic and easy to understand, but very slow */

  for (y = 0; y < GBH; y++) {
    for (x = 0; x < GBW; x++) {
      CONTEXT = 0;
      CONTEXT |= jbig2_image_get_pixel(image, x - 1, y) << 0;
      CONTEXT |= jbig2_image_get_pixel(image, x - 2, y) << 1;
      CONTEXT |= jbig2_image_get_pixel(image, x - 3, y) << 2;
      CONTEXT |= jbig2_image_get_pixel(image, x - 4, y) << 3;
      CONTEXT |= jbig2_image_get_pixel(image, x + params->gbat[0],
	y + params->gbat[1]) << 4;
      CONTEXT |= jbig2_image_get_pixel(image, x + 1, y - 1) << 5;
      CONTEXT |= jbig2_image_get_pixel(image, x + 0, y - 1) << 6;
      CONTEXT |= jbig2_image_get_pixel(image, x - 1, y - 1) << 7;
      CONTEXT |= jbig2_image_get_pixel(image, x - 2, y - 1) << 8;
      CONTEXT |= jbig2_image_get_pixel(image, x - 3, y - 1) << 9;
      bit = jbig2_arith_decode(as, &GB_stats[CONTEXT]);
      jbig2_image_set_pixel(image, x, y, bit);
    }
  }
  return 0;
}


/**
 * jbig2_decode_generic_region: Decode a generic region.
 * @ctx: The context for allocation and error reporting.
 * @segment: A segment reference for error reporting.
 * @params: Decoding parameter set.
 * @as: Arithmetic decoder state.
 * @image: Where to store the decoded data.
 * @GB_stats: Arithmetic stats.
 *
 * Decodes a generic region, according to section 6.2. The caller should
 * pass an already allocated Jbig2Image object for @image
 *
 * Because this API is based on an arithmetic decoding state, it is
 * not suitable for MMR decoding.
 *
 * Return code: 0 on success.
 **/
int
jbig2_decode_generic_region(Jbig2Ctx *ctx,
			    Jbig2Segment *segment,
			    const Jbig2GenericRegionParams *params,
			    Jbig2ArithState *as,
			    Jbig2Image *image,
			    Jbig2ArithCx *GB_stats)
{
  const int8_t *gbat = params->gbat;

  if (!params->MMR && params->GBTEMPLATE == 0) {
    if (gbat[0] == +3 && gbat[1] == -1 &&
        gbat[2] == -3 && gbat[3] == -1 &&
        gbat[4] == +2 && gbat[5] == -2 &&
        gbat[6] == -2 && gbat[7] == -2)
      return jbig2_decode_generic_template0(ctx, segment, params,
                                          as, image, GB_stats);
    else
      return jbig2_decode_generic_template0_unopt(ctx, segment, params,
                                          as, image, GB_stats);
  } else if (!params->MMR && params->GBTEMPLATE == 1)
    return jbig2_decode_generic_template1(ctx, segment, params,
					  as, image, GB_stats);
  else if (!params->MMR && params->GBTEMPLATE == 2)
    {
      if (gbat[0] == 3 && gbat[1] == -1)
	return jbig2_decode_generic_template2a(ctx, segment, params,
					       as, image, GB_stats);
      else
	return jbig2_decode_generic_template2(ctx, segment, params,
                                              as, image, GB_stats);
    }
  else if (!params->MMR && params->GBTEMPLATE == 3) {
   if (gbat[0] == 2 && gbat[1] == -1)
     return jbig2_decode_generic_template3_unopt(ctx, segment, params,
                                         as, image, GB_stats);
   else
     return jbig2_decode_generic_template3_unopt(ctx, segment, params,
                                         as, image, GB_stats);
  }

  {
    int i;
    for (i = 0; i < 8; i++)
      jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
        "gbat[%d] = %d", i, params->gbat[i]);
  }
  jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
	      "decode_generic_region: MMR=%d, GBTEMPLATE=%d NYI",
	      params->MMR, params->GBTEMPLATE);
  return -1;
}

/** 
 * Handler for immediate generic region segments
 */
int
jbig2_immediate_generic_region(Jbig2Ctx *ctx, Jbig2Segment *segment,
			       const byte *segment_data)
{
  Jbig2RegionSegmentInfo rsi;
  byte seg_flags;
  int8_t gbat[8];
  int offset;
  int gbat_bytes = 0;
  Jbig2GenericRegionParams params;
  int code;
  Jbig2Image *image;
  Jbig2WordStream *ws;
  Jbig2ArithState *as;
  Jbig2ArithCx *GB_stats = NULL;

  /* 7.4.6 */
  if (segment->data_length < 18)
    return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
		       "Segment too short");

  jbig2_get_region_segment_info(&rsi, segment_data);
  jbig2_error(ctx, JBIG2_SEVERITY_INFO, segment->number,
	      "generic region: %d x %d @ (%d, %d), flags = %02x",
	      rsi.width, rsi.height, rsi.x, rsi.y, rsi.flags);

  /* 7.4.6.2 */
  seg_flags = segment_data[17];
  jbig2_error(ctx, JBIG2_SEVERITY_INFO, segment->number,
	      "segment flags = %02x", seg_flags);
  if ((seg_flags & 1) && (seg_flags & 6))
    jbig2_error(ctx, JBIG2_SEVERITY_WARNING, segment->number,
		"MMR is 1, but GBTEMPLATE is not 0");

  /* 7.4.6.3 */
  if (!(seg_flags & 1))
    {
      gbat_bytes = (seg_flags & 6) ? 2 : 8;
      if (18 + gbat_bytes > segment->data_length)
	return jbig2_error(ctx, JBIG2_SEVERITY_FATAL, segment->number,
			   "Segment too short");
      memcpy(gbat, segment_data + 18, gbat_bytes);
      jbig2_error(ctx, JBIG2_SEVERITY_INFO, segment->number,
		  "gbat: %d, %d", gbat[0], gbat[1]);
    }

  offset = 18 + gbat_bytes;

  /* Table 34 */
  params.MMR = seg_flags & 1;
  params.GBTEMPLATE = (seg_flags & 6) >> 1;
  params.TPGDON = (seg_flags & 8) >> 3;
  params.USESKIP = 0;
  memcpy (params.gbat, gbat, gbat_bytes);

  image = jbig2_image_new(ctx, rsi.width, rsi.height);
  jbig2_error(ctx, JBIG2_SEVERITY_DEBUG, segment->number,
    "allocated %d x %d image buffer for region decode results",
        rsi.width, rsi.height);
    
  if (params.MMR)
    {
      code = jbig2_decode_generic_mmr(ctx, segment, &params,
				      segment_data + offset, segment->data_length - offset,
				      image);
    }
  else
    {
      int stats_size = jbig2_generic_stats_size(ctx, params.GBTEMPLATE);
      GB_stats = jbig2_alloc(ctx->allocator, stats_size);
      memset(GB_stats, 0, stats_size);

      ws = jbig2_word_stream_buf_new(ctx,
				     segment_data + offset,
				     segment->data_length - offset);
      as = jbig2_arith_new(ctx, ws);
      code = jbig2_decode_generic_region(ctx, segment, &params,
					 as, image, GB_stats);
      jbig2_free(ctx->allocator, as);
      jbig2_word_stream_buf_free(ctx, ws);

      jbig2_free(ctx->allocator, GB_stats);
    }

  jbig2_page_add_result(ctx, &ctx->pages[ctx->current_page], 
			image, rsi.x, rsi.y, JBIG2_COMPOSE_OR);
  jbig2_image_release(ctx, image);
  
  return code;
}

