/*
 * Copyright 2004-2005 The Trix Development Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.trix.cuery.value;

import java.util.HashMap;
import java.util.Map;

import org.w3c.css.sac.CSSException;
import org.w3c.dom.DOMException;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.w3c.dom.css.RGBColor;

/**
 * DOCUMENT.
 * 
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: CSSColor.java,v 1.1 2005/08/09 19:54:14 Teletha Exp $
 */
public class CSSColor extends AbstractValue implements RGBColor {

    /** The defined color mapping. */
    private static final Map<String, int[]> DEFINED_COLOR = new HashMap<String, int[]>(145);

    /** The actual expression. */
    protected String actual;

    /** The red value. */
    protected int red = 0;

    /** The green value. */
    protected int green = 0;

    /** The blue value. */
    protected int blue = 0;

    /**
     * Create CSSColor instance from RGB format.
     * 
     * @param red A red value.
     * @param green A green value.
     * @param blue A blue value.
     * @param previous A previous css value.
     * @throws CSSException If this RGB values hava a invalid format.
     */
    public CSSColor(int red, int green, int blue, Value previous) throws CSSException {
        super(previous);

        this.red = correctRange(red);
        this.green = correctRange(green);
        this.blue = correctRange(blue);
        this.actual = "rgb(" + red + ", " + green + ", " + blue + ")";
    }

    /**
     * Create CSSColor instance from RGB format.
     * 
     * @param red A red value.
     * @param green A green value.
     * @param blue A blue value.
     * @param previous A previous css value.
     * @throws CSSException If this RGB values hava a invalid format.
     */
    public CSSColor(String red, String green, String blue, Value previous) throws CSSException {
        super(previous);

        this.red = parseRGB(red);
        this.green = parseRGB(green);
        this.blue = parseRGB(blue);
        this.actual = "rgb(" + red + ", " + green + ", " + blue + ")";
    }

    /**
     * Create CSSColor instance from Hex format or color name.
     * 
     * @param value A Hex color value or color name.
     * @param previous A previous css value.
     * @throws CSSException If this Hex values hava a invalid format.
     */
    public CSSColor(String value, Value previous) throws CSSException {
        super(previous);

        // check null
        if (value == null || value.length() == 0) {
            throw new IllegalStateException("This HEX color value is null.");
        }

        // cehck hash
        if (value.charAt(0) == '#') {
            value = value.substring(1);

            // check length
            if (value.length() != 3 && value.length() != 6) {
                throw new IllegalStateException("This is a invalid HEX color value. '" + value + "'");
            }

            // parse
            if (value.length() == 3) {
                this.red = parseHex(value.substring(0, 1));
                this.green = parseHex(value.substring(1, 2));
                this.blue = parseHex(value.substring(2, 3));
            } else {
                this.red = parseHex(value.substring(0, 2));
                this.green = parseHex(value.substring(2, 4));
                this.blue = parseHex(value.substring(4, 6));
            }
            this.actual = "#" + value;
        } else {
            int[] rgb = DEFINED_COLOR.get(value);

            if (rgb == null) {
                throw new IllegalStateException("This is a invalid color name. '" + value + "'");
            }

            this.red = rgb[0];
            this.green = rgb[1];
            this.blue = rgb[2];
            this.actual = value;
        }
    }

    /**
     * @see org.trix.cuery.value.AbstractCSSValue#getFunctionName()
     */
    public String getFunctionName() {
        return "rgb";
    }

    /**
     * @see org.trix.cuery.value.AbstractCSSValue#getLexicalUnitType()
     */
    public short getLexicalUnitType() {
        return SAC_RGBCOLOR;
    }

    /**
     * @see org.w3c.css.sac.LexicalUnit#getFloatValue()
     */
    public float getFloatValue() {
        throw new UnsupportedOperationException(getClass().getSimpleName() + " can't execute this method.");
    }

    /**
     * @see org.trix.cuery.value.AbstractCSSValue#getStringValue()
     */
    public String getStringValue() {
        return getHexValue();
    }

    /**
     * @see org.w3c.dom.css.CSSPrimitiveValue#getPrimitiveType()
     */
    public short getPrimitiveType() {
        return CSS_RGBCOLOR;
    }

    /**
     * @see org.w3c.dom.css.RGBColor#getBlue()
     */
    public CSSPrimitiveValue getBlue() {
        return new CSSNumber(blue, null);
    }

    /**
     * @see org.w3c.dom.css.RGBColor#getGreen()
     */
    public CSSPrimitiveValue getGreen() {
        return new CSSNumber(green, null);
    }

    /**
     * @see org.w3c.dom.css.RGBColor#getRed()
     */
    public CSSPrimitiveValue getRed() {
        return new CSSNumber(red, null);
    }

    /**
     * @see org.trix.cuery.value.AbstractValue#getCssText()
     */
    @Override
    public String getCssText() {
        return actual;
    }

    /**
     * @see org.trix.cuery.value.AbstractValue#getRGBColorValue()
     */
    @Override
    public RGBColor getRGBColorValue() throws DOMException {
        return this;
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object object) {
        if (!(object instanceof CSSColor)) {
            return false;
        }

        CSSColor another = (CSSColor) object;
        return red == another.red && green == another.green && blue == another.blue;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return Integer.valueOf(red).hashCode() + Integer.valueOf(green).hashCode() + Integer.valueOf(blue).hashCode();
    }

    /**
     * Get a RGB value of this color.
     * 
     * @return A RGB format value.
     */
    public String getRGBValue() {
        return "rgb(" + red + ", " + green + ", " + blue + ")";
    }

    /**
     * Get a Hex value of this color.
     * 
     * @return A Hex format value.
     */
    public String getHexValue() {
        return "#" + convertIntToHex(red) + convertIntToHex(green) + convertIntToHex(blue);
    }

    /**
     * Return integer red value.
     * 
     * @return A red value.
     */
    public int getRedAsInteger() {
        return red;
    }

    /**
     * Return integer green value.
     * 
     * @return A green value.
     */
    public int getGreenAsInteger() {
        return green;
    }

    /**
     * Return integer blue value.
     * 
     * @return A blue value.
     */
    public int getBlueAsInteger() {
        return blue;
    }

    /**
     * Helper method to convert int to hex.
     * 
     * @param value A int value.
     * @return A hex value.
     */
    private String convertIntToHex(int value) {
        String hex = Integer.toHexString(value);

        if (hex.length() == 1) {
            hex = "0" + hex;
        }
        return hex;
    }

    /**
     * Parse a value of RGB and convert to int value.
     * 
     * @param value A one of a color valeu of RGB.
     * @return A int value.
     * @throws IllegalStateException If this value is invalid.
     */
    private int parseRGB(String value) throws IllegalStateException {
        // cehck null
        if (value == null || value.length() == 0) {
            throw new IllegalStateException("This value is null.");
        }

        try {
            int color = -1;

            // parse
            if (value.charAt(value.length() - 1) == '%') {
                color = (int) (Float.parseFloat(value.substring(0, value.length() - 1)) / 20 * 51);
            } else if (value.charAt(0) == '+') {
                color = Integer.parseInt(value.substring(1));
            } else {
                color = Integer.parseInt(value);
            }

            return correctRange(color);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("This value is invalid format.", e);
        }
    }

    /**
     * Parse a value of Hex and convert to int value.
     * 
     * @param value A one of a color valeu of Hex.
     * @return A int value.
     * @throws IllegalStateException If this value is invalid.
     */
    private int parseHex(String value) throws IllegalStateException {
        // cehck null
        if (value == null || !(value.length() == 1 || value.length() == 2)) {
            throw new IllegalStateException("This value is null or invalid format.");
        }

        try {
            // parse
            if (value.length() == 1) {
                value = value + value;
            }

            int color = Integer.parseInt(value, 16);
            return correctRange(color);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("This value is invalid format.", e);
        }
    }

    /**
     * Check the range of color value.
     * 
     * @param color A target value.
     * @return A fixed value.
     */
    private int correctRange(int color) {
        // check range
        if (color < 0) {
            return 0;
        }

        if (255 < color) {
            return 255;
        }
        return color;
    }

    /**
     * Check whether this name is the defined color name or not.
     * 
     * @param name A colo name.
     * @return A result
     */
    public static final boolean isDefinedColor(String name) {
        // check null
        if (name == null || name.length() == 0) {
            return false;
        }
        return DEFINED_COLOR.containsKey(name);
    }

    // color initilization
    static {
        DEFINED_COLOR.put("aliceblue", new int[] {240, 248, 255});
        DEFINED_COLOR.put("antiquewhite", new int[] {250, 235, 215});
        DEFINED_COLOR.put("aqua", new int[] {0, 255, 255});
        DEFINED_COLOR.put("aquamarine", new int[] {127, 255, 212});
        DEFINED_COLOR.put("azure", new int[] {240, 255, 255});
        DEFINED_COLOR.put("beige", new int[] {245, 245, 220});
        DEFINED_COLOR.put("bisque", new int[] {255, 228, 196});
        DEFINED_COLOR.put("black", new int[] {0, 0, 0});
        DEFINED_COLOR.put("blanchedalmond", new int[] {255, 235, 205});
        DEFINED_COLOR.put("blue", new int[] {0, 0, 255});
        DEFINED_COLOR.put("blueviolet", new int[] {138, 43, 226});
        DEFINED_COLOR.put("brown", new int[] {165, 42, 42});
        DEFINED_COLOR.put("burlywood", new int[] {222, 184, 135});
        DEFINED_COLOR.put("cadetBlue", new int[] {95, 158, 160});
        DEFINED_COLOR.put("chartreuse", new int[] {127, 255, 0});
        DEFINED_COLOR.put("chocolate", new int[] {210, 105, 30});
        DEFINED_COLOR.put("coral", new int[] {255, 127, 80});
        DEFINED_COLOR.put("cornflowerblue", new int[] {100, 149, 237});
        DEFINED_COLOR.put("cornsilk", new int[] {255, 248, 220});
        DEFINED_COLOR.put("crimson", new int[] {220, 20, 60});
        DEFINED_COLOR.put("cyan", new int[] {0, 255, 255});
        DEFINED_COLOR.put("darkblue", new int[] {0, 0, 139});
        DEFINED_COLOR.put("darkcyan", new int[] {0, 139, 139});
        DEFINED_COLOR.put("darkgoldenrod", new int[] {184, 134, 11});
        DEFINED_COLOR.put("darkgray", new int[] {169, 169, 169});
        DEFINED_COLOR.put("darkgreen", new int[] {0, 100, 0});
        DEFINED_COLOR.put("darkkhaki", new int[] {189, 183, 107});
        DEFINED_COLOR.put("darkmagenta", new int[] {139, 0, 139});
        DEFINED_COLOR.put("darkolivegreen", new int[] {85, 107, 47});
        DEFINED_COLOR.put("darkorange", new int[] {255, 140, 0});
        DEFINED_COLOR.put("darkorchid", new int[] {153, 50, 204});
        DEFINED_COLOR.put("darkred", new int[] {139, 0, 0});
        DEFINED_COLOR.put("darksalmon", new int[] {233, 150, 122});
        DEFINED_COLOR.put("darkseagreen", new int[] {143, 188, 143});
        DEFINED_COLOR.put("darkslateblue", new int[] {72, 61, 139});
        DEFINED_COLOR.put("darkslategray", new int[] {47, 79, 79});
        DEFINED_COLOR.put("darkslategrey", new int[] {47, 79, 79});
        DEFINED_COLOR.put("darkturquoise", new int[] {0, 206, 209});
        DEFINED_COLOR.put("darkviolet", new int[] {148, 0, 211});
        DEFINED_COLOR.put("deeppink", new int[] {255, 20, 147});
        DEFINED_COLOR.put("deepskyblue", new int[] {0, 191, 255});
        DEFINED_COLOR.put("dimgray", new int[] {105, 105, 105});
        DEFINED_COLOR.put("dimgrey", new int[] {105, 105, 105});
        DEFINED_COLOR.put("dodgerblue", new int[] {30, 144, 255});
        DEFINED_COLOR.put("firebrick", new int[] {178, 34, 34});
        DEFINED_COLOR.put("floralwhite", new int[] {255, 250, 240});
        DEFINED_COLOR.put("forestgreen", new int[] {34, 139, 34});
        DEFINED_COLOR.put("fuchsia", new int[] {255, 0, 255});
        DEFINED_COLOR.put("gainsboro", new int[] {220, 220, 220});
        DEFINED_COLOR.put("ghostwhite", new int[] {248, 248, 255});
        DEFINED_COLOR.put("gold", new int[] {255, 215, 0});
        DEFINED_COLOR.put("goldenrod", new int[] {218, 265, 32});
        DEFINED_COLOR.put("gray", new int[] {128, 128, 128});
        DEFINED_COLOR.put("green", new int[] {0, 128, 0});
        DEFINED_COLOR.put("greenyellow", new int[] {173, 255, 47});
        DEFINED_COLOR.put("grey", new int[] {128, 128, 128});
        DEFINED_COLOR.put("honeydew", new int[] {240, 255, 240});
        DEFINED_COLOR.put("hotpink", new int[] {255, 105, 180});
        DEFINED_COLOR.put("indianred", new int[] {205, 92, 92});
        DEFINED_COLOR.put("indigo", new int[] {75, 0, 130});
        DEFINED_COLOR.put("ivory", new int[] {255, 255, 240});
        DEFINED_COLOR.put("khaki", new int[] {240, 230, 140});
        DEFINED_COLOR.put("lavender", new int[] {230, 230, 250});
        DEFINED_COLOR.put("lavenderblush", new int[] {255, 240, 245});
        DEFINED_COLOR.put("lawngreen", new int[] {124, 242, 0});
        DEFINED_COLOR.put("lemonchiffon", new int[] {255, 250, 205});
        DEFINED_COLOR.put("lightblue", new int[] {173, 213, 230});
        DEFINED_COLOR.put("lightcoral", new int[] {240, 128, 128});
        DEFINED_COLOR.put("lightcyan", new int[] {224, 255, 255});
        DEFINED_COLOR.put("lightgoldenrodyellow", new int[] {250, 250, 210});
        DEFINED_COLOR.put("lightgreen", new int[] {144, 238, 144});
        DEFINED_COLOR.put("lightgray", new int[] {211, 211, 211});
        DEFINED_COLOR.put("lightgrey", new int[] {211, 211, 211});
        DEFINED_COLOR.put("lightsalmon", new int[] {255, 160, 122});
        DEFINED_COLOR.put("lightseagreen", new int[] {32, 178, 170});
        DEFINED_COLOR.put("lightskyblue", new int[] {135, 206, 250});
        DEFINED_COLOR.put("lightslategray", new int[] {119, 136, 153});
        DEFINED_COLOR.put("lightslategrey", new int[] {119, 136, 153});
        DEFINED_COLOR.put("lightsteelblue", new int[] {172, 196, 222});
        DEFINED_COLOR.put("lightyellow", new int[] {255, 255, 224});
        DEFINED_COLOR.put("lime", new int[] {0, 255, 0});
        DEFINED_COLOR.put("limegreen", new int[] {50, 205, 50});
        DEFINED_COLOR.put("linen", new int[] {250, 240, 230});
        DEFINED_COLOR.put("magenta", new int[] {255, 0, 255});
        DEFINED_COLOR.put("maroon", new int[] {128, 0, 0});
        DEFINED_COLOR.put("mediumaquamarine", new int[] {102, 205, 170});
        DEFINED_COLOR.put("mediumblue", new int[] {0, 0, 205});
        DEFINED_COLOR.put("mediumorchid", new int[] {186, 85, 211});
        DEFINED_COLOR.put("mediumpurple", new int[] {147, 112, 219});
        DEFINED_COLOR.put("mediumseagreen", new int[] {60, 179, 113});
        DEFINED_COLOR.put("mediumslateblue", new int[] {123, 104, 238});
        DEFINED_COLOR.put("mediumspringgreen", new int[] {0, 250, 154});
        DEFINED_COLOR.put("mediumturquoise", new int[] {72, 209, 204});
        DEFINED_COLOR.put("mediumvioletred", new int[] {199, 21, 133});
        DEFINED_COLOR.put("midnightblue", new int[] {25, 25, 112});
        DEFINED_COLOR.put("mintcream", new int[] {245, 255, 250});
        DEFINED_COLOR.put("mistyrose", new int[] {255, 228, 225});
        DEFINED_COLOR.put("moccasin", new int[] {255, 228, 181});
        DEFINED_COLOR.put("navajowhite", new int[] {255, 222, 173});
        DEFINED_COLOR.put("navy", new int[] {0, 0, 128});
        DEFINED_COLOR.put("oldlace", new int[] {253, 245, 230});
        DEFINED_COLOR.put("olive", new int[] {128, 128, 0});
        DEFINED_COLOR.put("olivedrab", new int[] {107, 142, 35});
        DEFINED_COLOR.put("orange", new int[] {255, 165, 0});
        DEFINED_COLOR.put("orangered", new int[] {255, 69, 0});
        DEFINED_COLOR.put("orchid", new int[] {218, 112, 214});
        DEFINED_COLOR.put("palegoldenrod", new int[] {238, 232, 170});
        DEFINED_COLOR.put("palegreen", new int[] {152, 251, 152});
        DEFINED_COLOR.put("paleturquoise", new int[] {175, 238, 238});
        DEFINED_COLOR.put("palevioletred", new int[] {219, 112, 147});
        DEFINED_COLOR.put("papayawhip", new int[] {255, 239, 213});
        DEFINED_COLOR.put("peachpuff", new int[] {255, 218, 185});
        DEFINED_COLOR.put("peru", new int[] {205, 133, 63});
        DEFINED_COLOR.put("pink", new int[] {255, 192, 203});
        DEFINED_COLOR.put("plum", new int[] {221, 160, 221});
        DEFINED_COLOR.put("powderBlue", new int[] {176, 224, 230});
        DEFINED_COLOR.put("purple", new int[] {128, 0, 128});
        DEFINED_COLOR.put("red", new int[] {255, 0, 0});
        DEFINED_COLOR.put("rosybrown", new int[] {188, 143, 143});
        DEFINED_COLOR.put("royalblue", new int[] {65, 105, 225});
        DEFINED_COLOR.put("saddlebrown", new int[] {139, 69, 19});
        DEFINED_COLOR.put("salmon", new int[] {250, 128, 114});
        DEFINED_COLOR.put("sandybrown", new int[] {244, 164, 96});
        DEFINED_COLOR.put("seagreen", new int[] {46, 139, 87});
        DEFINED_COLOR.put("seashell", new int[] {255, 245, 238});
        DEFINED_COLOR.put("sienna", new int[] {160, 82, 45});
        DEFINED_COLOR.put("silver", new int[] {192, 192, 192});
        DEFINED_COLOR.put("skyblue", new int[] {135, 206, 235});
        DEFINED_COLOR.put("slateblue", new int[] {106, 90, 205});
        DEFINED_COLOR.put("slategray", new int[] {112, 128, 144});
        DEFINED_COLOR.put("slategrey", new int[] {112, 128, 144});
        DEFINED_COLOR.put("snow", new int[] {255, 250, 250});
        DEFINED_COLOR.put("springgreen", new int[] {0, 255, 127});
        DEFINED_COLOR.put("steelblue", new int[] {70, 130, 180});
        DEFINED_COLOR.put("tan", new int[] {210, 180, 140});
        DEFINED_COLOR.put("teal", new int[] {0, 128, 128});
        DEFINED_COLOR.put("thistle", new int[] {216, 191, 216});
        DEFINED_COLOR.put("tomato", new int[] {255, 99, 71});
        DEFINED_COLOR.put("turquoise", new int[] {64, 224, 208});
        DEFINED_COLOR.put("violet", new int[] {238, 130, 238});
        DEFINED_COLOR.put("wheat", new int[] {245, 222, 179});
        DEFINED_COLOR.put("white", new int[] {255, 255, 255});
        DEFINED_COLOR.put("whitesmoke", new int[] {245, 245, 245});
        DEFINED_COLOR.put("yellow", new int[] {255, 255, 0});
        DEFINED_COLOR.put("yellowgreen", new int[] {154, 205, 50});
    }
}
