/*
 * program pbmarrange - arrange several pbm files on a canvas
 *
 * (C) by Oliver Vogel (e-mail: vogel@ikp.uni-koeln.de)
 * April 5th, 1997
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public Licence as by published
 * by the Free Software Foundation; either version 2; or (at your option)
 * any later version
 *
 * This program is distributed in the hope that it will entertaining,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * $Id: pbmarrange.c,v 1.1 1999/04/04 11:47:56 xblast Exp $
 * $Log: pbmarrange.c,v $
 * Revision 1.1  1999/04/04 11:47:56  xblast
 * Initial revision
 *
 */

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

/*
 * some type defintions
 */
typedef struct _pbm_file {
  unsigned x, y;
  unsigned wbyte, height;
  unsigned nbyte;
  long offset;
  char *fname;
  char *line;
  struct _pbm_file * next;
} PbmFile;

typedef struct _pbm_data {
  unsigned wbyte, height;
  unsigned nbyte;
  char *data;
} PbmData;


/*
 * local variables
 */
static char *progname;

/*
 * parse_input: parse the input file for pbmfile names and positions
 */
#ifdef __STDC__
static PbmFile *
parse_input (char *filename) 
#else
static PbmFile *
parse_input (filename) 
  char *filename;
#endif
{
  FILE *fp;
  static char line[1024];
  static char pbmname[1024];
  unsigned x, y;
  PbmFile *new, *last = NULL, *root = NULL;

  if ( (NULL == filename) || (0 == strcmp (filename, "-") ) ) {
    /* use stdin */
    fp = stdin;
  } else {
    if (NULL == (fp = fopen(filename, "r") ) ) {
      fprintf(stderr, "%s: failed to open file %s\n", progname, filename);
      perror(progname);
      return NULL;
    }
  }
  
  while (NULL != fgets(line, sizeof(line), fp) ) {
    if (3 == sscanf(line, "%u %u %s", &x, &y, pbmname) ) {
      if ( (NULL == (new = (PbmFile *) malloc(sizeof(PbmFile) ) ) ) ||
	   (NULL == (new->fname = (char *) malloc(strlen(pbmname)+1) ) ) ) {
	fprintf(stderr, "%s: alloc failed\n", progname);
	return NULL;
      }
      /* set entries */
      if (x % 8) {
	fprintf(stderr, "%s: Warning! unaligned xoffset %d ignored",
		progname, x);
      }
      new->x = x/8;
      new->y = y;
      strcpy(new->fname, pbmname);
      if (root == NULL) {
	root = last = new;
      } else {
	last->next = new;
	last = new;
      }
    }
  }
  return root;
}


#ifdef __STDC__
static int
check_pbm (PbmFile *pbm)
#else
static int
check_pbm (pbm)
  PbmFile *pbm;
#endif
{
  FILE *fp;
  char magic[256];

  for (; pbm != NULL; pbm = pbm->next) {
    /* open file */
    if (NULL == (fp = fopen(pbm->fname, "r") ) ) {
      fprintf(stderr, "%s: failed to open file %s\n", progname, pbm->fname);
      perror(progname);
      return 1;
    }
    /* read header */
    if (3 != fscanf(fp, "%s%d%d%*c", magic,&(pbm->wbyte),&(pbm->height) ) ) {
      fprintf(stderr, "%s: Failed to read pbm header\n",progname);
      return 1;
    }
    /* test magic */
    if (0 != strcmp(magic,"P4")) {
      fprintf(stderr, "%s: Wrong magic word \"%s\".\n", progname, magic);
      return 1;
    }
    /* get offset of pixel data */
    pbm->offset = ftell(fp);
    /* close file */
    fclose(fp);
    /* alloc line buffer */
    pbm->wbyte /= 8;
    if (NULL == (pbm->line = malloc(pbm->wbyte) ) ) {
      fprintf(stderr, "%s: alloc failed\n", progname);
      return 1;
    }
    /* set total number of pixel */
    pbm->nbyte = pbm->wbyte * pbm->height;
  }

  return 0;
}



#ifdef __STDC__
static PbmData *
init_pbm (PbmFile *pbm)
#else
static PbmData *
init_pbm (pbm)
  PbmFile *pbm,
#endif
{
  PbmData *new;
  unsigned w, h;

  if (NULL == (new = calloc(1, sizeof(PbmData) ) ) ) {
    fprintf(stderr, "%s: alloc failed\n", progname);
    return NULL;
  }

  for (; pbm != NULL; pbm = pbm->next) {
    /* new wbyte ? */
    w = pbm->x + pbm->wbyte;
    if (w > new->wbyte) {
      new->wbyte = w;
    }
    /* new height ? */
    h = pbm->y + pbm->height;
    if (h > new->height) {
      new->height = h;
    }
  }
  new->nbyte = new->wbyte * new->height;

  /* alloc one data */
  if (NULL == (new->data = malloc(new->nbyte) ) ) {
    fprintf(stderr, "%s: alloc failed\n", progname);
    return NULL;
  }

  return new;
}


#ifdef __STDC__
static int
draw_data (PbmData *dst,
	   PbmFile *src)
#else
static int
draw_data (dst, src)
  PbmData *dst;
  PbmFile *src;
#endif
{
  FILE *fp;
  char *ptr;
  unsigned y;

  /* clear it first */
  memset(dst->data, 0, src->nbyte);

  for (; src != NULL; src = src->next) {
    /* open src file */
    if (NULL == (fp = fopen(src->fname, "r") ) ) {
      fprintf(stderr, "%s: failed to open file %s\n", progname, src->fname);
      perror(progname);
      return 1;
    }
    /* skip to data data */
    if (0 > fseek(fp, src->offset, SEEK_SET) ) {
      perror(progname);
      return 1;
    }
    /* draw pixels */
    ptr = dst->data + src->x + src->y*dst->wbyte;
    for (y=0; y<src->height; y++) {
      /* read line */
      if (src->wbyte != fread (src->line, 1, src->wbyte, fp) ) {
	if (feof(fp)) {
	  fprintf(stderr, "%s: premature eof in file %s\n",
		  progname, src->fname);
	} else {
	  perror(progname);
	}
	return 1;
      }
      /* copy to dest pbm */
      memcpy (ptr, src->line, src->wbyte);
      ptr += dst->wbyte;
    }
    /* close file */
    fclose(fp);
  }
  return 0;
}


#ifdef __STDC__	      
static FILE *
pbm_open (PbmData *pbm,
	  char *filename)
#else
static FILE *
pbm_open (pbm, filename)
  PbmData *pbm;
  char *filename;
#endif
{
  FILE *fp;

  if ( (NULL == filename) || (0 == strcmp (filename, "-") ) ){
    fp = stdout;
  } else {
    if (NULL == (fp = fopen(filename, "w") ) ) {
      perror(progname);
      return NULL;
    }
  }

  /* write header */
  fprintf(fp,"P4\n");
  fprintf(fp,"%u %u\n", pbm->wbyte*8, pbm->height);
  
  return fp;
}


#ifdef __STDC__
static int
write_data (FILE *fp,
	     PbmData *pbm)
#else
static int
write_data (fp, pbm)
  FILE *fp;
  PbmData *pbm;
#endif
{
  if (pbm->nbyte != fwrite(pbm->data, 1, pbm->nbyte, fp) ) {
    perror(progname);
    return 1;
  }

  return 0;
}



/*
 * the main program
 */

#ifdef __STDC__
int
main (int argc,
      char *argv[])
#else
int
main (argc, argv)
  int argc;
  char *argv[];
#endif
{
  char *input  = NULL;
  char *output = NULL;
  PbmFile *pbm_list;
  PbmData *new_pbm;
  FILE *fp;

  /* parse commandline args */
  switch (argc) {
    /* correct number */
  case 3:
    output   = argv[2];
  case 2:
    input    = argv[1];
  case 1:
    progname = argv[0];
    break;

  default:
    /* print usage message */
    fprintf(stderr, "usage: %s [datafile [outputfile]]\n", argv[0]);
    return 1;
  }
  /* inits */
  if (NULL == (pbm_list = parse_input(input) ) ) {
    return 1;
  }
  if (check_pbm (pbm_list) ){
    return 1;
  }
  if (NULL == (new_pbm = init_pbm(pbm_list) ) ) {
    return 1;
  }
  /* open output file */
  if (NULL == (fp = pbm_open(new_pbm, output) ) ) {
    return 1;
  }
  if (draw_data(new_pbm, pbm_list) ) {
    return 1;
  }
  if (write_data(fp, new_pbm) ) {
    return 1;
  }
  fclose(fp);

  return 0;
}

