# include "Ximage.h"
# include "jpeglib.h"

# define WHITE_R 255
# define WHITE_G 255
# define WHITE_B 255

static float slope = 1.0;
static float start = 0.0;
static int MaxValue = 255;

// XXX inline this if needed
static int PixelLookup(float value) {
  int out;
  out = MIN (MAX (slope * value - start, 0), MaxValue);
  return (out);
}

// XXX this currently writes out the jpeg for just the active image
int JPEGit24 (int sock) {

  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  JSAMPROW row_pointer[1];	/* pointer to JSAMPLE row[s] */
  JSAMPLE *image_buffer;	/* Points to data for current line */
  JSAMPLE *line_buffer;	        /* Points to data for current line */
  Section *section;
  Graphic *graphic;
  KapaImageWidget *image;

  int ii, i, j;
  int i_start, i_end, j_start, j_end;
  int dropback;  /* this is a bit of a kludge... */
  int dx, dy, DX, DY, pixelN;
  int status, Nbytes, quality;
  int expand_in, expand_out;
  double expand, Rx, Ry, X, Y;
  unsigned char *out_pix;
  float *imdata, *in_pix, *in_pix_ref;
  unsigned char pixel1[256], pixel2[256], pixel3[256];
  char filename[1024];
  FILE *f;

  /* expect a line telling the number of bytes and a filename */
  KiiScanMessage (sock, "%s", filename);

  graphic = GetGraphic();
  section = GetActiveSection();
  image   = section->image;
  if (image == NULL) return (TRUE);

  /***** JPEG init calls */
  cinfo.err = jpeg_std_error (&jerr);
  jpeg_create_compress (&cinfo);

  f = fopen (filename, "w");
  if (f == (FILE *) NULL) {
    fprintf (stderr, "Kii: failed to open %s for output\n", filename);
    return (TRUE);
  }
  jpeg_stdio_dest(&cinfo, f);
  
  quality = 75;
  cinfo.image_width = image[0].picture.dx; 	/* image width and height, in pixels */
  cinfo.image_height = image[0].picture.dy;
# ifdef GREYSCALE
  cinfo.input_components = 1;		        /* # of color components per pixel */
  cinfo.in_color_space = JCS_GRAYSCALE; 	/* colorspace of input image */
# else 
  cinfo.input_components = 3;		        
  cinfo.in_color_space = JCS_RGB; 	
# endif
  jpeg_set_defaults (&cinfo);
  jpeg_set_quality (&cinfo, quality, TRUE       /* limit to baseline-JPEG values */);
  jpeg_start_compress (&cinfo, TRUE);

  /** cmap[i].pixel must be defined even if X is not used **/
  for (i = 0; i < 256; i++) { /* set up pixel array */
    pixel1[i] = graphic[0].cmap[i].red >> 8;
    pixel2[i] = graphic[0].cmap[i].green >> 8;
    pixel3[i] = graphic[0].cmap[i].blue >> 8;
  }

  // define the color transform parameters
  MaxValue = graphic[0].Npixels - 1;
  if (image[0].range != 0.0) {
    slope = graphic[0].Npixels / image[0].range;
    start = graphic[0].Npixels * image[0].zero / image[0].range;
  } else {
    slope = 1.0;
    start = image[0].zero;
  }

  expand = expand_in = expand_out = 1.0;
  if (image[0].expand == 0) /* set up expansions */
    image[0].expand = 1;
  if (image[0].expand > 0) {
    expand = 1 / (1.0*image[0].expand);
    expand_out = image[0].expand;
    expand_in  = 1;
  }
  if (image[0].expand < 0) {
    expand = fabs((double)image[0].expand);
    expand_out = 1;
    expand_in  = -image[0].expand;
  }

  dx = image[0].picture.dx;
  dy = image[0].picture.dy;
  DX = image[0].matrix.Naxis[0];
  DY = image[0].matrix.Naxis[1];

  /* X,Y are the image coordinates of the first image pixel */
  X = MAX(0.5*(DX - dx*expand) - image[0].X, 0);
  if ((int)X != X) 
    X = (int) X + 1;
  else 
    X = (int) X;
  Y = MAX(0.5*(DY - dy*expand) - image[0].Y, 0);
  if ((int)Y != Y) 
    Y = (int) Y + 1;
  else 
    Y = (int) Y;

  /* Rx,Ry are the screen coordinates of the first image pixel */
  Rx = (X + image[0].X - 0.5*DX)/expand + 0.5*dx;
  Ry = (Y + image[0].Y - 0.5*DY)/expand + 0.5*dy;

  i_start = MIN (MAX (Rx, 0), dx - expand_out + 1);
  j_start = MIN (MAX (Ry, 0), dy - expand_out + 1);
  
  if (image[0].expand > 0) {
    i_end = MAX (MIN (i_start + ((int)(expand*(dx-i_start)))/expand, expand_out*(DX-X) + Rx), 0);
    j_end = MAX (MIN (j_start + ((int)(expand*(dy-j_start)))/expand, expand_out*(DY-Y) + Ry), 0);
  } else {
    i_end = MAX (MIN (dx, (DX-X)/expand + Rx), 0);
    j_end = MAX (MIN (dy, (DY-Y)/expand + Ry), 0);
  }    

  dropback = expand_out - (i_end - i_start) % expand_out;
  if ((i_end - i_start) % expand_out == 0) dropback = 0;

  /* output line buffer */
  ALLOCATE (image_buffer, JSAMPLE, 3*dx*dy);
  ALLOCATE (line_buffer, JSAMPLE, 3*dx);

  imdata = (float *) image[0].matrix.buffer;
  in_pix_ref = &imdata[DX*(int)MAX(Y,0) + (int)MAX(X,0)];

  /********** below we do the mapping from buffer pixels (in) to picture pixels (out) **********/

  /**** fill in bottom area ****/
  out_pix = line_buffer;
  for (i = 0; i < dx; i++, out_pix+=3) {
    out_pix[0] = WHITE_R;
    out_pix[1] = WHITE_G;
    out_pix[2] = WHITE_B;
  }
  for (j = 0; j < j_start; j++) {
    memcpy (&image_buffer[j*3*dx], line_buffer, 3*dx);
  }
  
  /*** fill in the image data region ***/
  for (j = j_start; j < j_end; j+= expand_out, in_pix_ref += expand_in*DX) {
    
    /* create one output image line */
    in_pix = in_pix_ref;
    out_pix = line_buffer;

    /**** fill in area to the left of the picture ****/
    for (i = 0; i < i_start; i++, out_pix+=3) {
      out_pix[0] = WHITE_R;
      out_pix[1] = WHITE_G;
      out_pix[2] = WHITE_B;
    }
    
    /*** fill in the picture region ***/
    for (i = i_start; i < i_end; i+=expand_out, in_pix+=expand_in) {
      pixelN = PixelLookup(*in_pix);
      for (ii = 0; ii < expand_out; ii++, out_pix+=3) {
	out_pix[0] = pixel1[pixelN];
	out_pix[1] = pixel2[pixelN];
	out_pix[2] = pixel3[pixelN];
      }
    }
    
    /**** fill in area to the right of the picture ****/
    for (i = i_end; i < dx; i++, out_pix+=3) {
      out_pix[0] = WHITE_R;
      out_pix[1] = WHITE_G;
      out_pix[2] = WHITE_B;
    }

    /* write out the image line expand_out times */
    for (i = 0; i < expand_out; i++) {
      memcpy (&image_buffer[(j + i)*3*dx], line_buffer, 3*dx);
    }
  }

  /**** fill in top area ****/
  out_pix = line_buffer;
  for (i = 0; i < dx; i++, out_pix+=3) { 
    out_pix[0] = WHITE_R;
    out_pix[1] = WHITE_G;
    out_pix[2] = WHITE_B;
  }
  for (j = j_end; j < dy; j++) {
    memcpy (&image_buffer[j*3*dx], line_buffer, 3*dx);
  }


  /* I need to write the overlay objects on the jpeg image.
     if i can write / overwrite data in jpeg buffer, then do it here,
     otherwise i need to create a temporary image buffer, then write the 
     scanlines to that buffer */

  {
    int Npalette;
    png_color *palette;
    bDrawColor white, color;
    bDrawBuffer *buffer;

    palette = KapaPNGPalette (&Npalette);

    buffer = bDrawBufferCreate (dx, dy);
    bDrawSetBuffer (buffer);
    for (i = 0; i < NOVERLAYS; i++) {
      if (image[i].overlay[i].active) bDrawOverlay (image, i);
    }

    white = KapaColorByName ("white");
    for (j = 0; j < dy; j++) {
      for (i = 0; i < dx; i++) {
	color = buffer[0].pixels[j][i];
	if (color == white) continue;
	image_buffer[j*3*dx + 3*i + 0] = palette[color].red;
	image_buffer[j*3*dx + 3*i + 1] = palette[color].green;
	image_buffer[j*3*dx + 3*i + 2] = palette[color].blue;
      }
    }
    bDrawBufferFree (buffer);
  }

  for (i = 0; i < dy; i++) {
    row_pointer[0] = &image_buffer[i*3*dx];
    (void) jpeg_write_scanlines (&cinfo, row_pointer, 1);
  }

  jpeg_finish_compress (&cinfo);
  fclose (f);
  jpeg_destroy_compress (&cinfo);

  return (TRUE);
}
