/* DialModelScale.java

	Purpose:
		
	Description:
		
	History:
		Jun 24, 2009 5:21:08 PM, Created by henrichen

Copyright (C) 2009 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.0 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/

package org.zkoss.zul;

import java.awt.Font;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.zkoss.lang.Objects;
import org.zkoss.zul.event.ChartDataEvent;
import org.zkoss.zul.event.DialChartDataEvent;

/**
 * A set of information of a scale in a Dial chart. You cannot new a DialModelScale
 * directly; instead, use {@link DialModel#newScale()} to start setting the scale.
 * @author henrichen
 *
 */
public class DialModelScale implements Serializable {
	private DialModel _model;

	//data
	private double _value;

	//text annotation
	private String _text;
	private Font _textFont; //14, 10
	private double _textRadius = 0.7; //0.7, 0.5

	//value indicator
	private Font _valueFont; //10, 10
	private double _valueRadius = 0.6; //0.6, 0.6
	private double _valueAngle = -90; //-103.0, -77.0 

	//tick
	private Font _tickFont; //14, 10
	private double _tickRadius = 0.88; //0.88, 0.50
	private String _tickColor; //tick, 0xFF0000
	private int[] _tickRGB; //tick, red, green, blue (0 ~ 255, 0 ~ 255, 0 ~ 255)
	private double _tickLabelOffset = 0.20; //offset between tick and tick label

	//scale
	double _lowerBound;
	double _upperBound;
	double _startAngle;
	double _extent;
	double _majorTickInterval;
	int _minorTickCount;

	//ranges
	private List<DialModelRange> _ranges = new ArrayList<DialModelRange>(4);

	//needle
	private String _needleType = "pointer"; //"pointer", "pin"
	private String _needleColor; //needle, 0xFF0000
	private int[] _needleRGB; //needle, red, green, blue (0 ~ 255, 0 ~ 255, 0 ~ 255)
	private double _needleRadius = 0.9; //0.9, 0.55

	/*package*/ DialModelScale(DialModel model) {
		_model = model;
	}

	public int getIndex() {
		return _model.indexOf(this);
	}

	/** Get the value */
	public double getValue() {
		return _value;
	}

	public void setValue(double val) {
		if (Double.compare(_value, val) != 0) {
			_value = val;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.SCALE_VALUE, val);
		}
	}

	/**
	 * Returns the text annotation of this scale.
	 */
	public String getText() {
		return _text;
	}

	/**
	 * Sets the text annotation of this scale; e.g. "Temperature" for a temperature dial meter.
	 * @param text text annotation(subtitle) of this scale.
	 */
	public void setText(String text) {
		if (!Objects.equals(text, _text)) {
			_text = text;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.SCALE_TEXT, text);
		}
	}

	/**
	 * Returns the text annotation font.
	 * @return the text annotation font.
	 */
	public Font getTextFont() {
		return _textFont;
	}

	/**
	 * Sets the text annotation font.
	 * @param font the text annotation font.
	 */
	public void setTextFont(Font font) {
		if (!Objects.equals(font, _textFont)) {
			_textFont = font;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.SCALE_FONT, font);
		}
	}

	/**
	 * Return the radius percentage(0 ~ 1) to place the text annotation.
	 * @return the radius percentage(0 ~ 1) to place the text annotation.
	 */
	public double getTextRadius() {
		return _textRadius;
	}

	/**
	 * Sets the radius percentage(0 ~ 1) to place the text annotation.
	 * @param radius radius percentage(0 ~ 1) to place the text annotation.
	 */
	public void setTextRadius(double radius) {
		if (Double.compare(_textRadius, radius) != 0) {
			_textRadius = radius;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.TEXT_RADIUS, radius);
		}
	}

	/**
	 * Returns the value font.
	 * @return the value font.
	 */
	public Font getValueFont() {
		return _valueFont;
	}

	/**
	 * Sets the value font.
	 * @param font the value font.
	 */
	public void setValueFont(Font font) {
		if (!Objects.equals(font, _valueFont)) {
			_valueFont = font;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.VALUE_FONT, font);
		}
	}

	/**
	 * Return the radius percentage(0 ~ 1) to place the value.
	 * @return the radius percentage(0 ~ 1) to place the value.
	 */
	public double getValueRadius() {
		return _valueRadius;
	}

	/**
	 * Sets the radius percentage(0 ~ 1) to place the value.
	 * @param radius radius percentage(0 ~ 1) to place the value.
	 */
	public void setValueRadius(double radius) {
		if (Double.compare(_valueRadius, radius) != 0) {
			_valueRadius = radius;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.VALUE_RADIUS, radius);
		}
	}

	/**
	 * Return the angle to place the value (counter clockwise is positive).
	 * @return the angle to place the value (counter clockwise is positive).
	 */
	public double getValueAngle() {
		return _valueAngle;
	}

	/**
	 * Sets the angle in degree to place the value (counter clockwise is positive).
	 * @param angle angle in degree to place the value (counter clockwise is positive).
	 */
	public void setValueAngle(double angle) {
		if (Double.compare(_valueAngle, angle) != 0) {
			_valueAngle = angle;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.VALUE_ANGLE, angle);
		}
	}

	/**
	 * Sets the scale information of this DialModelScale.
	 * @param lowerBound lower bound of this scale.
	 * @param upperBound upper bound of this scale.
	 * @param startAngle starting angle in degree associated to the sclae's lower bound(0 degree point to east, counter-clockwise is positive). 
	 * @param extent angles in degree extended from the starting angle (counter clockwise is positive).
	 * @param majorTickInterval the interval between major tick (in lower bound and upper bound).
	 * @param minorTickCount the number of minor ticks between major tick.
	 */
	public void setScale(double lowerBound, double upperBound, double startAngle, double extent,
			double majorTickInterval, int minorTickCount) {
		if (Double.compare(lowerBound, _lowerBound) != 0 || Double.compare(upperBound, _upperBound) != 0
				|| Double.compare(startAngle, _startAngle) != 0 || Double.compare(extent, _extent) != 0
				|| Double.compare(majorTickInterval, _majorTickInterval) != 0
				|| Double.compare(minorTickCount, _minorTickCount) != 0) {
			_lowerBound = lowerBound;
			_upperBound = upperBound;
			_startAngle = startAngle;
			_extent = extent;
			_majorTickInterval = majorTickInterval;
			_minorTickCount = minorTickCount;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.SCALE, this);
		}
	}

	/**
	 * Returns the scale's lower bound.
	 * @return the scale's lower bound.
	 */
	public double getScaleLowerBound() {
		return _lowerBound;
	}

	/**
	 * Returns the scale's upper bound.
	 * @return the scale's upper bound.
	 */
	public double getScaleUpperBound() {
		return _upperBound;
	}

	/**
	 * Returns starting angle in degree associated to the sclae's lower bound
	 * (0 degree point to east, counter-clockwise is positive).
	 * @return starting angle in degree associated to the sclae's lower bound
	 * (0 degree point to east, counter-clockwise is positive).
	 */
	public double getScaleStartAngle() {
		return _startAngle;
	}

	/**
	 * Returns angles in degree extended from the starting angle (counter clockwise is positive).
	 * @return angles in degree extended from the starting angle (counter clockwise is positive).
	 */
	public double getScaleExtent() {
		return _extent;
	}

	/**
	 * Returns the interval between major tick (in lower bound and upper bound).
	 * @return the interval between major tick (in lower bound and upper bound).
	 */
	public double getMajorTickInterval() {
		return _majorTickInterval;
	}

	/**
	 * returns the number of minor ticks between major tick.
	 * @return the number of minor ticks between major tick.
	 */
	public int getMinorTickCount() {
		return _minorTickCount;
	}

	/**
	 * Returns the tick label font.
	 * @return the tick label font.
	 */
	public Font getTickFont() {
		return _tickFont;
	}

	/**
	 * Sets the tick label font.
	 * @param font the tick label font.
	 */
	public void setTickFont(Font font) {
		if (!Objects.equals(font, _tickFont)) {
			_tickFont = font;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.TICK_FONT, font);
		}
	}

	/**
	 * Return the radius percentage(0 ~ 1) to place the tick label.
	 * @return the radius percentage(0 ~ 1) to place the tick label.
	 */
	public double getTickRadius() {
		return _tickRadius;
	}

	/**
	 * Sets the radius percentage(0 ~ 1) to place the tick label.
	 * @param radius radius percentage(0 ~ 1) to place the tick label.
	 */
	public void setTickRadius(double radius) {
		if (Double.compare(_tickRadius, radius) != 0) {
			_tickRadius = radius;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.TICK_RADIUS, radius);
		}
	}

	/**
	 * Returns the radius offset in percentage(0 ~ 1) between the tick and tick label.
	 * @return the radius offset in percentage(0 ~ 1) between the tick and tick label.
	 */
	public double getTickLabelOffset() {
		return _tickLabelOffset;
	}

	/**
	 * Sets the radius offset in percentage(0 ~ 1) between the tick and tick label.
	 * @param tickLabelOffset the radius offset in percentage(0 ~ 1) between the tick and tick label.
	 */
	public void setTickLabelOffset(double tickLabelOffset) {
		if (Double.compare(_tickLabelOffset, tickLabelOffset) != 0) {
			_tickLabelOffset = tickLabelOffset;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.TICK_LABEL_OFFSET, tickLabelOffset);
		}
	}

	/**
	 * Set the tick color.
	 * @param color in #RRGGBB format (hexadecimal).
	 */
	public void setTickColor(String color) {
		if (Objects.equals(color, _tickColor)) {
			return;
		}
		_tickColor = color;
		if (_tickColor == null) {
			_tickRGB = null;
		} else {
			_tickRGB = new int[3];
			Chart.decode(_tickColor, _tickRGB);
		}
		fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.TICK_COLOR, color);
	}

	/**
	 * Get the tick color of this scale(in string as #RRGGBB).
	 * null means default.
	 */
	public String getTickColor() {
		return _tickColor;
	}

	/**
	 * Get the tick color of this scale in int array (0: red, 1: green, 2:blue).
	 * null means default.
	 */
	public int[] getTickRGB() {
		return _tickRGB;
	}

	/**
	 * Setup the DailModel range.
	 * @param lower the lower bound in the scale.
	 * @param upper the upper bound in the scale.
	 * @param color the color in #RRGGBB format (hexadecimal); default to blue.
	 * @param innerRadius the inner radius percentage(0 ~ 1) of the range; default to 0.7.
	 * @param outerRadius the outer radius percentage(0 ~ 1) of the range; default to 0.8;
	 */
	public DialModelRange newRange(double lower, double upper, String color, double innerRadius, double outerRadius) {
		final DialModelRange range = new DialModelRange(this);
		range.setRange(lower, upper, color, innerRadius, outerRadius);
		_ranges.add(range);
		fireEvent(ChartDataEvent.ADDED, DialChartDataEvent.RANGE, range);
		return range;
	}

	/** Returns the number of ranges associated with this scale.
	 * 
	 * @return the number of ranges associated with this scale.
	 */
	public int rangeSize() {
		return _ranges.size();
	}

	/** Returns the color range of the specified index.
	 * 
	 * @param index the specified index.
	 * @return the color range of the specified index.
	 */
	public DialModelRange getRange(int index) {
		return _ranges.get(index);
	}

	/**
	 * Removes the specified range from this scale.
	 * @param range the range to be removed.
	 */
	public void removeRange(DialModelRange range) {
		int index = _ranges.indexOf(range);
		_ranges.remove(range);
		fireEvent(ChartDataEvent.REMOVED, DialChartDataEvent.RANGE, index);
	}

	/**
	 * Returns the needle type of this scale ("pointer" or "pin")
	 * @return the needle type of this scale ("pointer" or "pin")
	 */
	public String getNeedleType() {
		return _needleType;
	}

	/**
	 * Sets the needle type of this scale ("pointer" or "pin")
	 * @param type the needle type of this scale ("pointer" or "pin")
	 */
	public void setNeedleType(String type) {
		if (!Objects.equals(_needleType, type)) {
			_needleType = type;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.NEEDLE_TYPE, type);
		}
	}

	/**
	 * Set the needle color.
	 * @param color in #RRGGBB format (hexadecimal).
	 */
	public void setNeedleColor(String color) {
		if (Objects.equals(color, _needleColor)) {
			return;
		}
		_needleColor = color;
		if (_needleColor == null) {
			_needleRGB = null;
		} else {
			_needleRGB = new int[3];
			Chart.decode(_needleColor, _needleRGB);
		}
		fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.NEEDLE_COLOR, color);
	}

	/**
	 * Get the needle color of this scale(in string as #RRGGBB).
	 * null means default.
	 */
	public String getNeedleColor() {
		return _needleColor;
	}

	/**
	 * Get the needle color of this scale in int array (0: red, 1: green, 2:blue).
	 * null means default.
	 */
	public int[] getNeedleRGB() {
		return _needleRGB;
	}

	/**
	 * Sets the radius percentage(0 ~ 1) of the scale's needle; default to 0.9.
	 * @param radius the radius percentage(0 ~ 1) of the scale's needle; default to 0.9.
	 */
	public void setNeedleRadius(double radius) {
		if (Double.compare(_needleRadius, radius) != 0) {
			_needleRadius = radius;
			fireEvent(ChartDataEvent.CHANGED, DialChartDataEvent.NEEDLE_RADIUS, radius);
		}
	}

	/**
	 * Return the radius percentage(0 ~ 1) of the scale's needle; default to 0.9.
	 * @return the radius percentage(0 ~ 1) of the scale's needle; default to 0.9.
	 */
	public double getNeedleRadius() {
		return _needleRadius;
	}

	/**
	 * Utility method to delegate event to {@link DialModel}
	 * @param evt the {@link ChartDataEvent}.
	 */
	/*package*/ void fireEvent(int evt, String propertyKey, Object data) {
		if (_model != null)
			_model.fireEvent(evt, propertyKey, data);
	}
}
