// cvs:  $ID$

/**
***   Class ColorName functions as an extension to NTC.class, which itself
*** is a port of ntc.js.  The existing NTC.names[][] array is public final,
*** so any sort must first make a copy.  In this case it is extended into
*** a list so that there can be hex, name, and hsi primitives.  The first
*** two are directly from the array.  The int hsi is calculated as the
*** list is built, in hhhhhhssssiiiiii form -- 6 bits of hue, 4 bits of
*** saturation, and 6 bits of intensity.  This will enable either an
*** alphabetical sort or a sort by hue.
***
***   v1.10, 10 Dec 2010, Richard W. Franzen
***
***   based on example provided by Rob Camick:
*** http://forums.oracle.com/forums/thread.jspa?threadID=1555000
***
***	This source is released under the: Creative Commons License:
***	Attribution 2.5 --  http://creativecommons.org/licenses/by/2.5/
***
*** v0.5+ doesn't use generics, which Java target 1.4 Java doesn't support
**/
package us.r0k.ntc;

import java.util.*;
import java.awt.Color;
import edu.mit.csail.people.jaffer.Hilb;

public class ColorName extends Color implements Comparable<ColorName>
{
    private String	name, hex;
    private int		hsi, lumi, luma, avg, h_rgb, h_lab, h_hsb;
    final static int nt_HSI   = 0;
    final static int nt_LUMI  = 1;
    final static int nt_LUMA  = 2;
    final static int nt_AVG   = 3;
    final static int nt_H_RGB = 4;
    final static int nt_H_LAB = 5;
    final static int nt_H_HSB = 6;

    static private List<ColorName> colorNames;

    // so many sorts now, giving them externally accessible names
    final public static int	byHEX   = -1;
    final public static int	byNAME  = -2;
    final public static int	byHSI   = -3;
    final public static int	byLUMI  = -4;
    final public static int	byLUMA  = -5;
    final public static int	byAVG   = -6;
    final public static int	byH_RGB = -7;
    final public static int	byH_LAB = -8;
    final public static int	byH_HSB = -9;

    // translate -byXXX to its nt_XXX equivalent
    private static int	by2nt[] = { 0, 0, 0, nt_HSI, nt_LUMI, nt_LUMA,
				    nt_AVG, nt_H_RGB, nt_H_LAB, nt_H_HSB };

    public ColorName(String name, String hex)
    {
	// I'm a Color now!  (needs to be first statement in constructor)
	super(Integer.valueOf(hex.substring(0,2), 16).intValue(),
	      Integer.valueOf(hex.substring(2,4), 16).intValue(),
	      Integer.valueOf(hex.substring(4,6), 16).intValue());

	float	hsb[] = {0f, 0f, 0f};
	int	h, s, b;	// b for brightness, same as i

	// prescale coefficents so sum will scale to 0..65535
	final float	redLumiC = .212671f * 257f;
	final float	grnLumiC = .715160f * 257f;
	final float	bluLumiC = .072169f * 257f;
	final float	redLumaC = .299f * 257f;
	final float	grnLumaC = .587f * 257f;
	final float	bluLumaC = .114f * 257f;
	final float	rgbAvgC  = .333333f * 257f;

	this.name = name;
	this.hex  = hex;

	// Now we need to calculate hsi,
	// which will be a scaled, quantized, and packed version of HSB.
	RGBtoHSB(getRed(), getGreen(), getBlue(), hsb);

	// Range h from 0 to 59.  That way there will be 10
	// increments per sextant, and sextant boundaries will be exact.
	// Note that hue won't ever be 1.0, but sat and bri will be.
	h = (int)(hsb[0] * 60f);	// 0..59
	s = (int)(hsb[1] * 15f);	// 0..15
	b = (int)(hsb[2] * 63f);	// 0..63

	// force all greys and very, very low sat colors together, before red
	if (s == 0)  h = 0;
	// consider (s = 15 - s;) so that most-saturated show first in a sextant
	this.hsi = (h << 16) + (s << 8) + b;  //  3 bytes, h, s, and b

	// make hilbert curve for hsb
	h = (int)(hsb[0] * 252f);	// 0..251 (84 increments/sextant)
	s = (int)(hsb[1] * 255f);	// 0..255
	b = (int)(hsb[2] * 255f);	// 0..255
	this.h_hsb = Hilb.hilbertIndex((b << 16) + (h << 8) + s);

	// calculate luminescence
	this.lumi = (int)(getRed()   * redLumiC +
			  getGreen() * grnLumiC +
			  getBlue()  * bluLumiC);
	// calculate luma
	this.luma = (int)(getRed()   * redLumaC +
			  getGreen() * grnLumaC +
			  getBlue()  * bluLumaC);
	// calculate average
	this.avg  = (int)(getRed()   * rgbAvgC +
			  getGreen() * rgbAvgC +
			  getBlue()  * rgbAvgC + 0.49f);

	// hilbert curve with rgb value
	this.h_rgb = Hilb.hilbertIndex((getRed()   << 16) +
				       (getGreen() <<  8) +
				        getBlue());

	// hilbert curve with CIE L*ab value
	// L is between 0 and 100, while a and b are between -110 and 110
	double xyz[] = {0, 0, 0};
	double lab[] = {0, 0, 0};
	NTC.RGBtoXYZ(getRed(), getGreen(), getBlue(), xyz);
	NTC.XYZtoCIElab(xyz[0], xyz[1], xyz[2], lab);
	int L = (int)(lab[0] * 2.55);
	int A = (int)((lab[1] + 110.0) * 255.0 / 220.0);
	int B = (int)((lab[2] + 110.0) * 255.0 / 220.0);
	this.h_lab = Hilb.hilbertIndex((L << 16) + (A << 8) + B);
    }

    public String getName() {
	return(name);
    }
    public String getHex() {
	return(hex);
    }

    public int getHSI() {
	return(hsi);
    }
    public int getHSI_Hue() {
	return((hsi >> 16) & 0xff);
    }
    public int getHSI_Sat() {
	return((hsi >> 8) & 0xff);
    }
    public int getHSI_Int() {
	return(hsi & 0xff);
    }

    public int getLuminance() {
	return(lumi);
    }
    public int getLuma() {
	return(luma);
    }
    public int getAverage() {
	return(avg);
    }
    public int getHilbRGB() {
	return(h_rgb);
    }
    public int getHilbLAB() {
	return(h_lab);
    }
    public int getHilbHSB() {
	return(h_hsb);
    }


    public String toString()
    {
	return(hex + ", " + Integer.toHexString(hsi) +
	       ":" + Integer.toHexString(lumi) +
	       ":" + Integer.toHexString(luma) +
	       ":" + Integer.toHexString(avg) +
	       ":" + Integer.toHexString(h_rgb) +
	       ":" + Integer.toHexString(h_lab) +
	       ":" + Integer.toHexString(h_hsb) + ", " + name);
    }


    /**
    ***  Implement the natural order for this class
    **/
    public int compareTo(ColorName c)
    {
	return getName().compareTo(c.getName());
    }

    static class NumericComparator implements Comparator<ColorName>
    {
        private int sortType;

        public NumericComparator(int numericSortType)
        {
            this.sortType = numericSortType;
        }

	public int compare(ColorName c1, ColorName c2)
	{
	    int		first, secnd;

	    switch (sortType)
	    {
	      case nt_H_HSB:
		first = c1.getHilbHSB();
		secnd = c2.getHilbHSB();
		break;
	      case nt_H_LAB:
		first = c1.getHilbLAB();
		secnd = c2.getHilbLAB();
		break;
	      case nt_H_RGB:
		first = c1.getHilbRGB();
		secnd = c2.getHilbRGB();
		break;
	      case nt_AVG:
		first = c1.getAverage();
		secnd = c2.getAverage();
		break;
	      case nt_LUMA:
		first = c1.getLuma();
		secnd = c2.getLuma();
	        break;
	      case nt_LUMI:
		first = c1.getLuminance();
		secnd = c2.getLuminance();
	        break;
	      default:	// nt_HSI, the original
		first = c1.getHSI();
		secnd = c2.getHSI();
	    }

	    if (first == secnd)
		return 0;
	      else if (first > secnd)
		return 1;
	      else
		return -1;
	}
    }

    /**
    ***   findColorName() performs 3 functions.
    *** First, it will auto-initialize itself if necessary.
    *** Second, it will perform multiple sorts.
    *** Third, on 0 or greater, will return the nth element of list
    *** Note: NTC.init() need not have been called.
    **/
    public static ColorName findColorName(int which)
    {   // force valid input
	if (which < byH_HSB)  which = byNAME;
	if (which >= NTC.names.length)  which = NTC.names.length - 1;
	// byHEX does not need a sort; the source is already sorted
	// byHSI and byAVG contain many duplicate keys.  By reading in the
	//   raw list prior to these sorts, it will always sort in same order.
	if ((which == byHEX) || (which == byHSI) || (which == byAVG))
	    colorNames = null;	// free up for garbage collection

	if (colorNames == null)
	{   // (re)create list
	    colorNames = new ArrayList <ColorName> (NTC.names.length);
	    for (int i = 0; i < NTC.names.length; i++)
	        colorNames.add(new ColorName(NTC.names[i][1], NTC.names[i][0]));
	}

	switch (which)
	{
	  case byHEX:  // have already (re)read items sorted by hex value
	    return(colorNames.get(0));

	  case byNAME:  // alphabetical
	    Collections.sort(colorNames);
	    return(colorNames.get(0));

	  case byHSI:	// algorithmic,by hue, saturation, and intensity
	  case byLUMI:	// luminescent coefficients
	  case byLUMA:	// luma coefficients
	  case byAVG:	// equal weight to r, g, and b
	  case byH_RGB:	// hilbert curva applied to rgb value
	  case byH_LAB:	// hilbert curve applied to CIE L*ab value
	  case byH_HSB:	// hilbert curve applied to CIE L*ab value
	    Collections.sort(colorNames,
		new NumericComparator(by2nt[-which]));
	    return(colorNames.get(0));
	}

	// no sort; just return an element
	return(colorNames.get(which));
    }

    public static void main(String args[])
    {   // allow stand-alone testing
	if (args.length == 0) {
	    System.out.println("usage: java ColorName sort# [file]");
	    System.out.println("enter:\t-1 for byHEX, -2 for byNAME, -3 for byHSI");
	    System.out.println("\t-4 for byLumi, -5 for byLUMA, -6 for byAVG");
	    System.out.println("\t-7 for byH_RGB, -8 for byH_LAB, -9 for byH_HSB");
	    System.out.println("example file name: cnd_ntc.properties");
	    System.exit(-1);
	}

	if (args.length > 1) {
	    if (!NTC.readCND(args[1]))  NTC.init();
	}

	if (args[0].equals("-9"))
	    System.out.println(findColorName(byH_HSB));
	  else if (args[0].equals("-8"))
	    System.out.println(findColorName(byH_LAB));
	  else if (args[0].equals("-7"))
	    System.out.println(findColorName(byH_RGB));
	  else if (args[0].equals("-6"))
	    System.out.println(findColorName(byAVG));
	  else if (args[0].equals("-5"))
	    System.out.println(findColorName(byLUMA));
	  else if (args[0].equals("-4"))
	    System.out.println(findColorName(byLUMI));
	  else if (args[0].equals("-3"))
	    System.out.println(findColorName(byHSI));
	  else if (args[0].equals("-2"))
	    System.out.println(findColorName(byNAME));
	  else if (args[0].equals("-1"))
	    System.out.println(findColorName(byHEX));
	  else
	  {
	      System.out.println("Illegal argument");
	      System.exit(-1);
	  }

	int i = 0;
	for (; i < NTC.names.length; i++)
	{
	    System.out.println(findColorName(i));
	    /* if ((i & 1) == 1)
	        System.out.println("");
	      else
	        System.out.print(" | "); */
	}
	// and check that the color property is working
	ColorName check = findColorName(i - 1);
	System.out.println("r=" + check.getRed() +
	    ", g=" + check.getGreen() +
	    ", b=" + check.getBlue());
    }
}

