package nxm.sys.inc;

import java.awt.Color;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.regex.Pattern;

import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.ReflectUtil;

/** Interface created for visibility and JDK 11 modularity.
 *
 *  @author Kevin Esterheld
 *
 */
public interface MidasColor {

  /** Commonly used Color constants.
      @since NeXtMidas 3.5.3
  */
  public static final Color RED = getColor(80,00,00),  // #cc0000
                       DARK_RED = getColor(55,00,00),  // #8c0000
                 DARK_GOLDENROD = getColor(75,50,00),  // #bf8000
                         YELLOW = getColor(80,80,00),  // #cccc00
                    LIGHT_GREEN = getColor(25,85,00),  // #40d900
                     DARK_GREEN = getColor(00,75,00),  // #00bf00
                     LIME_GREEN = getColor(20,100,20), // #33ff33
                DARK_LIME_GREEN = getColor(00,60,00),  // #009900
                           CYAN = getColor(00,90,75),  // #00e6bf
                  MODERATE_BLUE = getColor(25,30,75),  // #404dbf
                  PEARLY_PURPLE = getColor(60,30,50),  // #994d80
                      DARK_GRAY = getColor(30,30,30);  // #4d4d4d
  /** Regex for white space */
  static final Pattern WHITESPACE_REGEX = Pattern.compile("\\s");

  /** Color map name list: <code>{@value}</code> */
  public static final String colorMapList =
      "GrayScale,Ramp,XRamp,Wheel,Terrain,Multi,T5Layer,MapLine,Grid,Spectrum,XDatalist";

  /** Color map number for GRAYSCALE */
  public static final int GRAYSCALE = 1;

  /** Color map number for RAMP */
  public static final int RAMP = 2;

  /** Color map number for XRAMP */
  public static final int XRAMP = 3;

  /** Color map number for WHEEL */
  public static final int WHEEL = 4;

  /** Color map number for TERRAIN */
  public static final int TERRAIN = 5;

  /** Color map number for MULTI */
  public static final int MULTI = 6;

  /** Color map number for T5 */
  public static final int T5 = 7;

  /** Color map number for MAPLINE */
  public static final int MAPLINE = 8;

  /** Color map number for GRID */
  public static final int GRID = 9;

  /** Color map number for SPECTRUM */
  public static final int SPECTRUM = 10;

  /** Color map number for XDATALIST */
  public static final int XDATALIST = 11;

  /** the maximum acceptable color map number for 2D layers */
  public static final int MAXTYPE2D = 6;

  /** the maximum acceptable color map number */
  public static final int MAXTYPE=9;

  /** Color index for CEP in T5 color map.
      @deprecated Since NeXtMidas 3.5.3, use {@link #T5_INDEX_CEP} instead.
   */
  @Deprecated
  public static final int C_CEP = 1;
  /** Color index for CEP in T5 color map
      @since 3.5.3
  */
  public static final int T5_INDEX_CEP = 1;

  /** Color index for HORIZON in T5 color map.
      @deprecated Since NeXtMidas 3.5.3, use {@link #T5_INDEX_HORIZON} instead.
   */
  @Deprecated
  public static final int C_HORIZON = 2;
  /** Color index for HORIZON in T5 color map.
      @since 3.5.3
  */
  public static final int T5_INDEX_HORIZON = 2;

  /** Color index for TDOA in T5 color map.
      @deprecated Since NeXtMidas 3.5.3, use {@link #T5_INDEX_TDOA} instead.
   */
  @Deprecated
  public static final int C_TDOA = 3;
  /** Color index for TDOA in T5 color map.
      @since 3.5.3
   */
  public static final int T5_INDEX_TDOA = 3;

  /** Color index for FDOA in T5 color map.
      @deprecated Since NeXtMidas 3.5.3, use {@link #T5_INDEX_FDOA} instead.
   */
  @Deprecated
  public static final int C_FDOA = 4;
  /** Color index for FDOA in T5 color map.
      @since 3.5.3
   */
  public static final int T5_INDEX_FDOA = 4;

  /** Color index for TRACE5000 in T5 color map.
      @deprecated Since NeXtMidas 3.5.3, use {@link #T5_INDEX_TRACE5000} instead.
   */
  @Deprecated
  public static final int C_T5000=5;
  /** Color index for TRACE5000 in T5 color map.
      @since 3.5.3
   */
  public static final int T5_INDEX_TRACE5000=5;

  /** Color flag used in a type 5000 in the FLAG field that gives a very dark gray (#4d4d4d) color.
      (This is same as {@link #T5_INDEX_CEP} = {@value #T5_INDEX_CEP}).
      @since 3.5.3 */
  public static final byte T5_INDEX_GRAY = (byte) T5_INDEX_CEP;

  /** Color flag used in a type 5000 in the FLAG field that gives a darkgreen (#00bf00) color.
      (This is same as {@link #T5_INDEX_HORIZON} = {@value #T5_INDEX_HORIZON}).
      @since 3.5.3 */
  public static final byte T5_INDEX_DARK_GREEN = (byte) T5_INDEX_HORIZON;

  /** Color flag used in a type 5000 in the FLAG field that gives a strong red (#cc0000) color.
      (This is same as {@link #T5_INDEX_TDOA} = {@value #T5_INDEX_TDOA}).
      @since 3.5.3 */
  public static final byte T5_INDEX_RED = (byte) T5_INDEX_TDOA;

  /** Color flag used in a type 5000 in the FLAG field that gives a strong yellow (#cccc00) color.
      (This is same as {@link #T5_INDEX_FDOA} = {@value #T5_INDEX_FDOA}).
      @since 3.5.3 */
  public static final byte T5_INDEX_YELLOW = (byte) T5_INDEX_FDOA;

  /** Color flag used in a type 5000 in the FLAG field that gives a vivid lime green (#33ff33) color.
      (This is same as {@link #T5_INDEX_TRACE5000} = {@value #T5_INDEX_TRACE5000}).
      @since 3.5.3 */
  public static final byte T5_INDEX_GREEN = (byte) T5_INDEX_TRACE5000;

  /** Color that matches X-Midas XGEOPLOT CEP color - dark gray (#4d4d4d).
      @since 3.5.3 */
  public static final Color T5_COLOR1_CEP = DARK_GRAY;

  /** Color that matches X-Midas XGEOPLOT HORIZON color - dark lime green (#009900).
      @since 3.5.3 */
  public static final Color T5_COLOR2_HORIZON = DARK_LIME_GREEN;

  /** Color that matches X-Midas XGEOPLOT TDOA color - strong red (#cc0000).
      @since 3.5.3 */
  public static final Color T5_COLOR3_TDOA = RED;

  /** Color that matches X-Midas XGEOPLOT FDOA color - strong yellow (#cccc00)
      @since 3.5.3 */
  public static final Color T5_COLOR4_FDOA = YELLOW;

  /** Color that matches X-Midas XGEOPLOT TRACE5000 color - lime green (#33ff33).
      @since 3.5.3 */
  public static final Color T5_COLOR5_TRACE5000 = LIME_GREEN;

  /** Color index for COASTLINE in MAPLINE color map */
  public static final int C_COASTLINE = 1;

  /** Color index for POLITICAL in MAPLINE color map */
  public static final int C_POLITICAL = 2;

  /** Color index for INTERPOL in MAPLINE color map */
  public static final int C_INTERPOL = 3;

  /** Color index for RIVER in MAPLINE color map */
  public static final int C_RIVER = 4;

  /** Color index for RAIL in MAPLINE color map */
  public static final int C_RAIL = 5;

  /** Color index for ROAD in MAPLINE color map */
  public static final int C_ROAD = 6;

  /** Color index for POL_OLD in MAPLINE color map */
  public static final int C_POL_OLD = 7;

  /** Color index for INTPOL_OLD in MAPLINE color map */
  public static final int C_INTPOL_OLD=8;

  /** Color index for axis in GRID color map */
  public static final int C_AXIS = 1;

  /** Color index for grid in GRID color map */
  public static final int  C_GRID=2;

  /** JGS why is this here , not used */
  public static final int NOSHARE=0x0001;

  /** Default number of colors */
  public static final int DEFCOLORS=32;

  /** Grayscale color map */
  public static final byte[] CM_GRAY = {0,0,0,0, 100,100,100,100};
  /** NeXtMidas ramp color map */
  public static final byte[] CM_RAMP = {0,10,0,23, 18,34,0,60, 36,58,20,47,
        55,74,20,28, 72,90,43,0, 87,100,72,0, 100,100,100,76};
  /** Wheel color map */
  public static final byte[] CM_WHEEL = {0,100,100,0, 20,0,80,40, 30,0,100,100,
        50,10,10,90, 65,100,0,100, 88,100,40,0, 100,100,100,0};
  /** Terrain color map */
  public static final byte[] CM_TERRAIN = {0,0,0,40, 50,0,45,55, 50,0,55,55,
        50,0,70,20, 75,75,40,25, 100,100,100,95, 100,100,100,100};
  /** LayerMulti color map */
  public static final byte[] CM_MULTI = {0,0,75,0, 17,0,90,90, 34,0,0,90,
        49,90,0,90, 66,90,0,0, 83,90,90,0, 100,95,95,95};
  /** X-Midas ramp color map */
  public static final byte[] CM_XRAMP = {0,0,0,15, 10,0,0,50, 31,0,65,75,
        50,0,85,0, 70,75,80,0, 83,100,60,0, 100,100,0,0};
  /** LayerMulti color map */
  public static final byte[] CM_SPECTRUM = {0,0,75,0, 22,0,90,90, 37,0,0,85,
        49,90,0,85, 68,90,0,0, 80,90,90,0, 100,95,95,95};

  /** The color (white) to use if a named color is not found or if a color is <code>null</code>. */
  static final Color DEFAULT_COLOR = Color.white;

  /** The MColor.toString(String) method */
  static final Method MCOLOR_TOSTRING = ReflectUtil.getMethodOfClass("nxm.sys.libg.MColor", "toString", Color.class);

  /** Opaque color in hex format */
  @InternalUseOnly
  public static final int OPAQUE = 0xFF;

  /** Get a single color.  This is a convenience wrapper for the Java constructor
    which has component ranges from 0 to 1.0 instead of 0-100.
    @param r Red component: 0-100
    @param g Green component: 0-100
    @param b Blue component: 0-100
    @return A Java color object
  */
  public static Color getColor (int r, int g, int b) {
    Color color = new Color ( r*0.01f, g*0.01f, b*0.01f);
    return color;
  }

  /** Get a single color by name or number string. This checks to see if the object
      passed in is an instance of {@link Color}, if so it just returns that object,
      otherwise it converts the object to a string and calls {@link #getLegacyColor(String)}.
      @param obj The name/value of the color.
      @return a Java Color object (this never returns null, if no match it returns {@link #DEFAULT_COLOR}.)
      @since NeXtMidas 2.3.1
  */
  public static Color getColor (Object obj) {
    return getColor(obj,DEFAULT_COLOR);
  }

  /** Get a single color by name or number string. This checks to see if the object
   *  passed in is an instance of {@link Color}, if so it just returns that object,
   *  otherwise it converts the object to a string and calls {@link #getLegacyColor(String)}.
   *
   *  @param obj The name/value of the color.
   *  @param defaultColor The default Color if conversion is otherwise not successful
   *  @return a Java Color object (this never returns null, if no match it returns defaultColor
   *         or {@link #DEFAULT_COLOR} if defaultColor is null too.)
   *  @since NeXtMidas 4.0.0 from original getColor
   */
  @InternalUseOnly
  public static Color getColor (Object obj, Color defaultColor) {
    if (obj instanceof Color)  return (Color)obj;

    // Check to see if they gave us something that has already been converted to
    // a number. If so, use the "old" conversion to color.
    if (obj instanceof Data) {
      Data data = (Data)obj;
      if (!data.isString()) {
        int val = Convert.o2l(data);
        return new Color(val, (val & 0xFF000000) != 0 );
      }
    }
    if (obj instanceof Number) {
      int val = ((Number)obj).intValue();
      return new Color(val, (val & 0xFF000000) != 0 );
    }

   // Normal conversions.
    Color color = null;
    String str = (obj == null)? null : obj.toString();

    //if (color == null) { color = getColorFromJavaString(str); }  // might be useful if passing String form of Java color
    if (color == null) { color = MidasColor.getLegacyColor(str); }
    if (color == null) { color = NamedColors.getColor(str); }
    // not used: if (color == null) { color = getIntColor(str); }
    Color colorForDefault = (defaultColor!=null) ? defaultColor : DEFAULT_COLOR; // since 4.0.0
    if (color == null) { color = Color.getColor(str, colorForDefault); }

    return color;
  }

  /** Get a single color by name or number string.  The recognized color
      names are the java.awt.Color names as well as the SVG standard
      colors: <br>
        TEAL, NAVY, OLIVE, DIMGRAY, BROWN, PURPLE<br>
        The color name may be prefixed with
        DARK - To increase the intensity by one unit <br>
        DEEP - To increase the intensity by three units <br>
        LIGHT - To decrease the intensity by one unit <br>
        BRIGHT - To decrease the intensity by three units <br>
        <br>
        To get a color by number the string must be prefixed with: <br>
        INDEX: - To get a color from the color map by index <br>
        # or 0X - To get a color by HEX representation (6-digit RGB or 8-digit ARGB) <br>
        <br>
        Since 2.7.0 this method supports alpha components on HEX values
     @param s the name or number String
     @return a Java Color object (null if s is null or legacy color not found)
  */
  @InternalUseOnly
  public static Color getLegacyColor (String s) {
    if (s == null) return null; // since 3.3.1 to prevent NPE below

    Color color; int intense=0;
    s = s.toUpperCase();

    if (s.startsWith("INDEX:")) {
      int index = (s.charAt(6) - '0');
      return getColorByIndex(index);
    }
    if (s.startsWith("#")) s = "0X" + s.substring(1);
    if (s.startsWith("0X")) {
      String alpha = (s.length() > 8)? s.substring(2, 4) : "";
      if ("FF".equals(alpha)) s = "0X"+s.substring(4); // strip off the FF if opaque
      int value = Convert.s2l(s);
      boolean hasAlpha = ("00".equals(alpha) || ((value & 0xFF000000)!= 0) );
      return new Color(value, hasAlpha);
    }
    else if (s.startsWith("X") || s.startsWith("FF")) {
      throw new IllegalArgumentException("Specifying color using hex values must start with '#' or '0X', given: "+s);
    }

    while (s.startsWith("DARK"))   { intense++;  s=s.substring(4); }
    while (s.startsWith("DEEP"))   { intense+=3; s=s.substring(4); }
    while (s.startsWith("LIGHT"))  { intense--;  s=s.substring(5); }
    while (s.startsWith("BRIGHT")) { intense-=3; s=s.substring(6); }

    if      (s.equals("BLACK")) color = Color.black;
    else if (s.equals("BLUE")) color = Color.blue;
    else if (s.equals("CYAN")) color = Color.cyan;
    else if (s.equals("GRAY")) color = Color.gray;
    else if (s.equals("GREEN")) color = Color.green;
    else if (s.equals("MAGENTA")) color = Color.magenta;
    else if (s.equals("ORANGE")) color = Color.orange;
    else if (s.equals("PINK")) color = Color.pink;
    else if (s.equals("RED")) color = Color.red;
    else if (s.equals("WHITE")) color = Color.white;
    else if (s.equals("YELLOW")) color = Color.yellow;
    // Colors match SVG Standard
    else if (s.equals("TEAL")) color = new Color(0,128,128);
    else if (s.equals("NAVY")) color = new Color(0,0,128);
    else if (s.equals("OLIVE")) color = new Color(128,128,0);
    else if (s.equals("DIMGRAY")) color = new Color(105,105,105);
    else if (s.equals("BROWN")) color = new Color(165,42,42);
    else if (s.equals("PURPLE")) color = new Color(128,0,128);
//  else color = Color.getColor(s,Color.white);
    else return null;

    while (intense>0) { intense--; color = color.darker(); }
    while (intense<0) { intense++; color = color.brighter(); }
    return color;
  }

  /** Get a single color from a indexed list
      JGS--are these standard colors?
      @param index the index number in the color list
      @return a Java Color object
  */
  public static Color getColorByIndex (int index) {
    Color color;
    switch (index) {
      case 1:  color = DARK_GREEN; break;
      case 2:  color = MODERATE_BLUE; break;
      case 3:  color = DARK_GOLDENROD; break;
      case 4:  color = PEARLY_PURPLE; break;
      case 5:  color = CYAN; break;
      case 6:  color = LIGHT_GREEN; break;
      case 7:  color = RED; break;
      default: color = DARK_RED; break;
    }
    return color;
  }

  /** Make an even blend between two colors. <br>
      Note: To preserve transparency, blending is NOT done on the Alpha component.
      @param color1     First color to blend.
      @param color2     Second color to blend.
      @return       Blended color.
      @since        NeXtMidas 2.7.1
   */
  public static Color blend (Color color1, Color color2) {
    return MidasColor.blend (color1, color2, 0.5);
  }

  /** Blend two colors.<br>
      Note: To preserve transparency, blending is NOT done on the Alpha component
      @param color1  First color to blend.
      @param color2  Second color to blend.
      @param ratio   Blend ratio. 0.5 will give even blend, 1.0 will return
                     color1, 0.0 will return color2 and so on.
      @return        Blended color.
      @since         NeXtMidas 2.7.1
   */
  public static Color blend (Color color1, Color color2, double ratio) {
    float r  = (float) ratio;
    float ir = (float) 1.0 - r;

    float rgb1[] = new float[4];
    float rgb2[] = new float[4];

    color1.getComponents(rgb1);
    color2.getComponents(rgb2);

    Color color = new Color (rgb1[0] * r + rgb2[0] * ir,
                             rgb1[1] * r + rgb2[1] * ir,
                             rgb1[2] * r + rgb2[2] * ir,
                             rgb1[3]);

    return color;
  }

  /** This is not for public use outside the NeXtMidas framework.
      This method captures 'special'  color numbers that can be in 1998, 1999 or 5000 files.
      X-Midas XGEOPLOT keys off these special numbers to adds 5 before calling mx$draw_lines  A DR
      (g765978-1) was written in Jan 2008 to document all special XGEOPLOT color numbers. This does
      not yet do all the special handling documented in this DR to include the COLORMAP=RAW
      COLORMAP=USER and foreground and background colors where I have chosen white or black.

      See docs in XM 4.5.5. or later.
      @param colNum color number
      @return the corresponding Color
  */
  @InternalUseOnly
  public static java.awt.Color getSpecialXMidasColor (int colNum) {
    java.awt.Color color = java.awt.Color.white;
    if (colNum < -6 ) {
      color = Color.white;  // X-Midas says choose foreground color
     }
    else {
      switch (colNum) {
        case -6:
          color = Color.white;
          break;
        case -5:
          color = Color.black;
          break;
        case -4:
         color = Color.gray;
          break;
        case -3:
          color = Color.green;
          break;
        case -2:
          color = Color.red;
          break;
        case -1:
         color = Color.yellow;
          break;
        case 0:
          color = new Color(128,0,128); // purple
          break;
        default:
          color = Color.white;
         break;
      }
    }
    return color;
  }

  /** Inverts the given Color. The alpha (transparency) component will remain the same.
      Inverting creates a photo negative effect. Doing this twice on a Color will
      return it back to normal.
      @param inColor Color to be inverted. null returns null.
      @return new Color representing the inverted color of the input color
      @since NeXtMidas 2.9.0
   */
  public static Color invert (Color inColor) {
    if (inColor==null) return null;
    int argb = inColor.getRGB();
    byte alpha = (byte) ((argb >> 24) & 0xFF);
    byte red   = (byte) ((argb >> 16) & 0xff);
    byte green = (byte) ((argb >>  8) & 0xff);
    byte blue  = (byte) ((argb >>  0) & 0xff);

    // invert the RGB color (keeping the alpha component the same)
    int invertedRGB = (alpha << 24) | ((255-red) << 16) | ((255-green) << 8) | ((255-blue) << 0);

    if (alpha == 0xff) { // solid (opaque) color
      return new Color(invertedRGB);
    } else { // have alpha component (semi-transparent color)
      return new Color(invertedRGB, true);
    }
  }

  /** Get a mixed color. This is frequently used when shading.
    @param c0  The first Java Color object to be mixed.
    @param c1  The second Java Color object to be mixed.
    @param mix The percentage of c0 to use (<code>1.00-mix</code> is the percentage used for c1).
               The acceptable range of mix is <code>0.00 - 1.00</code>.  If the value is outside this
               range it is clipped.
    @return A color between c0 and c1.
  */
  public static Color getColorMix (Color c0, Color c1, double mix) {
         if (mix > 1) mix=1;
    else if (mix < 0) mix=0;

    double mixi = 1.0-mix;
    int r = (int)(c0.getRed()*mixi   + c1.getRed()*mix);
    int g = (int)(c0.getGreen()*mixi + c1.getGreen()*mix);
    int b = (int)(c0.getBlue()*mixi  + c1.getBlue()*mix);
    return new Color (r,g,b);
  }

  /** Converts a color to it's string form.
      If the color matches a named color that name will be returned.
      otherwise the Hex form of the color will be returned. If the color is
      OPAQUE, the hex form will be six digits, otherwise it will be eight.
      <br>
      Examples (color arg shown in hex):
      <pre>
        toString(ff888888)   --&gt; "GREY"
        toString(ff123456)   --&gt; "#123456"     (OPAQUE      - no alpha)
        toString(7f123456)   --&gt; "#7f123456"   (Semi-OPAQUE - has alpha)
      </pre>
      <br>

      @param color The color.
      @return The string form of the color (or <code>"null"</code> if the color is null).
 */
  public static String toString (Color color) {
    // need to call MColor.toString if available
    if (MCOLOR_TOSTRING != null) {
      Optional<Object> result = ReflectUtil.callMethod(null, MCOLOR_TOSTRING, new Object[] {color});
      if (result.isPresent()) {
        return (String)result.get();
      }
    }
    return color.toString();
  }

  /** Simple formula for determining a difference between colors.
      Primarily used to determine how close colors are visually. This is based on the CIE76
      formula since it easy to implement and sufficient to represent the color difference.
      @see <a href="http://en.wikipedia.org/wiki/Color_difference">Color Difference</a>
      @param color1 First color to compare (cannot be null).
      @param color2 Second color to compare (cannot be null).
      @return double value (normalized) between 0.0 (comparing same color) and 1.0 (i.e. comparing black with white)
      @since  3.5.0
   */
  public static double colorDifference (Color color1, Color color2) {
    double retVal;

    int deltaRed = color1.getRed() - color2.getRed();
    int deltaRedSquared = deltaRed * deltaRed;

    int deltaGreen = color1.getGreen() - color2.getGreen();
    int deltaGreenSquared = deltaGreen * deltaGreen;

    int deltaB = color1.getBlue() - color2.getBlue();
    int deltaBlueSquared = deltaB * deltaB;

    retVal = Math.sqrt(deltaRedSquared + deltaGreenSquared + deltaBlueSquared);

    return retVal / Math.sqrt(255 * 255 * 3); // normalize return value to double in range of [0.0, 1.0]
  }

  /****************************************************************************/
  /* NAMED-COLOR DEFINITION CLASS                                             */
  /****************************************************************************/

  /** This class holds the standard definitions for named colors. <br>
      <br>
      The following color sets are available:
      <pre>
        Netscape 7.1 Colors - The 133 SVG colors recognized by Netscape.
        SVG Standard Colors - All 147 SVG colors (some of which are duplicates).
      </pre>

      The list of Netscape colors was obtained from a chart published at
      <a href="http://mindprod.com/jgloss/netscapecolours.html">
      http://mindprod.com/jgloss/netscapecolours.html</a>. The additional
      14 SVG colors were manually added from various sources.
    @author J. Czechowski IV
    @since  NeXtMidas 1.9.2
  */
  public static class NamedColors {

    /** This constructor was automatically provided by Java,
     *  but it is unneeded as all the methods are static methods.
     *  @deprecated now that Javadocs are required, it is temporarily
     *              being explicitely provided but deprecated
     */
    @Deprecated
    public NamedColors () {}

    /** Gets the color for the given color name from the most inclusive list
        of color names. <br>
        <br>
        Currently this is the same as using {@link #getSvgColor(String)} but
        may change in the future if more colors are added. <br>
        <br>
        Note that the named colors are case- and whitespace-insensitive (e.g.
        "Slate Blue", "slateblue", and "SlateBlue" all represent the same
        color).
      @param name The name of the color.
      @return The color associated with the given name or <code>null</code>
              if no color matches.
    */
    public static Color getColor(String name) {
      return getSvgColor(name);
    }

    /** Gets the color name for the given color from the most inclusive list
        of color names. <br>
        <br>
        Currently this is the same as using {@link #getSvgColorName(Color)} but
        may change in the future if more colors are added.
      @param color The color.
      @return The color name associated with the given color or <code>null</code>
              if no match is found.
    */
    public static String getColorName(Color color) {
      return getSvgColorName(color);
    }

    /** Gets the color for the given SVG color name. <br>
        <br>
        This method is case insensitive and ignores any white space.
      @param name The name of the color.
      @return The color associated with the given name or <code>null</code>
                if no color matches.
    */
    public static Color getSvgColor(String name) {
      Color color = getNetscapeColor(name);

      if (color == null) {
        color = getColor(name, SVG_COLOR_NAMES);
      }

      return color;
    }

    /** Gets the color name for the given SVG color.
      @param color The color.
      @return The color name associated with the given color or <code>null</code>
              if no match is found.
    */
    public static String getSvgColorName(Color color) {
      String name = getColorName(color, NS_COLOR_NAMES);

      if (name == null) {
        name = getColorName(color, SVG_COLOR_NAMES);
      }

      return name;
    }

    /** Gets the color for the given Netscape color name. <br>
        <br>
        This method is case insensitive and ignores any white space.
      @param name The name of the color.
      @return The color associated with the given name or <code>null</code>
              if no color matches.
    */
    public static Color getNetscapeColor(String name) {
      return getColor(name, NS_COLOR_NAMES);
    }

    /** Gets the color name for the given Netscape color.
      @param color The color.
      @return The color name associated with the given color or <code>null</code>
                if no match is found.
    */
    public static String getNetscapeColorName(Color color) {
      return getColorName(color, NS_COLOR_NAMES);
    }

    /** Gets the color for the given color name. <br>
        <br>
        This method is case insensitive and ignores any white space.
      @param name     The name of the color.
      @param colorSet The set of colors to use.
      @return The color associated with the given name or <code>null</code>
                if no color matches.
    */
    protected static Color getColor(String name, String[] colorSet) {
      if (name == null) {
        return null;
      }

      // Remove all spaces and make the name lower case.
      //   JC4: Note that WHITESPACE_REGEX.matcher(str).replaceAll("") is much faster than using
      //        name.replaceAll("\\s","") because the later one recompiles the regex every time.
      name = name.toLowerCase();
      name = WHITESPACE_REGEX.matcher(name).replaceAll("");

      for (int i = 1; i < colorSet.length; i+=2) {
        if (name.equals(colorSet[i])) {
          try {
            final int rgb = Integer.parseInt(colorSet[i-1], 16);

            return new Color(rgb);

          } catch(NumberFormatException e) {
            /* This should never happen. */
            throw new RuntimeException("Unable to initialize color: " + e);
          }
        }
      }

      return null; /* Name Not Found */
    }

    /** Gets the color name for the given color.
      @param color    The color.
      @param colorSet The set of colors to use.
      @return The color name associated with the given color or <code>null</code>
              if no match is found.
    */
    protected static String getColorName(Color color, String[] colorSet) {
      if (color == null) {
        return null;
      }

      final int colorRGB = color.getRGB(); /* The RGB form of the color searching for. */

      try {
        for (int i = 0; i < colorSet.length; i+=2) {
          int rgb = Integer.parseInt(colorSet[i], 16) + ((colorSet[i].length()<=6)? 0xFF000000: 0);

          if (colorRGB == rgb) {
            return colorSet[i+1];
          }
        }
      } catch(NumberFormatException e) {
        /* This should never happen. */
        throw new RuntimeException("Unable to initialize color: " + e);
      }

      return null; /* Name Not Found */
    }

    /** The 14 SVG color names NOT recognized by Netscape 7.1. */
    private static final String[] SVG_COLOR_NAMES = new String[] {
      "00ffff", "cyan",                 "a9a9a9", "darkgrey",
      "2f4f4f", "darkslategrey",        "696969", "dimgrey",
      "ffd700", "gold",                 "808080", "grey",
      "d3d3d3", "lightgrey",            "778899", "lightslategrey",
      "cd853f", "peru",                 "ffc0cb", "pink",
      "dda0dd", "plum",                 "708090", "slategrey",
      "fffafa", "snow",                 "d2b48c", "tan" };

    /** The 133 color names recognized by Netscape 7.1. */
    private static final String[] NS_COLOR_NAMES = new String[] {
      "f0f8ff", "aliceblue",            "faebd7", "antiquewhite",
      "00ffff", "aqua",                 "7fffd4", "aquamarine",
      "f0ffff", "azure",                "f5f5dc", "beige",
      "ffe4c4", "bisque",               "000000", "black",
      "ffebcd", "blanchedalmond",       "0000ff", "blue",
      "8a2be2", "blueviolet",           "a52a2a", "brown",
      "deb887", "burlywood",            "5f9ea0", "cadetblue",
      "7fff00", "chartreuse",           "d2691e", "chocolate",
      "ff7f50", "coral",                "6495ed", "cornflowerblue",
      "fff8dc", "cornsilk",             "dc143c", "crimson",
      "00008b", "darkblue",             "008b8b", "darkcyan",
      "b8860b", "darkgoldenrod",        "a9a9a9", "darkgray",
      "006400", "darkgreen",            "bdb76b", "darkkhaki",
      "8b008b", "darkmagenta",          "556b2f", "darkolivegreen",
      "ff8c00", "darkorange",           "9932cc", "darkorchid",
      "8b0000", "darkred",              "e9967a", "darksalmon",
      "8fbc8f", "darkseagreen",         "483d8b", "darkslateblue",
      "2f4f4f", "darkslategray",        "00ced1", "darkturquoise",
      "9400d3", "darkviolet",           "ff1493", "deeppink",
      "00bfff", "deepskyblue",          "696969", "dimgray",
      "1e90ff", "dodgerblue",           "b22222", "firebrick",
      "fffaf0", "floralwhite",          "228b22", "forestgreen",
      "ff00ff", "fuchsia",              "dcdcdc", "gainsboro",
      "f8f8ff", "ghostwhite",           "daa520", "goldenrod",
      "808080", "gray",                 "008000", "green",
      "adff2f", "greenyellow",          "f0fff0", "honeydew",
      "ff69b4", "hotpink",              "cd5c5c", "indianred",
      "4b0082", "indigo",               "fffff0", "ivory",
      "f0e68c", "khaki",                "e6e6fa", "lavender",
      "fff0f5", "lavenderblush",        "7cfc00", "lawngreen",
      "fffacd", "lemonchiffon",         "add8e6", "lightblue",
      "f08080", "lightcoral",           "e0ffff", "lightcyan",
      "fafad2", "lightgoldenrodyellow", "90ee90", "lightgreen",
      "d3d3d3", "lightgrey",            "ffb6c1", "lightpink",
      "ffa07a", "lightsalmon",          "20b2aa", "lightseagreen",
      "87cefa", "lightskyblue",         "778899", "lightslategray",
      "b0c4de", "lightsteelblue",       "ffffe0", "lightyellow",
      "00ff00", "lime",                 "32cd32", "limegreen",
      "faf0e6", "linen",                "ff00ff", "magenta",
      "800000", "maroon",               "66cdaa", "mediumaquamarine",
      "0000cd", "mediumblue",           "ba55d3", "mediumorchid",
      "9370db", "mediumpurple",         "3cb371", "mediumseagreen",
      "7b68ee", "mediumslateblue",      "00fa9a", "mediumspringgreen",
      "48d1cc", "mediumturquoise",      "c71585", "mediumvioletred",
      "191970", "midnightblue",         "f5fffa", "mintcream",
      "ffe4e1", "mistyrose",            "ffe4b5", "moccasin",
      "ffdead", "navajowhite",          "000080", "navy",
      "fdf5e6", "oldlace",              "808000", "olive",
      "6b8e23", "olivedrab",            "ffa500", "orange",
      "ff4500", "orangered",            "da70d6", "orchid",
      "eee8aa", "palegoldenrod",        "98fb98", "palegreen",
      "afeeee", "paleturquoise",        "db7093", "palevioletred",
      "ffefd5", "papayawhip",           "ffdab9", "peachpuff",
      "b0e0e6", "powderblue",           "800080", "purple",
      "ff0000", "red",                  "bc8f8f", "rosybrown",
      "4169e1", "royalblue",            "8b4513", "saddlebrown",
      "fa8072", "salmon",               "f4a460", "sandybrown",
      "2e8b57", "seagreen",             "fff5ee", "seashell",
      "a0522d", "sienna",               "c0c0c0", "silver",
      "87ceeb", "skyblue",              "6a5acd", "slateblue",
      "708090", "slategray",            "00ff7f", "springgreen",
      "4682b4", "steelblue",            "008080", "teal",
      "d8bfd8", "thistle",              "ff6347", "tomato",
      "40e0d0", "turquoise",            "ee82ee", "violet",
      "f5deb3", "wheat",                "ffffff", "white",
      "f5f5f5", "whitesmoke",           "ffff00", "yellow",
      "9acd32", "yellowgreen" };
  } // end static class NamedColors

}
