/************************************************************************
*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you 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.odftoolkit.odfdom.type;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * This class represents the in OpenDocument format used data type {@odf.datatype color}
 * See <a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-color">W3C CSS specification</a> for further details.
 */
public class Color implements OdfDataType {

	private static final Pattern sixHexRGBPattern = Pattern.compile("^#[0-9a-fA-F]{6}$");
	private static final Pattern threeHexRGBPattern = Pattern.compile("^#[0-9a-fA-F]{3}$");
	private static final Map<String, String> labeledColors = new HashMap<String, String>();
	static {
		labeledColors.put("aqua", "#00ffff");
		labeledColors.put("black", "#000000");
		labeledColors.put("blue", "#0000ff");
		labeledColors.put("fuchsia", "#ff00ff");
		labeledColors.put("gray", "#808080");
		labeledColors.put("green", "#008000");
		labeledColors.put("lime", "#00ff00");
		labeledColors.put("maroon", "#800000");
		labeledColors.put("navy", "#000080");
		labeledColors.put("olive", "#808000");
		labeledColors.put("orange", "#ffA500");
		labeledColors.put("purple", "#800080");
		labeledColors.put("red", "#ff0000");
		labeledColors.put("silver", "#c0c0c0");
		labeledColors.put("teal", "#008080");
		labeledColors.put("white", "#ffffff");
		labeledColors.put("yellow", "#ffff00");
	}
	private static final String COLOR_PREFIX = "#";

	private final String mColorAsSixHexRGB;

	/**
	* The color aqua in sRGB space.
	*/
	public static final Color AQUA = new Color("#00ffff");


	/**
	* The color black in sRGB space.
	*/
	public static final Color BLACK = new Color("#000000");


	/**
	* The color blue in sRGB space.
	*/
	public static final Color BLUE = new Color("#0000ff");


	/**
	* The color fuchsia in sRGB space.
	*/
	public static final Color FUCHSIA = new Color("#ff00ff");


	/**
	* The color gray in sRGB space.
	*/
	public static final Color GRAY = new Color("#808080");


	/**
	* The color green in sRGB space.
	*/
	public static final Color GREEN = new Color("#008000");


	/**
	* The color lime in sRGB space.
	*/
	public static final Color LIME = new Color("#00ff00");


	/**
	* The color maroon in sRGB space.
	*/
	public static final Color MAROON = new Color("#800000");


	/**
	* The color navy in sRGB space.
	*/
	public static final Color NAVY = new Color("#000080");


	/**
	* The color olive in sRGB space.
	*/
	public static final Color OLIVE = new Color("#808000");


	/**
	* The color orange in sRGB space.
	*/
	public static final Color ORANGE = new Color("#ffA500");


	/**
	* The color purple in sRGB space.
	*/
	public static final Color PURPLE = new Color("#800080");


	/**
	* The color red in sRGB space.
	*/
	public static final Color RED = new Color("#ff0000");


	/**
	* The color silver in sRGB space.
	*/
	public static final Color SILVER = new Color("#c0c0c0");


	/**
	* The color teal in sRGB space.
	*/
	public static final Color TEAL = new Color("#008080");


	/**
	* The color white in sRGB space.
	*/
	public static final Color WHITE = new Color("#ffffff");


	/**
	* The color yellow in sRGB space.
	*/
	public static final Color YELLOW = new Color("#ffff00");


	/**
	 * Construct Color by the parsing the given string. The string should be observed sRGB color standard
	 * which starts with "#" and following with six numbers or three numbers in Hex format.
	 * For example, "#FFFFFF" is a valid argument and white color will be constructed.
	 * <p>
	 * For further information on sRGB,
	 * see <A href="http://www.w3.org/pub/WWW/Graphics/Color/sRGB.html">
	 * http://www.w3.org/pub/WWW/Graphics/Color/sRGB.html
	 * </A>.
	 *
	 * @param color  represented using the 3 or 6 HEX sRGB notation.
	 * @throws IllegalArgumentException  if the given argument is not a valid Color in sRGB HEX notation.
	 */
	public Color(String color) {
		if(!isValid(color)){
			throw new IllegalArgumentException("parameter is invalid for datatype Color");
		}
		if(color.length()==4){
			mColorAsSixHexRGB = mapColorFromThreeToSixHex(color);
		}else{
			mColorAsSixHexRGB = color;
		}
	}

	/**
	 * Construct Color using the specified red, green and blue values in the range (0 - 255).
	 *
	 * @param red  the red component.
	 * @param green  the green component.
	 * @param blue  the blue component.
	 * @throws IllegalArgumentException  if <code>red</code>, <code>green</code> or <code>blue</code>
	 *         are outside of the range 0 to 255, inclusive.
	 */
	public Color(int red, int green, int blue) {
		this(mapColorIntegerToString(red, green, blue));
	}

	/**
	 * Construct Color using the specified red, green, and blue values in the range (0.0 - 1.0).
	 *
	 * @param red  the red component
	 * @param green  the green component
	 * @param blue  the blue component
	 * @throws IllegalArgumentException  if <code>red</code>, <code>green</code> or <code>blue</code> are
	 *             outside of the range 0.0 to 1.0, inclusive.
	 */
	public Color(float red, float green, float blue) {
		this((int) (red * 255 + 0.5), (int) (green * 255 + 0.5), (int) (blue * 255 + 0.5));
	}

	/**
	 * Construct Color using {@link java.awt.Color <code>java.awt.Color</code>}.
	 *
	 * @param color the specified {@link java.awt.Color <code>java.awt.Color</code>}.
	 * @throws IllegalArgumentException  if the given argument is not a valid Color.
	 * @see java.awt.Color
	 */
	public Color(java.awt.Color color) {
		this(color.getRed(),color.getGreen(),color.getBlue());
	}

	/**
	 * Returns the Color in six HEX sRGB notation.
	 * format.
	 *
	 * @return a six number hexadecimal string representation of the Color
	 */
	@Override
	public String toString() {
		return mColorAsSixHexRGB;
	}

	/**
	 * Returns a Color instance representing the specified String value.
	 *
	 * @param colorValue a six (or three) number hexadecimal string representation of the Color
	 * @return return a Color instance representing <code>stringValue</code>.
	 * @throws IllegalArgumentException  if the given argument is not a valid Color.
	 */
	public static Color valueOf(String colorValue) {
		return new Color(colorValue);
	}

	/**
	 * Check if the specified String is a valid {@odf.datatype color} data type.
	 *
	 * @param colorValue  a six (or three) number hexadecimal string representation of the Color
	 * @return true if the value of argument is valid for{@odf.datatype color} data type false otherwise.
	 */
	public static boolean isValid(String colorValue) {
		if ((colorValue == null) || !(threeHexRGBPattern.matcher(colorValue).matches() || sixHexRGBPattern.matcher(colorValue).matches())) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Convert RGB color formats to six-digit hex RGB format.
	 *
	 * The RGB mapping works as follows:
	 * rgb(110%, 0%, 0%)----clipped to rgb(100%,0%,0%), return #ff0000
	 * maroon----one of the seventeen fixed labeled numbers, return #800000
	 * #ff0000----six-digit notation #rrggbb, returns the input
	 * #f00----three-digit notation #rgb, return #ff0000
	 * rgb(255,0,0)----integer range 0 - 255, return #ff0000
	 * rgb(300,0,0)----clipped to rgb(255,0,0), return #ff0000
	 * rgb(255,-10,0)----clipped to rgb(255,0,0), return #ff0000
	 *
	 * @param colorValue  The sRGB color value to be converted.
	 * @return the converted color.
	 */
	public static String toSixDigitHexRGB(String colorValue){
		if (colorValue == null) {
			throw new IllegalArgumentException("parameter should not be null.");
		} else {
			colorValue = colorValue.toLowerCase().trim();
			if (sixHexRGBPattern.matcher(colorValue).matches()) {
				// 6-digit notation #rrggbb - return itself.
				return colorValue;
			} else if (threeHexRGBPattern.matcher(colorValue).matches()) {
				// convert 3-digit notation #rgb.
				return mapColorFromThreeToSixHex(colorValue);
			} else if (colorValue.startsWith("rgb")) {
				colorValue = colorValue.substring(3);
				colorValue = colorValue.substring(colorValue.indexOf("(") + 1,
						colorValue.indexOf(")"));
				String[] rgbValues = colorValue.split(",");
				if (rgbValues.length == 3) {
					int r = mapColorValueToInteger(rgbValues[0].trim());
					int g = mapColorValueToInteger(rgbValues[1].trim());
					int b = mapColorValueToInteger(rgbValues[2].trim());
					String rs = Integer.toHexString(r);
					String gs = Integer.toHexString(g);
					String bs = Integer.toHexString(b);
					String hexColor = COLOR_PREFIX;
					if (r < 16) {
						hexColor += "0";
					}
					hexColor += rs;
					if (g < 16) {
						hexColor += "0";
					}
					hexColor += gs;
					if (b < 16) {
						hexColor += "0";
					}
					hexColor += bs;
					return hexColor;
				}else{
					throw new IllegalArgumentException("parameter: "+ colorValue + " can't be converted six-digit hex RGB.");
				}
			} else {
				// convert the seventeen fixed labeled numbers.
				String hexColor = labeledColors.get(colorValue);
				if (hexColor != null) {
					return hexColor;
				} else {
					throw new IllegalArgumentException("parameter: "+ colorValue + " can't be converted six-digit hex RGB.");
				}
			}
		}
	}

	/**
	 * Return the corresponding {@link java.awt.Color <code>java.awt.Color</code>} instance of the Color data type.
	 *
	 * @return the converted {@link java.awt.Color <code>java.awt.Color</code>} instance..
	 */
	public java.awt.Color getAWTColor(){
		return mapColorToAWTColor(this);
	}

	/**
	 * Map a Color data type to {@link java.awt.Color <code>java.awt.Color</code>}.
	 *
	 * @param color  The color data type to be mapped..
	 * @return the converted {@link java.awt.Color <code>java.awt.Color</code>} instance.
	 */
	public static java.awt.Color mapColorToAWTColor(Color color) {
		int rgb = Integer.decode("0x" + color.mColorAsSixHexRGB.substring(1));
		return new java.awt.Color(rgb);
	}

	/**
	 * Converts Color expressed by red, green and blue values in the range (0 - 255) to
	 * a string format which is used in {@odf.datatype color}.
	 *
	 * @param red  the red component.
	 * @param green  the green component.
	 * @param blue  the blue component.
	 * @return the string format color.
	 */
	private static String mapColorIntegerToString(int red, int green, int blue) {
		String rs = Integer.toHexString(red);
		String gs = Integer.toHexString(green);
		String bs = Integer.toHexString(blue);
		String hexColor = COLOR_PREFIX;
		if (red < 16) {
			hexColor += "0";
		}
		hexColor += rs;
		if (green < 16) {
			hexColor += "0";
		}
		hexColor += gs;
		if (blue < 16) {
			hexColor += "0";
		}
		hexColor += bs;
		return hexColor;
	}

	/**
	 * Converts Color from three-digit to six-digit form. The three-digit (#rgb) is converted into six-digit form (#rrggbb) by replicating digits,
	 * not by adding zeros. For example, #fb0 expands to #ffbb00.
	 * 	 *
	 * @param threeDigitcColor  the three-digit color form.
	 * @return the six-digit color form.
	 */
	private static String mapColorFromThreeToSixHex(String threeDigitcColor) {
		char[] colorData = threeDigitcColor.toCharArray();
		char[] sixDigitColor = new char[7];
		for (int i = 0; i < 7; i++) {
			sixDigitColor[i] = colorData[(i + 1) / 2];
		}
		return new String(sixDigitColor);
	}

	/** Convert percent string and integer string to integer.
		value range will be checked and adapted to border (error correction) */
	private static int mapColorValueToInteger(String colorValue) {
		if (colorValue.endsWith("%")) {
			colorValue = colorValue.substring(0, colorValue.indexOf("%"));
			int value = Integer.parseInt(colorValue);
			if (value < 0) {
				value = 0;
			}
			if (value > 100) {
				value = 100;
			}
			return 255 * value / 100;
		} else {
			int value = Integer.parseInt(colorValue);
			if (value < 0) {
				value = 0;
			}
			if (value > 255) {
				value = 255;
			}
			return value;
		}
	}


    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Color other = (Color) obj;
        if ((this.mColorAsSixHexRGB == null) ? (other.mColorAsSixHexRGB != null) : !this.mColorAsSixHexRGB.equals(other.mColorAsSixHexRGB)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 97 * hash + (this.mColorAsSixHexRGB != null ? this.mColorAsSixHexRGB.hashCode() : 0);
        return hash;
    }

}
