//
// Copyright (C) 1999-2002 Toshikaz Hirabayashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// TOSHIKAZ HIRABAYASHI BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Except as contained in this notice, the name of Toshikaz Hirabayashi shall
// not be used in advertising or otherwise to promote the sale, use or other
// dealings in this Software without prior written authorization from
// Toshikaz Hirabayashi.

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <WScom.h>
#include <unistd.h>

extern "C" {
#include <jpeglib.h>
extern long JpegReadFileToPixmap(Display* display,Window window,GC gc,
             char* filename,Pixmap* pixmap,long* w,long* h);
extern long JpegWriteBufToFp(FILE* fp, char* buf,long width,long height);
};

static void ehandler(j_common_ptr jinf,int level){
}

static void exhandler(j_common_ptr jinf){
}
static int chandler(j_decompress_ptr jinf){
return TRUE;
}

long JpegReadFileToPixmap(Display* display,Window window,GC gc,char* filename,Pixmap* pixmap,long* w,long* h){

    int red_mask, green_mask, blue_mask;
    int red_shift, green_shift, blue_shift;
    int start_shift, msb_flag;
    unsigned int start_mask, udat;
    XWindowAttributes win_attr;

    red_mask = green_mask = blue_mask = 0;
    red_shift = green_shift = blue_shift = 0;

    FILE* ifile = fopen(filename,"r");
    if (ifile == NULL){
      return -1;
    }
    struct jpeg_decompress_struct    jinf;
    struct jpeg_error_mgr            jerr;

    jinf.err = jpeg_std_error(&jerr);
    jinf.err->emit_message = ehandler;
    jinf.err->error_exit = exhandler;

    jpeg_create_decompress(&jinf);
    jpeg_set_marker_processor(&jinf,JPEG_COM,chandler);

    jpeg_stdio_src(&jinf,ifile);
    jpeg_read_header(&jinf,TRUE);
    jpeg_calc_output_dimensions(&jinf);

    long display_depth = XDefaultDepth(display,XDefaultScreen(display));

    jpeg_start_decompress(&jinf);

    long rwidth = jinf.output_width;
    long rheight = jinf.output_height;
    long vwidth = *w;
    long vheight = *h;
    long stretched =0;
    if (*w == 0 || *h == 0){
      *w = rwidth;
      *h = rheight;
    }else{
      if ((long)((double)rwidth * vheight/vwidth) < rheight){
        *w = (long)((double)vheight * rwidth/rheight);
      }else{
        *h = (long)((double)vwidth * rheight/rwidth);
      }
      stretched = 1;
    }
    vwidth = *w;
    vheight = *h;
//    long bpp = jinf.data_precision;

    long components = jinf.output_components;
//printf("jpg::readjpg w,h=%d,%d  c=%d\n",width,height,components);

//    unsigned char* buf = (unsigned char*)malloc(components * height * width*sizeof(JSAMPLE));
    unsigned char* buf = new unsigned char[components * rheight * rwidth*sizeof(JSAMPLE)];
    JSAMPROW rowptr[1];
    long i;
    for(i=0; i< rheight; i++){
      rowptr[0] = (JSAMPROW)&(buf[i * rwidth * components]);
      jpeg_read_scanlines(&jinf, rowptr,1);
    }

    XImage* image = 0;
    Visual* visual = XDefaultVisual(display,XDefaultScreen(display));
    if (display_depth >16){
      image = XCreateImage(display,visual, display_depth,
                           ZPixmap,0,0,vwidth,vheight,32,0);
    }else
    if (display_depth >8){
      image = XCreateImage(display,visual, display_depth,
                           ZPixmap,0,0,vwidth,vheight,16,0);
    }else{
      image = XCreateImage(display,visual, display_depth,
                           ZPixmap,0,0,vwidth,vheight,8,0);
    }

    msb_flag = (ImageByteOrder(display) == MSBFirst)?1:0;

    if (XGetWindowAttributes(display,
                     RootWindow(display, DefaultScreen(display)),
                     &win_attr) == 0) {
        fclose(ifile);
                 return -1;
    }
    //
    if ((win_attr.depth == 24) || (win_attr.depth == 16)) {
      if (win_attr.depth == 24) {
        start_shift = 24;
        start_mask = 0x80000000;
      }else{
        start_shift = 8;
        start_mask = 0x8000;
      }
      red_mask = win_attr.visual->red_mask;
      red_shift = start_shift;
      unsigned int n = start_mask;
      while (!(n & red_mask)) {
        n >>= 1;
        red_shift--;
      }
      green_mask = win_attr.visual->green_mask;
      green_shift = start_shift;
      n = start_mask;
      while (!(n & green_mask)) {
        n >>= 1;
        green_shift--;
      }
      blue_mask = win_attr.visual->blue_mask;
      blue_shift = start_shift;
      n = start_mask;
      while (!(n & blue_mask)) {
        n >>= 1;
        blue_shift--;
      }
    }

    char* data1 = new char[image->bytes_per_line * vheight];
    unsigned char r,g,b;
    long ptr = 0;
    long ptr2 = 0;
    long j;
    if (image->bits_per_pixel ==32){
      if (components == 3){
        for(i=0; i<vheight; i++){
          for(j=0; j<vwidth; j++){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight)*rwidth
                   + (long)((double)j*rwidth/vwidth);
              ptr *= 3;
            }
            r = buf[ptr++];
            g = buf[ptr++];
            b = buf[ptr++];

            udat = 0;
            if (red_shift >= 0){
              udat |= (((int)r << red_shift) & red_mask);
            }else{
              udat |= (((int)r >> (-red_shift)) & red_mask);
            }
            if (green_shift >= 0){
              udat |= (((int)g << green_shift) & green_mask);
            }else{
              udat |= (((int)g >> (-green_shift)) & green_mask);
            }
            if (blue_shift >= 0){
              udat |= (((int)b << blue_shift) & blue_mask);
            }else{
              udat |= (((int)b >> (-blue_shift)) & blue_mask);
            }
            if (msb_flag){
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff000000)>>24;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff0000)>>16;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff00)>>8;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff);
            }else{
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff);
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff00)>>8;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff0000)>>16;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff000000)>>24;
            }
          }
        }
      }else{
        for(i=0; i<vheight; i++){
          for(j=0; j<vwidth; j++){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight)*rwidth
                   + (long)((double)j*rwidth/vwidth);
            }
            r = buf[ptr];
            g = buf[ptr];
            b = buf[ptr++];
            if (msb_flag){
              ((unsigned char*)data1)[ptr2++] =  0;
              ((unsigned char*)data1)[ptr2++] =  b;
              ((unsigned char*)data1)[ptr2++] =  g;
              ((unsigned char*)data1)[ptr2++] =  r;
            }else{
              ((unsigned char*)data1)[ptr2++] =  r;
              ((unsigned char*)data1)[ptr2++] =  g;
              ((unsigned char*)data1)[ptr2++] =  b;
              ((unsigned char*)data1)[ptr2++] =  0;
            }
          }
        }
      }
    }else if (image->bits_per_pixel == 24){
      if (components == 3){
        for(i=0; i<vheight; i++){
          ptr2 = i * image->bytes_per_line;
          for(j=0; j<vwidth; j++){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight)*rwidth
                   + (long)((double)j*rwidth/vwidth);
              ptr *= 3;
            }
            r = buf[ptr++];
            g = buf[ptr++];
            b = buf[ptr++];

            udat = 0;
            if (red_shift >= 0){
              udat |= (((int)r << red_shift) & red_mask);
            }else{
              udat |= (((int)r >> (-red_shift)) & red_mask);
            }
            if (green_shift >= 0){
              udat |= (((int)g << green_shift) & green_mask);
            }else{
              udat |= (((int)g >> (-green_shift)) & green_mask);
            }
            if (blue_shift >= 0){
              udat |= (((int)b << blue_shift) & blue_mask);
            }else{
              udat |= (((int)b >> (-blue_shift)) & blue_mask);
            }

            if (msb_flag){
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff0000)>>16;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff00)>>8;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff);
            }else{
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff);
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff00)>>8;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff0000)>>16;
            }
          }
        }
      }else{
        for(i=0; i<vheight; i++){
          for(j=0; j<vwidth; j++){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight)*rwidth
                   + (long)((double)j*rwidth/vwidth);
            }
            r = buf[ptr];
            g = buf[ptr];
            b = buf[ptr++];
            if (msb_flag){
              ((unsigned char*)data1)[ptr2++] =  b;
              ((unsigned char*)data1)[ptr2++] =  g;
              ((unsigned char*)data1)[ptr2++] =  r;
            }else{
              ((unsigned char*)data1)[ptr2++] =  r;
              ((unsigned char*)data1)[ptr2++] =  g;
              ((unsigned char*)data1)[ptr2++] =  b;
            }
          }
        }
      }
    }else if (image->bits_per_pixel ==16){
      if (components == 3){
        for(i=0; i<vheight; i++){
          for(j=0; j<vwidth; j++){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight+0.5)*rwidth
                   + (long)((double)j*rwidth/vwidth + 0.5);
              ptr *= 3;
            }
            unsigned int rr = buf[ptr++];
            unsigned int gg = buf[ptr++];
            unsigned int bb = buf[ptr++];

            udat = 0;
            if (red_shift >= 0){
              udat |= (((int)rr << red_shift) & red_mask);
            }else{
              udat |= (((int)rr >> (-red_shift)) & red_mask);
            }
            if (green_shift >= 0){
              udat |= (((int)gg << green_shift) & green_mask);
            }else{
              udat |= (((int)gg >> (-green_shift)) & green_mask);
            }
            if (blue_shift >= 0){
              udat |= (((int)bb << blue_shift) & blue_mask);
            }else{
              udat |= (((int)bb >> (-blue_shift)) & blue_mask);
            }
            if (msb_flag){
              ((unsigned char*)data1)[ptr2++] = (udat >> 8) & 0xff;
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff);
            }else{
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff);
              ((unsigned char*)data1)[ptr2++] = (udat & 0xff00)>>8;
            }
          }
        }
      }else{
        for(i=0; i<vheight; i++){
          for(j=0; j<vwidth; j++){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight)*rwidth
                   + (long)((double)j*rwidth/vwidth);
            }
            r = buf[ptr]>>3;
            g = buf[ptr]>>2;
            b = buf[ptr++]>>3;
            ((short*)data1)[ptr2++] =  r <<11 | g<<5 | b;
          }
        }
      }
    }else if (image->bits_per_pixel == 8){
//printf("components=%d\n",components);
      XColor col[5*5*5];
      Colormap cm = DefaultColormap(display,DefaultScreen(display));
      long k;
      long cnt=0;
      for(i=0; i<5; i++){
        for(j=0; j<5; j++){
          for(k=0; k<5; k++){
            if (i != 4){
              col[cnt].red = i*64*256;
            }else{
              col[cnt].red = 0xffff;
            }
            if (j != 4){
              col[cnt].green = j*64*256;
            }else{
              col[cnt].green = 0xffff;
            }
            if (k != 4){
              col[cnt].blue = k*64*256;
            }else{
              col[cnt].blue = 0xffff;
            }
            XAllocColor(display,cm,&(col[cnt]));
            cnt++;
          }
        }
      }

      long colptr = 0;
      long rr = 0,gg = 0,bb = 0;
      long tr1,tg1,tb1;
      signed char dr1,dg1,db1;
      for(i=0; i<vheight; i++){
        dr1 = 0;
        dg1 = 0;
        db1 = 0;
        for(j=0; j<vwidth; j++){

          if (components == 3){
            if (stretched != 0){
              ptr = (long)((double)i*rheight/vheight)*rwidth
                     + (long)((double)j*rwidth/vwidth);
              ptr *= 3;
            }
            tr1 = buf[ptr++];
            tg1 = buf[ptr++];
            tb1 = buf[ptr++];
          }else{
            ptr = (long)((double)i*rheight/vheight)*rwidth
                     + (long)((double)j*rwidth/vwidth);
            tr1 = buf[ptr];
            tg1 = buf[ptr];
            tb1 = buf[ptr++];
          }
          if ((0 < tr1 + dr1) && (tr1 + dr1) < 256){
            if ((-32 < dr1) && (dr1 < 32)){
              rr = (tr1 + dr1 + 31) & 0x1c0;
            }else if (dr1 > 31){
              rr = (tr1 + 63) & 0x1c0;
            }else{
              rr = tr1  & 0x1c0;
            }
          }else if (tr1 + dr1 > 255){
            rr = 0x100;
          }else{
            rr = 0;
          }
          dr1 += (tr1 - rr);
          if (0 < tg1 + dg1  &&tg1 + dg1 < 256){
            if (-32 < dg1 && dg1 < 32 ){
              gg = (tg1 + dg1 + 31) & 0x1c0;
            }else if (dg1 > 31){
              gg = (tg1 + 63) & 0x1c0;
            }else{
              gg = tg1  & 0x1c0;
            }
          }else if (tg1 + dg1 > 255){
            gg = 0x100;
          }else{
            gg = 0;
          }
          dg1 += (tg1 - gg);

          if (0 < tb1 + db1 && tb1 + db1 < 256){
            if (-32 < db1 && db1 < 32){
              bb = (tb1 + db1 + 31) & 0x1c0;
            }else if (db1 > 31){
              bb = (tb1 + 63) & 0x1c0;
            }else{
              bb = tb1 & 0x1c0;
            }
          }else if (tb1 + db1 > 255){
            bb = 0x100;
          }else{
            bb = 0;
          }
          db1 += (tb1 - bb);
          db1 += (tb1 - bb);
          colptr = (rr>>6)*5*5  + (gg>>6)*5 + (bb>>6);
          data1[ptr2++] =  col[colptr].pixel;
        }
      }
    }
    image->data = data1;
    delete buf;
    Pixmap pix = XCreatePixmap(display,window, vwidth,vheight,display_depth);
    *pixmap = pix;
    XPutImage(display,pix,gc,image,0,0,0,0,vwidth,vheight);

    jpeg_finish_decompress(&jinf);
    jpeg_destroy_decompress(&jinf);

    if (image->data != NULL){
      delete image->data;
      image->data = NULL;
    }
    XDestroyImage(image);

    fclose(ifile);
    return 0;
}

long JpegWriteBufToFp(FILE* fp, char* buf,long width,long height){
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;

  JSAMPROW row_pointer[0];
  int row_stride;

  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);

  jpeg_stdio_dest(&cinfo, fp);
  cinfo.image_width = width;
  cinfo.image_height = height;
  cinfo.input_components = 3;
  cinfo.in_color_space = JCS_RGB;

  jpeg_set_defaults(&cinfo);
  jpeg_set_quality(&cinfo, 100, TRUE );
  jpeg_start_compress(&cinfo, TRUE);

  row_stride = width * 3;

  while (cinfo.next_scanline < cinfo.image_height) {
    row_pointer[0] = (JSAMPLE*)& buf[cinfo.next_scanline * row_stride];
    (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
  }
  jpeg_finish_compress(&cinfo);
  jpeg_destroy_compress(&cinfo);

  return WS_NO_ERR;
}

