package VASSAL.build.module.gamepieceimage;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;

/** Converts an RGB image to 8-bit index color using Heckbert's median-cut
   color quantization algorithm. Based on median.c by Anton Kruger from the
   September, 1994 issue of Dr. Dobbs Journal.
   @deprecated
*/
@Deprecated public class MedianCut {
   
   static final int MAXCOLORS = 256;   // maximum # of output colors
   static final int HSIZE = 32768;     // size of image histogram
   private int[] hist;                 // RGB histogram and reverse color lookup table
   private int[] histPtr;              // points to colors in "hist"
   private Cube[] list;                // list of cubes
   private int[] pixels32;
   private int width, height;
   private IndexColorModel cm; 

   public MedianCut(BufferedImage image) {
     int color16;
     
     width = image.getWidth();
     height = image.getHeight();
     pixels32 = image.getRGB(0, 0, width, height, null, 0, width);
     
     //build 32x32x32 RGB histogram
     hist = new int[HSIZE];
     for (int i=0; i<width*height; i++) {
         color16 = rgb(pixels32[i]);
         hist[color16]++;
     }
   }
   
   int getColorCount() {
       int count = 0;
       for (int i=0; i<HSIZE; i++)
           if (hist[i]>0) count++;
       return count;
   }
   

   Color getModalColor() {
       int max=0;
       int c = 0;
       for (int i=0; i<HSIZE; i++)
           if (hist[i]>max) {
               max = hist[i];
               c = i;
           }
       return new Color(red(c), green(c), blue(c));
   }
   

   // Convert from 24-bit to 15-bit color
   private final int rgb(int c) {
       int r = (c&0xf80000)>>19;
       int g = (c&0xf800)>>6;
       int b = (c&0xf8)<<7;
       return b | g | r;
   }
   
   // Get red component of a 15-bit color
   private final int red(int x) {
       return (x&31)<<3;
   }
   
   // Get green component of a 15-bit color
   private final int green(int x) {
       return (x>>2)&0xf8;
   }
   
   // Get blue component of a 15-bit color
   private final int blue(int x) {
       return (x>>7)&0xf8;
   }


   /** Uses Heckbert's median-cut algorithm to divide the color space defined by
   "hist" into "maxcubes" cubes. The centroids (average value) of each cube
   are are used to create a color table. "hist" is then updated to function
   as an inverse color map that is used to generate an 8-bit image. */
   public BufferedImage convert(int maxcubes) {
       return convertToByte(maxcubes);
   }
   
   public IndexColorModel buildColorModel(int maxcubes) {
     convertToByte(maxcubes);
     return cm;
   }

   /** This is a version of convert that returns a ByteProcessor. */
   public BufferedImage convertToByte(int maxcubes) {
       int lr, lg, lb;
       int i, median, color;
       int count;
       int k, level, ncubes, splitpos;
       int longdim=0;  //longest dimension of cube
       Cube cube, cubeA, cubeB;
       
       // Create initial cube
       list = new Cube[MAXCOLORS];
       histPtr = new int[HSIZE];
       ncubes = 0;
       cube = new Cube();
       for (i=0,color=0; i<=HSIZE-1; i++) {
           if (hist[i] != 0) {
               histPtr[color++] = i;
               cube.count = cube.count + hist[i];
           }
       }
       cube.lower = 0; cube.upper = color-1;
       cube.level = 0;
       Shrink(cube);
       list[ncubes++] = cube;

       //Main loop
       while (ncubes < maxcubes) { 

           // Search the list of cubes for next cube to split, the lowest level cube
           level = 255; splitpos = -1; 
           for (k=0; k<=ncubes-1; k++) {
               if (list[k].lower == list[k].upper)  
                   ;   // single color; cannot be split
               else if (list[k].level < level) {
                   level = list[k].level;
                   splitpos = k;
               }
           }
           if (splitpos == -1) // no more cubes to split
               break;

           // Find longest dimension of this cube
           cube = list[splitpos];
           lr = cube.rmax - cube.rmin;
           lg = cube.gmax - cube.gmin;
           lb = cube.bmax - cube.bmin;
           if (lr >= lg && lr >= lb) longdim = 0;
           if (lg >= lr && lg >= lb) longdim = 1;
           if (lb >= lr && lb >= lg) longdim = 2;
           
           // Sort along "longdim"
           reorderColors(histPtr, cube.lower, cube.upper, longdim);
           quickSort(histPtr, cube.lower, cube.upper);
           restoreColorOrder(histPtr, cube.lower, cube.upper, longdim);

           // Find median
           count = 0;
           for (i=cube.lower;i<=cube.upper-1;i++) {
               if (count >= cube.count/2) break;
               color = histPtr[i];
               count = count + hist[color];
           }
           median = i;

           // Now split "cube" at the median and add the two new
           // cubes to the list of cubes.
           cubeA = new Cube();
           cubeA.lower = cube.lower; 
           cubeA.upper = median-1;
           cubeA.count = count;
           cubeA.level = cube.level + 1;
           Shrink(cubeA);
           list[splitpos] = cubeA;             // add in old slot

           cubeB = new Cube();
           cubeB.lower = median; 
           cubeB.upper = cube.upper; 
           cubeB.count = cube.count - count;
           cubeB.level = cube.level + 1;
           Shrink(cubeB);
           list[ncubes++] = cubeB;             // add in new slot */
       }

       // We have enough cubes, or we have split all we can. Now
       // compute the color map, the inverse color map, and return
       // an 8-bit image.
       makeInverseMap(hist, ncubes);
       return makeImage();
   }
   
   void Shrink(Cube cube) {
   // Encloses "cube" with a tight-fitting cube by updating the
   // (rmin,gmin,bmin) and (rmax,gmax,bmax) members of "cube".

       int r, g, b;
       int color;
       int rmin, rmax, gmin, gmax, bmin, bmax;

       rmin = 255; rmax = 0;
       gmin = 255; gmax = 0;
       bmin = 255; bmax = 0;
       for (int i=cube.lower; i<=cube.upper; i++) {
           color = histPtr[i];
           r = red(color);
           g = green(color);
           b = blue(color);
           if (r > rmax) rmax = r;
           if (r < rmin) rmin = r;
           if (g > gmax) gmax = g;
           if (g < gmin) gmin = g;
           if (b > bmax) bmax = b;
           if (b < bmin) bmin = b;
       }
       cube.rmin = rmin; cube.rmax = rmax;
       cube.gmin = gmin; cube.gmax = gmax;
       cube.bmin = bmin; cube.bmax = bmax;
   }


   void makeInverseMap(int[] hist, int ncubes) {
   // For each cube in the list of cubes, computes the centroid
   // (average value) of the colors enclosed by that cube, and
   // then loads the centroids in the color map. Next loads
   // "hist" with indices into the color map

       int r, g, b;
       int color;
       float rsum, gsum, bsum;
       Cube cube;
       byte[] rLUT = new byte[256];
       byte[] gLUT = new byte[256];
       byte[] bLUT = new byte[256];

       for (int k=0; k<=ncubes-1; k++) {
           cube = list[k];
           rsum = gsum = bsum = (float)0.0;
           for (int i=cube.lower; i<=cube.upper; i++) {
               color = histPtr[i];
               r = red(color);
               rsum += (float)r*(float)hist[color];
               g = green(color);
               gsum += (float)g*(float)hist[color];
               b = blue(color);
               bsum += (float)b*(float)hist[color];
           }

           // Update the color map
           r = (int)(rsum/(float)cube.count);
           g = (int)(gsum/(float)cube.count);
           b = (int)(bsum/(float)cube.count);
           if (r==248 && g==248 && b==248)
               r=g=b=255;  // Restore white (255,255,255)
           rLUT[k] = (byte)r;
           gLUT[k] = (byte)g;
           bLUT[k] = (byte)b;
       }
       cm = new IndexColorModel(8, ncubes, rLUT, gLUT, bLUT);
       
       // For each color in each cube, load the corre- 
       // sponding slot in "hist" with the centroid of the cube.
       for (int k=0; k<=ncubes-1; k++) {
           cube = list[k];
           for (int i=cube.lower; i<=cube.upper; i++) {
               color = histPtr[i];
               hist[color] = k;
           }
       }
   }
   

   void reorderColors(int[] a, int lo, int hi, int longDim) {
   // Change the ordering of the 5-bit colors in each word of int[]
   // so we can sort on the 'longDim' color
   
       int c, r, g, b;
       switch (longDim) {
           case 0: //red
               for (int i=lo; i<=hi; i++) {
                   c = a[i];
                   r = c & 31;
                   a[i] = (r<<10) | (c>>5);
                   }
               break;
           case 1: //green
               for (int i=lo; i<=hi; i++) {
                   c = a[i];
                   r = c & 31;
                   g = (c>>5) & 31;
                   b = c>>10;
                   a[i] = (g<<10) | (b<<5) | r;
                   }
               break;
           case 2: //blue; already in the needed order
               break;
       }
   }
   

   void restoreColorOrder(int[] a, int lo, int hi, int longDim) {
   // Restore the 5-bit colors to the original order
   
       int c, r, g, b;
       switch (longDim){
           case 0: //red
               for (int i=lo; i<=hi; i++) {
                   c = a[i];
                   r = c >> 10;
                   a[i] = ((c&1023)<<5) | r;
               }
               break;
           case 1: //green
               for (int i=lo; i<=hi; i++) {
                   c = a[i];
                   r = c & 31;
                   g = c>>10;
                   b = (c>>5) & 31;
                   a[i] = (b<<10) | (g<<5) | r;
               }
               break;
           case 2: //blue
               break;
       }
   }
   
   
   void quickSort(int a[], int lo0, int hi0) {
  // Based on the QuickSort method by James Gosling from Sun's SortDemo applet
  
     int lo = lo0;
     int hi = hi0;
     int mid, t;

     if ( hi0 > lo0) {
        mid = a[ ( lo0 + hi0 ) / 2 ];
        while( lo <= hi ) {
           while( ( lo < hi0 ) && ( a[lo] < mid ) )
              ++lo;
           while( ( hi > lo0 ) && ( a[hi] > mid ) )
              --hi;
           if( lo <= hi ) {
             t = a[lo]; 
             a[lo] = a[hi];
             a[hi] = t;
              ++lo;
              --hi;
           }
        }
        if( lo0 < hi )
           quickSort( a, lo0, hi );
        if( lo < hi0 )
           quickSort( a, lo, hi0 );

     }
  }


   BufferedImage makeImage() {
   // Generate 8-bit image
   
       //Image img8;
       byte[] pixels8;
       int color16;
       
       pixels8 = new byte[width*height];
       for (int i=0; i<width*height; i++) {
           color16 = rgb(pixels32[i]);
           pixels8[i] = (byte)hist[color16];
       }

       SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width, new int[] {0}); 
       DataBufferByte Buffer = new DataBufferByte(pixels8, pixels8.length);
       WritableRaster raster = Raster.createWritableRaster(sampleModel, Buffer, null);

       return new BufferedImage(cm, raster, false, null);
   }
   
   
} //class MedianCut


class Cube {            // structure for a cube in color space
   int  lower;         // one corner's index in histogram
   int  upper;         // another corner's index in histogram
   int  count;         // cube's histogram count
   int  level;         // cube's level
   int  rmin, rmax;
   int  gmin, gmax;
   int  bmin, bmax;
   
   Cube() {
       count = 0;
   }   

   public String toString() {
       String s = "lower=" + lower + " upper=" + upper; //$NON-NLS-1$ //$NON-NLS-2$
       s = s + " count=" + count + " level=" + level; //$NON-NLS-1$ //$NON-NLS-2$
       s = s + " rmin=" + rmin + " rmax=" + rmax; //$NON-NLS-1$ //$NON-NLS-2$
       s = s + " gmin=" + gmin + " gmax=" + gmax; //$NON-NLS-1$ //$NON-NLS-2$
       s = s + " bmin=" + bmin + " bmax=" + bmax; //$NON-NLS-1$ //$NON-NLS-2$
       return s;
   }
   
}
 
