#include <stdio.h>
#include <X11/Xlib.h>
#ifndef NO_GTK
#include <gdk/gdk.h>
#endif

#include "imager.h"

int pal[256][3];
int rgb[256][3];
int palsize;
unsigned long pixels[256];
#if 0
void xqc_createpalette(Colormap cmap) {
  unsigned int trysize, i;
  for (trysize=6; trysize>=2; trysize--) {
    unsigned int ncol;
    unsigned long ign;
    XColor ctable[256];
#ifdef DEBUG
    fprintf(stderr, "trying %dx%dx%d palette...",
      trysize, trysize, trysize);  fflush(stderr);
#endif
    ncol = trysize * trysize * trysize;
    allocate_rgb_palette(trysize, pal, rgb);
    if (!XAllocColorCells(disp, cmap, 1, &ign, 0, pixels, ncol)) {
#ifdef DEBUG
      fprintf(stderr, " failed\n");
#endif
      continue;
    }
    for (i=0; i<ncol; i++) {
      ctable[i].pixel = pixels[i];
      ctable[i].red = pal[i][0] * 256;
      ctable[i].green = pal[i][1] * 256;
      ctable[i].blue = pal[i][2] * 256;
      ctable[i].flags = DoRed | DoGreen | DoBlue;
    }
    XStoreColors(disp, cmap, ctable, ncol);
    palsize = trysize;
#ifdef DEBUG
    fprintf(stderr, " success.\n");
#endif
    return;
  }
  fprintf(stderr, "Could not allocate even 8 color cells.\n");
  fprintf(stderr, "Close color-hungry programs, change your color depth,\n");
  fprintf(stderr, "or use the -p+ option.\n");
  exit(-1);
}
#endif

static int highbit(unsigned long n) {
  int i;
  for (i=31; ((n & 0x80000000) == 0) && i>=0; i--)
    n <<= 1;
  return i;
}

void prepare_ximage(unsigned char *to, unsigned char *from, int width,
  int height, int bpp, void *visual, int Gdk) {
  int depth, i;
  int red_mask, green_mask, blue_mask;
  int limit = width * height;
  if (Gdk) {
#ifndef NO_GTK
    GdkVisual *v = (GdkVisual*)visual;
    depth = v->depth;
    red_mask   = v->red_mask;    green_mask  = v->green_mask;
    blue_mask  = v->blue_mask;
#else
    fprintf(stderr,
      "prepare_ximage() called with Gdk=1, but compiled without GDK support\n");
    exit(-1);
#endif
  }
  else {
    Visual *v = (Visual*)visual;
    depth = v->bits_per_rgb;
    red_mask   = v->red_mask;    green_mask  = v->green_mask;
    blue_mask  = v->blue_mask;
  }
  
  /* since we're using the system default visual whether or not we're in
   * g[td]k, we can assume that depth==8 -> visual==Pseudocolor */
  switch (depth) {
    case 8:
      to = rgb_2_pal(from, width, height, palsize, pal, rgb);
#ifdef NO_ASM
      for (i=0; i<limit; i++, to++)
        *to = pixels[*to];
#else
      __asm__ (
      /* ecx, edi, and esi have to get saved because gcc 2.95 no longer
       * provides a clean way to indicate that input registers will be
       * clobbered. */
        "push %%ecx\n"
        "push %%esi\n"
        "push %%edi\n"
        "cld\n"
        "1: lodsb\n"
        "xlatb\n"
        "stosb\n"
        "loop 1b\n"
        "pop %%edi\n"
        "pop %%esi\n"
        "pop %%ecx"
        :
        : "b" ((long)pixels), "c" (limit), "D" ((long)to), "S" ((long)to)
        : "ax", "cc");
#endif
      break;
    case 15:
    case 16: {
#ifndef NO_ASM
        /* make 16-bit truecolor weight=565 faster    */
        /* because that's how my X is configured  ;-) */
        if (red_mask == 0xf800 && green_mask == 0x7e0 && blue_mask == 0x1f) {
          __asm__ ("cld\n"
          /* ecx, edi, and esi have to get saved because gcc 2.95 no longer
           * provides a clean way to indicate that input registers will be
           * clobbered. */
            "push %%ecx\n"
            "push %%esi\n"
            "push %%edi\n"
            "1: lodsb\n"
            "shl $8,%%eax\n"
            "and $0xf800,%%eax\n"
            "mov %%eax,%%edx\n"
            "lodsb\n"
            "shl $3,%%eax\n"
            "and $0x7e0,%%eax\n"
            "or %%eax,%%edx\n"
            "lodsb\n"
            "shr $3,%%eax\n"
            "and $0x1f,%%eax\n"
            "or %%edx,%%eax\n"
            "stosb\n"
            "shr $8,%%eax\n"
            "stosb\n"
            "loop 1b\n"
            "pop %%edi\n"
            "pop %%esi\n"
            "pop %%ecx"
            :
            : "c" (limit), "D" ((long)to), "S" ((long)from)
            : "dx", "ax", "cc");
        }
        else {
#endif
        int rshift = 15 - highbit(red_mask);
        int gshift = 15 - highbit(green_mask);
        int bshift = 15 - highbit(blue_mask);
        unsigned char *sbp = (unsigned char *)to;
        const unsigned char *scp = from;
        unsigned int temp;
        for (i=0; i<limit; i++) {
          temp  = (((*scp++ << 8) >> rshift) & red_mask);
          temp |= (((*scp++ << 8) >> gshift) & green_mask);
          temp |= (((*scp++ << 8) >> bshift) & blue_mask);
          *sbp++ = temp & 0xff;
          *sbp++ = (temp >> 8) & 0xff;
        }
      }
#ifndef NO_ASM
      }
#endif
      break;
    case 32:  /* do we ever actually see depth==32? */
    case 24: {
#ifndef NO_ASM
        /* make 16-bit truecolor weight=565 faster    */
        /* because that's how my X is configured  ;-) */
        if (red_mask == 0xff0000 && green_mask == 0xff00 && blue_mask == 0xff) {
          __asm__ (
          /* ecx, edi, and esi have to get saved because gcc 2.95 no longer
           * provides a clean way to indicate that input registers will be
           * clobbered. */
            "push %%ecx\n"
            "push %%esi\n"
            "push %%edi\n"
            "cld\n"
            "1: lodsw\n"
            "xchg %%al,%%ah\n"
            "shl $8,%%eax\n"
            "lodsb\n"
            "cmp $32,%%edx\n"
            "jne 2f\n"
            "stosl\n"
            "jmp 3f\n"
            "2: stosw\n"
            "shr $16,%%eax\n"
            "stosb\n"
            "3: loop 1b\n"
            "pop %%edi\n"
            "pop %%esi\n"
            "pop %%ecx"
            :
            : "c" (limit), "D" ((long)to), "S" ((long)from), "d" (bpp)
            : "ax", "cc");
        }
        else {
#endif
        int rshift = highbit(red_mask)   - 7;
        int gshift = highbit(green_mask) - 7;
        int bshift = highbit(blue_mask)  - 7;
        unsigned char *sbp = (unsigned char *)to;
        const unsigned char *scp = from;
        unsigned int temp;
        for (i=0; i<limit; i++) {
          temp  = (*scp++ << rshift);
          temp |= (*scp++ << gshift);
          temp |= (*scp++ << bshift);
          if (bpp == 32) {
            *sbp++ = (unsigned char)(temp         & 0xff);
            *sbp++ = (unsigned char)((temp >> 8)  & 0xff);
            *sbp++ = (unsigned char)((temp >> 16) & 0xff);
            *sbp++ = (unsigned char)((temp >> 24) & 0xff);
          }
          else {  /* bpp == 24, or at least we hope so... */
            *sbp++ = (unsigned char)(temp         & 0xff);
            *sbp++ = (unsigned char)((temp >> 8)  & 0xff);
            *sbp++ = (unsigned char)((temp >> 16) & 0xff);
          }
        }
      }
#ifndef NO_ASM
      }
#endif
      break;
  }
}
