/*
 * character, characterize.c, copyright 2003, Allan Hsu <allan@counterpop.net>
 */
#include "gd.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define BIG_PAGE
#ifdef BIG_PAGE
#define PAGE_WIDTH ((double)2592)
//#define PAGE_HEIGHT ((double)3888)
#define PAGE_HEIGHT ((double)5040)
#define MARGIN 60
#else
#define PAGE_WIDTH ((double)612)
#define PAGE_HEIGHT ((double)792)
#define MARGIN 18
#endif
#define WIDTH_RATIO 1
#define HEIGHT_RATIO 1
#define PS_FUDGE_HEIGHT 1422
#define PS_FUDGE_WIDTH 922
//#define PS_FONTSIZE ((double)PAGE_HEIGHT/792)
#define PS_FONTSIZE ((double)PAGE_HEIGHT/1027)
#define HTML_FONT_SIZE "xx-small"
#define SPACE_CHAR ' '
#define IGNORE_SPACE TRUE

typedef enum {FALSE, TRUE} bool;

typedef struct colour_T {
  int blue;
  int green;
  int red;
} colour;

typedef struct output_T {
  int width;
  int height;
  int iwidth, iheight;
  colour **colours;
  char **chars;
  float factor;
} outStruct;

static void do_mojo(gdImagePtr image, char *output, float factor);
static void init_output(outStruct *out, int width, int height, float factor);
static colour color_average(gdImagePtr image, int **matrix);
static void load_text(FILE *inText);
static char next_char();
static void translate_color(char *string, colour c);
static void char2html(char ch, char *dest);
static void write_html(char *filename, outStruct *os);
static void write_html_character(char ch, colour c, FILE *out);
static void write_html_header(FILE *out);
static void write_html_footer(FILE *out);
static void char2ps(char ch, char *dest);
static void write_ps(char *filename, outStruct *os);
static void write_ps_mojo(FILE *out, outStruct *os);
static void write_ps_header(FILE *out);
static void write_ps_footer(FILE *out);
static char convtab[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
static char *srcText;
static int srcTextPos;

int main(int argc, char *argv[]) {
  gdImagePtr sourceImage;
  FILE *input, *inText; 

  if(argc < 5) {
    printf("This program takes four arguments:\n");
    printf("%s <input image> <graininess factor> <input text> <output prefix>\n", argv[0]);
    exit(5);
  }
  input = fopen(argv[1], "rb");
  if(input == NULL) {
    printf("Error: the input image could not be opened.\n");
    exit(5);
  }
  inText = fopen(argv[3], "rb");
  if(inText == NULL) {
    printf("Error: the input text could not be opened.\n");
    exit(5);
  }
  printf("Reading in image %s...\n", argv[1]);
  if((sourceImage = gdImageCreateFromJpeg(input)) == NULL) {
    printf("Error: source file is not a valid jpeg image!\n");
    exit(5);
  }
  printf("Reading in text file %s...\n", argv[3]);
  load_text(inText);
  do_mojo(sourceImage, argv[4], atof(argv[2]));
  fclose(input);
  fclose(inText);
  gdImageDestroy(sourceImage);
  exit(0);
}

static void do_mojo(gdImagePtr image, char *output, float factor) {
  colour avg;
  int i, j, m, n;
  int **matrix;
  char *nbuffer;
  outStruct os;

  printf("Calculating pixel averages...\n");
  matrix = malloc(factor * WIDTH_RATIO * sizeof(int *));
  for(i = 0; i < factor * WIDTH_RATIO; i++)
    matrix[i] = malloc(factor * HEIGHT_RATIO * sizeof(int));
  init_output(&os, gdImageSX(image), gdImageSY(image), factor);
  for(i = 0; i < os.height; i++) {
    for(j = 0; j < os.width; j++) {
      int baseX = factor * WIDTH_RATIO * j;
      int baseY = factor * HEIGHT_RATIO * i;
      for(m = 0; m < factor * WIDTH_RATIO; m++) {
	for(n = 0; n < factor * HEIGHT_RATIO; n++)
	  matrix[m][n] = gdImageGetPixel(image, baseX + m, baseY + n);
      }
      avg = color_average(image, matrix);
      os.colours[j][i] = avg;
      os.chars[j][i] = next_char();

      //if(srcTextPos < 88) {
      //os.colours[j][i].blue = 0;
      //os.colours[j][i].green = 0;
      //os.colours[j][i].red = 0;
      //}
    }
  }
  nbuffer = (char *)malloc(strlen(output) + 6);
  strcpy(nbuffer, output);
  strcat(nbuffer, ".html");
  printf("Creating file %s...\n", nbuffer);
  write_html(nbuffer, &os);
  strcpy(nbuffer, output);
  strcat(nbuffer, ".ps");
  printf("Creating file %s...\n", nbuffer);
  write_ps(nbuffer, &os);
  free(nbuffer);
  for(i = 0; i < WIDTH_RATIO * factor; i++)
    free(matrix[i]);
  free(matrix);
  free(srcText);
}

static void init_output(outStruct *out, int width, int height, float factor) {
  int i;
  out->iwidth = width;
  out->iheight = height;
  out->factor = factor;
  out->width = width/(WIDTH_RATIO*factor);
  out->height = height/(HEIGHT_RATIO*factor);
  out->colours = malloc(out->width * sizeof(colour *));
  out->chars = malloc(out->width * sizeof(char *));
  for(i = 0; i < out->width; i++) {
    out->colours[i] = malloc(out->height * sizeof(colour));
    out->chars[i] = malloc(out->height * sizeof(char));
  }
}

static colour color_average(gdImagePtr image, int **matrix) {
  int i, j;
  colour c;
  
  c.blue = 0;
  c.green = 0;
  c.red = 0;
  for(i = 0; i < WIDTH_RATIO; i++) {
    for(j = 0; j < HEIGHT_RATIO; j++) {
      c.blue += gdImageBlue(image, matrix[i][j]);
      c.green += gdImageGreen(image, matrix[i][j]);
      c.red += gdImageRed(image, matrix[i][j]);
    }
  }
  c.blue /= WIDTH_RATIO*HEIGHT_RATIO;
  c.green /= WIDTH_RATIO*HEIGHT_RATIO;
  c.red /= WIDTH_RATIO*HEIGHT_RATIO;
  return(c);
}

static void load_text(FILE *inText) {
  char ch;

  srcTextPos = 0;
  fseek(inText, 0, SEEK_END);
  srcText = (char *)malloc(ftell(inText) + 1);
  rewind(inText);
  while(TRUE) {
    ch = fgetc(inText);
    if(feof(inText)) {
      srcText[srcTextPos] = '\0';
      break;
    }
    if(isspace(ch)) {
      if(!IGNORE_SPACE) srcText[srcTextPos++] = SPACE_CHAR;
      continue;
    }
    if(isprint(ch)) srcText[srcTextPos++] = ch;
  }
  if(srcTextPos == 0) {
    printf("Error: input source text has no non-space characters!\n");
    exit(5);
  }
  srcTextPos = 0;
}

static char next_char() {
  if(srcText[srcTextPos] == '\0') {
    srcTextPos = 0;
  }
  return(srcText[srcTextPos++]);
}

static void translate_color(char *string, colour c) {
  string[0] = convtab[c.red / 16];
  string[1] = convtab[c.red % 16];
  string[2] = convtab[c.green / 16];
  string[3] = convtab[c.green % 16];
  string[4] = convtab[c.blue / 16];
  string[5] = convtab[c.blue % 16];
  string[6] = '\0';
}

static void char2html(char ch, char *dest) {
  switch(ch) {
  case '"': strcpy(dest, "&quot;");
    break;
  case '&': strcpy(dest, "&amp;");
    break;
  case '<': strcpy(dest, "&lt;");
    break;
  case '>': strcpy(dest, "&gt;");
  default:
    dest[0] = ch;
    dest[1] = '\0';
  }
}

static void write_html(char *filename, outStruct *os) {
  FILE *out;
  int i, j;

  out = fopen(filename, "w");
  if(out == NULL) {
    printf("Error: write_html was unable to open the output file %s.\n", filename);
    exit(5);
  }
  write_html_header(out);
  for(i = 0; i < os->height; i++) {
    for(j = 0; j < os->width; j++)
      write_html_character(os->chars[j][i], os->colours[j][i], out);
    fputs("<br>\n", out);
  }
  write_html_footer(out);
  fclose(out);
}

static void write_html_character(char ch, colour c, FILE *out) {
  char cstring[6], eris[23];
  translate_color(cstring, c);
  char2html(ch, eris);
  fprintf(out, "<span style=\"color: #%s\">%s</span>", cstring, eris);
}

static void write_html_header(FILE *out) {
  fputs("<html><head><title>characterize output</title>", out);
  fprintf(out, "<style>\n span { font-size: %s; font-family: fixed, courier;} </style>", HTML_FONT_SIZE);
  fputs("</head><body bgcolor=\"#000000\"><nowrap>\n", out);
}

static void write_html_footer(FILE *out) {
  fputs("</nowrap></body></html>\n", out);
}

static void char2ps(char ch, char *dest) {
  switch(ch) {
  case '(': strcpy(dest, "\\(");
    break;
  case ')': strcpy(dest, "\\)");
    break;
  case '\\': strcpy(dest, "\\\\");
    break;
  default:
    dest[0] = ch;
    dest[1] = '\0';
  }
}

static void write_ps(char *filename, outStruct *os) {
  FILE *out;

  out = fopen(filename, "w");
  if(out == NULL) {
    printf("Error: write_ps was unable to open the output file %s.\n", filename);
    exit(5);
  }
  write_ps_header(out);
  write_ps_mojo(out, os);
  write_ps_footer(out);
  fclose(out);
}

static void write_ps_mojo(FILE *out, outStruct *os) {
  int i, j;
  double xspace, yspace, fontfactor;
  char ch, eris[23];

  xspace = yspace = MARGIN;
  if((double)os->iwidth/os->iheight > (double)(PAGE_WIDTH - 2*MARGIN)/(PAGE_HEIGHT - 2*MARGIN)) {
    yspace = (PAGE_HEIGHT - ((double)os->iheight/os->iwidth)*(PAGE_WIDTH - 2*MARGIN))/2;
    fontfactor = PS_FONTSIZE * os->factor * ((double)PS_FUDGE_WIDTH/os->iwidth);
  } else {
    xspace = (PAGE_WIDTH - ((double)os->iwidth/os->iheight)*(PAGE_HEIGHT - 2*MARGIN))/2;
    fontfactor = PS_FONTSIZE * os->factor * ((double)PS_FUDGE_HEIGHT/os->iheight);
  }
  fputs("/Courier-Bold findfont\n", out);
  fprintf(out, "%f scalefont\n", fontfactor);
  fputs("setfont\n", out);
  for(i = 0; i < os->width; i++) {
    for(j = 0; j < os->height; j++) {
      ch = os->chars[i][j];
      char2ps(ch, eris);
      fprintf(out, "(%s) %f %f %f %f %f dcz\n",
	      eris,
	      (double)(PAGE_WIDTH - 2*xspace) * i / os->width + xspace,
	      (double)(PAGE_HEIGHT - 2*yspace) * (os->height - j - 1) / (os->height) + yspace,
	      (double)os->colours[i][j].red / 255,
	      (double)os->colours[i][j].green / 255,
	      (double)os->colours[i][j].blue / 255);
    }
  }
}

static void write_ps_header(FILE *out) {
  fputs("%!PS-Adobe-2.0\n", out);
  fputs("%%Title: characterize output\n", out);
  fprintf(out, "%%%%BoundingBox: 0 0 %f %f\n", PAGE_WIDTH, PAGE_HEIGHT);
  fputs("%%Pages: 1\n", out);
  fputs("%%IncludeFreature: *PageSize Letter\n", out);
  fputs("%%EndSetup\n", out);
  fputs("/dcz {\n", out);
  fputs("newpath\n", out);
  fputs("setrgbcolor\n", out);
  fputs("moveto\n", out);
  fputs("show\n", out);
  fputs("} def\n", out);
}

static void write_ps_footer(FILE *out) {
  fputs("showpage\n", out);
  fputs("%%EOF\n", out);
}
