/*
 * Converts PhotoShop .ABR and Paint Shop Pro .JBR brushes to GIMP .GBR
 * Copyright (C) 2001 Marco Lamberto <lm@sunnyspot.org>
 * Web page: http://the.sunnyspot.org/gimp/
 *
 * Add partial support for v2 and v6 brushes
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * $Id:$
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <glib.h>

#ifndef WRITE_GBR
#define WRITE_GBR TRUE
#endif

#define GBRUSH_MAGIC    (('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0))

static gboolean write_gbr = WRITE_GBR;

typedef struct _AbrHeader				AbrHeader;
typedef struct _AbrBrushHeader				AbrBrushHeader;
typedef struct _AbrSampledBrushHeader			AbrSampledBrushHeader;
typedef struct _GbrBrushHeader				GbrBrushHeader;
typedef struct _GbrBrush				GbrBrush;

struct _AbrHeader
{
	gshort version;
	gshort count;
};

struct _AbrBrushHeader
{
	gshort type;
	gint32 size;
};

struct _AbrSampledBrushHeader
{
	gint32 misc;
	gshort spacing;
	gchar antialiasing;
	gshort bounds[4];
	gint32 bounds_long[4];
	gshort depth;
	gboolean wide;
};

struct _GbrBrushHeader
{
	guint header_size;
	guint version;
	guint width;
	guint height;
	guint depth;
	guint magic_number;
	guint spacing;
};

struct _GbrBrush
{
	GbrBrushHeader header;
	gchar *name;
	gchar *description;
	gchar *data;
	gint size;
};


gchar		abr_read_char	(FILE *abr);
gshort		abr_read_short	(FILE *abr);
gint32 		abr_read_long	(FILE *abr);
void		abr_load	(const gchar *file);
GbrBrush *	abr_brush_load	(FILE *abr, AbrHeader *abr_hdr);
void		gbr_brush_save	(GbrBrush *brush, const gchar *file,
				 const gint id);
void		gbr_brush_free	(GbrBrush *brush);
void 		gbr_brush_content_free (GbrBrush *brush);
void 		abr_load_v12 (FILE *abr, gint16 abr_version,
			      const gchar *file);
gboolean 	abr_reach_8BIM_section (FILE* abr, const gchar *name);
gint32 		abr_read_names_desc (FILE *abr, gint16 abr_subver,
				     GbrBrush **brushes);
gboolean 	abr_load_brush_v6   (FILE *abr, gint16 abr_subver,
				     GbrBrush *gbr);
void 		abr_load_brushes_v6 (FILE *abr, gint16 abr_subver,
				     GbrBrush *brushes, gint32 nbrush,
				     const gchar *file);
void 		abr_load_v6	    (FILE *abr, const gchar* file);
gint		main (gint argc, gchar *argv[]);


gchar
abr_read_char(FILE *abr)
{
  g_return_val_if_fail ( abr != NULL, 0 );

  return fgetc(abr);
}

gshort
abr_read_short(FILE *abr)
{
  gshort val;

  g_return_val_if_fail ( abr != NULL, 0 );

  fread(&val, sizeof(val), 1, abr);
	
  return GUINT16_FROM_BE(val);
}

gint32
abr_read_long(FILE *abr)
{
  gint32 val;

  g_return_val_if_fail ( abr != NULL, 0 );
  
  fread(&val, sizeof(val), 1, abr);
  
  return GUINT32_FROM_BE(val);
}

static gchar*
abr_v2_brush_name (FILE *file)
{
  gulong  name_size;
  gulong  buf_size;
  gchar  *name_ucs2;
  gchar  *name_utf8;
  guint   i;

  /* two-bytes characters encoded (UCS-2)
   *  format:
   *   long : size - number of characters in string
   *   data : zero terminated UCS-2 string
   */

  name_size = abr_read_long (file);
  if (name_size == 0)
    return (gchar*) NULL;

  buf_size = name_size * 2;

  name_ucs2 = (gchar*) g_malloc (buf_size * sizeof (gchar));
  for (i = 0; i < buf_size ; i++)
    {
         name_ucs2[i] =  abr_read_char (file);
    }
#if GLIB_MAJOR_VERSION > 1
  name_utf8 = g_convert (name_ucs2, buf_size,
                         "UTF-8", "UCS-2BE",
                         NULL, NULL, NULL);
#else
  name_utf8 = (char*) g_malloc (name_size * sizeof (gchar));
  /* keep low bytes - big endian */
  for (i = 0; i < name_size ; i++)
    {
      name_utf8[i] = name_ucs2[i * 2 + 1];
    }
#endif
  g_free(name_ucs2);

  return name_utf8;
}


GbrBrush *
abr_brush_load(FILE *abr, AbrHeader *abr_hdr)
{
  AbrBrushHeader abr_brush_hdr;
  GbrBrush *gbr = NULL;
  gchar *name = NULL;

  g_return_val_if_fail ( abr != NULL, NULL );

  abr_brush_hdr.type = abr_read_short(abr);
  abr_brush_hdr.size = abr_read_long(abr);
  g_print(" + BRUSH\n | << type: %d  block size: %d bytes\n",
	  abr_brush_hdr.type, abr_brush_hdr.size);
  
  switch (abr_brush_hdr.type) {
  case 1: /* computed brush */
    /* FIXME: support it! */
    g_print("WARNING: computed brush unsupported, skipping.\n");
    fseek(abr, abr_brush_hdr.size, SEEK_CUR);
    break;
  case 2: /* sampled brush */
    {
      AbrSampledBrushHeader abr_sampled_brush_hdr;
      gint width, height;
      gint size;
      gint i = 0;
      
      abr_sampled_brush_hdr.misc = abr_read_long(abr);
      abr_sampled_brush_hdr.spacing = abr_read_short(abr);
      if (abr_hdr->version == 2)
        name = abr_v2_brush_name(abr);

      abr_sampled_brush_hdr.antialiasing = abr_read_char(abr);
      
      for (i = 0; i < 4; i++)
	abr_sampled_brush_hdr.bounds[i] = abr_read_short(abr);
      for (i = 0; i < 4; i++) {
	abr_sampled_brush_hdr.bounds_long[i] = abr_read_long(abr);
      }
      abr_sampled_brush_hdr.depth = abr_read_short(abr);
      
      height = abr_sampled_brush_hdr.bounds_long[2] - 
	abr_sampled_brush_hdr.bounds_long[0]; /* bottom - top */
      width = abr_sampled_brush_hdr.bounds_long[3] -
	abr_sampled_brush_hdr.bounds_long[1]; /* right - left */
      size = width * (abr_sampled_brush_hdr.depth >> 3) * height;
      
      /* FIXME: support wide brushes */
      abr_sampled_brush_hdr.wide = height > 16384;
      if (abr_sampled_brush_hdr.wide)
	g_print("WARING: wide brushes not supported\n");
      
      gbr = g_new0(GbrBrush, 1);
      gbr->header.version = g_htonl(2);
      gbr->header.width	= g_htonl(width);
      gbr->header.height = g_htonl(height);
      gbr->header.depth	= g_htonl(abr_sampled_brush_hdr.depth >> 3);
      gbr->header.magic_number = g_htonl(GBRUSH_MAGIC);
      gbr->header.spacing = g_htonl(abr_sampled_brush_hdr.spacing);
      gbr->data	= g_new0(gchar, size);
      gbr->size	= size;
      gbr->description = name;

      /* data decoding */
      {
	gshort comp;
	
	comp = abr_read_char(abr);
	g_print(" | << size: %dx%d %d bit (%d bytes) %s\n",
		width, height, abr_sampled_brush_hdr.depth, size,
		comp ? "compressed" : "raw");
        if (name)
          g_print(" | << name: %s\n", name);
	if (!comp) {
	  fread(gbr->data, size, 1, abr);
	} else {
	  gint32 n;
	  gchar ch;
	  gint i, j, c;
	  gshort *cscanline_len;
	  gchar *data = gbr->data;
	  
	  /* read compressed size foreach scanline */
	  cscanline_len = g_new0(gshort, height);
	  for (i = 0; i < height; i++)
	    cscanline_len[i] = abr_read_short(abr);
	  
	  /* unpack each scanline data */
	  for (i = 0; i < height; i++) {
	    for (j = 0; j < cscanline_len[i];) {
	      n = abr_read_char(abr);
	      j++;
	      if (n >= 128) /* force sign */
		n -= 256;
	      if (n < 0) { /* copy the following char -n + 1 times */
		if (n == -128)	/* it's a nop */
		  continue;
		n = -n + 1;
		ch = abr_read_char(abr);
		j++;
		for (c = 0; c < n; c++, data++)
		  *data = ch;
	      } else { /* read the following n + 1 chars (no compr) */
		for (c = 0; c < n + 1; c++, j++, data++)
		  *data = (guchar)abr_read_char(abr);
	      }
	    }
	  }
	  g_free(cscanline_len);
	}
      }
    }
    break;
  default:
    g_print("WARNING: unknown brush type, skipping.\n");
    fseek(abr, abr_brush_hdr.size, SEEK_CUR);
  }
  
  return gbr;
}

void
gbr_brush_content_free(GbrBrush *brush)
{
  g_free(brush->name);
  g_free(brush->description);
  g_free(brush->data);

}

void
gbr_brush_free(GbrBrush *brush)
{
  gbr_brush_content_free(brush);
  g_free(brush);
}

void
abr_load_v12(FILE *abr, gint16 abr_version, const gchar *file)
{
  GbrBrush *brush;
  gint i;
  AbrHeader abr_hdr;
  
  abr_hdr.version = abr_version;
  
  abr_hdr.count = abr_read_short(abr);
  
  g_print(" + HEADER\n | << ver: %d  brushes: %d\n",
	  abr_version, abr_hdr.count);
  
  for (i = 0; i < abr_hdr.count; i++) {
    brush = abr_brush_load(abr, &abr_hdr);
    if (!brush) {
      g_print("ERROR: load error (brush #%d)\n", i);
      break;
    }
    if (write_gbr)
      gbr_brush_save(brush, file, i);
    
    gbr_brush_free(brush);
  }
}

gboolean
abr_reach_8BIM_section(FILE* abr, const gchar *name)
{
  gchar tag[4];
  gchar tagname[5];
  gint32 section_size;
  gint r;
  
  /* find 8BIMname section */
  while(!feof(abr)) {
    r = fread(&tag, 1, 4, abr);
    if (r != 4) {
      g_print("Error: Cannot read 8BIM tag %s\n", g_strerror(errno));
      return FALSE;
    }
    if (strncmp(tag, "8BIM", 4)) {
      g_print("Error: Start tag not 8BIM\n");
      return FALSE;
    }
    r = fread(&tagname, 1, 4, abr);
    if (r != 4) {
      g_print("Error: Cannot read 8BIM tag name %s\n",
	      g_strerror(errno));
      return FALSE;
    }
    tagname[4] = '\0';
    
    if (!strncmp(tagname, name, 4))
      return TRUE;
    
    section_size = abr_read_long(abr);
    fseek(abr, section_size, SEEK_CUR);
  }
  return FALSE;
}

gint32
abr_read_names_desc(FILE *abr, gint16 abr_subver, GbrBrush **brushes)
{
  gint32 section_size;
  gint r;
  gint i, j;
  gint32 nbrush;
  gchar *name;
  gint32 name_len;
  gint32 unknown_bytes;
  
  if (!abr_reach_8BIM_section(abr, "desc"))
    return 0;
  
  section_size = abr_read_long(abr);
  
  fseek(abr, 34, SEEK_CUR);
  nbrush = abr_read_long(abr);
  
  g_print(" + 8BIMdesc section\n | << brush count: %d\n", nbrush);
  
  *brushes = (GbrBrush*) g_new0(GbrBrush, nbrush);
  for (i = 0; i < nbrush; i++) {
    fseek(abr, 41, SEEK_CUR);
    name_len = abr_read_long(abr);
    name = g_malloc(name_len * 2 * sizeof(gchar));
    r = fread(name, 1, name_len * 2, abr);
    if (r != name_len * 2) {
      g_print("Error: cannot retrieve #%d name %d (expext %d)\n", 
	      i, r, name_len);
      g_free(name);
      return i;
    }
    /* convert UCS2 :) */
    (*brushes)[i].description = (gchar*) g_malloc(name_len * sizeof(gchar));
    for (j=0; j< name_len; j++)
      (*brushes)[i].description[j] = name[j*2+1];
    
    g_free(name);
    
    if (abr_subver == 1)
      unknown_bytes = 467;
    else
      unknown_bytes = 559;
    
    /* discard bytes to next brush - name is encoded there too */
    /* unknown/useless fields + name length (long) + name UCS-2 */
    fseek(abr, unknown_bytes + 4 + name_len * 2, SEEK_CUR);
    
  }
  
  return nbrush;
}

gboolean
abr_load_brush_v6(FILE *abr, gint16 abr_subver,
				  GbrBrush *gbr)
{
  gint32 brush_size;
  gint32 brush_end;
  gint32 next_brush;
  gint32 complement_4_bytes;
  gint   r;
  gint32 pad_height;
  gint32 pad_width;
  gint32 height;
  gint32 width;
  gint32 image_width;
  gint32 image_height;
  gint16 depth;
  gchar  compression;
		  
  g_print (" + Brush\n");
  brush_size = abr_read_long(abr);
  brush_end = brush_size;
  /* complement to 4 */
  while (brush_end % 4 != 0) brush_end++;
  complement_4_bytes = brush_end - brush_size;
  next_brush = ftell(abr) + brush_end;
  
  /* read brush key 36 chars */
  fseek(abr, 37, SEEK_CUR);
  
  if (abr_subver == 1) {
      gint16 top, left, bottom, right;
      gint32 ltop, lleft, lbottom, lright;
	  
      top = abr_read_short(abr);
      left = abr_read_short(abr);
      bottom = abr_read_short(abr);
      right = abr_read_short(abr);
	  
      abr_read_short(abr); /* spacing? */
	  
      ltop = abr_read_long(abr);
      lleft = abr_read_long(abr);
      lbottom = abr_read_long(abr);
      lright = abr_read_long(abr);
      
      image_width = lright - lleft;
      image_height = lbottom - ltop;
  }
  else {
      fseek(abr, 264, SEEK_CUR); /* unuseful bytes */
      pad_height = abr_read_long(abr);
      pad_width  = abr_read_long(abr);
      height = abr_read_long(abr);
      width = abr_read_long(abr);
      image_width = width - pad_width;
      image_height = height - pad_height;
  }
  depth = abr_read_short(abr);
  compression = abr_read_char(abr);
  g_print(" | << %dx%d depth %d (%s)\n", image_width, image_height,
	  depth, (compression)? "compressed":"raw");
  
  if (gbr->description)
	  g_print(" | << name: %s\n", gbr->description);
  
  gbr->header.version = g_htonl(2);
  gbr->header.width = g_htonl(image_width);
  gbr->header.height = g_htonl(image_height);
  gbr->header.depth = g_htonl(depth >> 3);
  gbr->header.magic_number = g_htonl(GBRUSH_MAGIC);
  gbr->header.spacing = g_htonl(25);
  
  gbr->size = image_width * (depth >> 3) * image_height;
  gbr->data = g_new0(gchar, gbr->size);
  
  if (compression == 0) {
      r = fread(gbr->data, 1, gbr->size, abr);
      if (r != gbr->size) {
		  g_print("Error: not cannot read uncompressed data %s\n",
				  g_strerror(errno));
		  return FALSE;
      }
  } else {
      gint32 n;
      gchar ch;
      gint i, j, c;
      gshort *cscanline_len;
      gchar *data = gbr->data;
      guint32 cdata_len = 0;
	  
      /* read compressed size foreach scanline */
      cscanline_len = g_new0(gshort, image_height);
      for (i = 0; i < image_height; i++) {
		  cscanline_len[i] = abr_read_short(abr);
		  cdata_len += cscanline_len[i];
      }

      /* unpack each scanline data */
      for (i = 0; i < image_height; i++) {
	for (j = 0; j < cscanline_len[i];) {
	  n = abr_read_char(abr);
	  j++;
	  if (n >= 128)	/* force sign */
	    n -= 256;
	  if (n < 0) {	/* copy the following char -n + 1 times */
	    if (n == -128)	/* it's a nop */
	      continue;
	    n = -n + 1;
	    ch = abr_read_char(abr);
	    j++;
	    for (c = 0; c < n; c++, data++)
	      {
		*data = ch;
	      }
	  } else {
	    /* read the following n + 1 chars (no compr) */
	    for (c = 0; c < n + 1; c++, j++, data++)
	      {
		*data = (guchar) abr_read_char(abr);
	      }
	  }
	}
      }
      g_free(cscanline_len);
  }
  
  fseek(abr, next_brush , SEEK_SET);
  
  return TRUE;
}

void
abr_load_brushes_v6(FILE *abr, gint16 abr_subver,
					GbrBrush *brushes, gint32 nbrush, const gchar *file)
{
  gint32 sample_section_size;
  gint32 sample_section_end;
  gint   brush = 0;

  if (!abr_reach_8BIM_section(abr, "samp"))
	  return;

  sample_section_size = abr_read_long(abr);
  sample_section_end = sample_section_size + ftell(abr);

  while (!feof(abr) && ftell(abr) < sample_section_end && brush < nbrush) {
	  if (!abr_load_brush_v6(abr, abr_subver, &brushes[brush]))
		  return;

	  if (write_gbr)
		  gbr_brush_save(&brushes[brush], file, brush);

	  gbr_brush_content_free(&brushes[brush]);

	  brush ++;
  }

  if (nbrush != brush) {
	  g_print("Warning: 8BIMsamp brush count %d != 8BIMdesc count %d\n",
			  brush, nbrush);
  }
}

void
abr_load_v6(FILE *abr, const gchar* file)
{
	gint16 abr_subver;
	GbrBrush *brushes = NULL;
	gint32 nbrush;

	abr_subver = abr_read_short(abr);

	g_print(" + HEADER\n | << ver: 6  subver: %d\n",
		abr_subver);

	nbrush = abr_read_names_desc(abr, abr_subver, &brushes);

	if (nbrush <= 0) {
		g_print("Error: cannot find brushes description\n");
		return;
	}

	/* rewind file to header */
	fseek(abr, 4, SEEK_SET);
	
	abr_load_brushes_v6(abr, abr_subver, brushes, nbrush, file);

}

void
abr_load(const gchar *file)
{
	FILE *abr;
	gint16 abr_version;

	g_print("FILE %s\n", file);

	if (!(abr = fopen(file, "rb"))) {
		g_print("ERROR: can't open '%s'\n", file);
		return;
	}

	abr_version = abr_read_short(abr);
	switch(abr_version) {
		case 1:
		case 2:
			abr_load_v12(abr, abr_version, file);
			break;
		case 6:
			abr_load_v6(abr, file);
			break;
		default:
			g_print("ERROR: unable to decode abr format version %d\n",
					abr_version);
			break;
	}

	fclose(abr);
}

void
gbr_brush_save(GbrBrush *brush, const gchar *file, const gint id)
{
	FILE *gbr;
	gchar *p;

	p = strrchr(file, '.');
	brush->name = g_strndup(file, p - file);

	p = brush->name;
	brush->name = g_strdup_printf("%s_%03d.gbr", p, id);
	if (brush->description == NULL)
	  brush->description = g_strdup_printf("%s %03d", p, id);
	g_free(p);

	brush->header.header_size = g_htonl(sizeof(GbrBrushHeader) +
		strlen(brush->description) + 1);
	gbr = fopen(brush->name, "wb");
	fwrite(&brush->header, sizeof(GbrBrushHeader), 1, gbr);
	fwrite(brush->description, strlen(brush->description) + 1, 1, gbr);
	fwrite(brush->data, brush->size, 1, gbr);
	g_print(" | >> %s (%d bytes)\n", brush->name,
		brush->size + sizeof(GbrBrushHeader) +
		strlen(brush->description) + 1);
	fclose(gbr);
}


gint
main(gint argc, gchar *argv[])
{
	gint i;
	
	if (argc < 2) {
		g_print("usage: %s {file1.abr} ...\n", argv[0]);
		return -1;
	}
	
	for (i = 1; i < argc; i++)
		abr_load(argv[i]);
	g_print("DONE\n");

	return 0;
}
