/*
 * Paraselene
 * Copyright (c) 2009, 2010  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene;

import java.math.*;
import paraselene.mockup.*;
import paraselene.tag.*;
import paraselene.css.*;


/**
 * 数を表す文字列。
 */
public class NumberValue extends PlainText implements AttributeValuable, CSSValuable {
	private static final long serialVersionUID = 2L;
	/**
	 * 無限回を表すNumber。
	 */
	public static final Number INFINITE = new Infinite();
	/**
	 * アスタリスク(残り全て)を表すNumber。
	 */
	public static final Number LEFTOVER = new Leftover();

	/**
	 * 符号。
	 */
	public enum Sign {
		/**
		 * 指定なし。
		 */
		NON( "" ),
		/**
		 * プラス。
		 */
		PLUS( "+" ),
		/**
		 * マイナス。
		 */
		MINUS( "-" );
		String	str;
		private Sign( String s ) {
			str = s;
		}
	}
	/**
	 * 単位を表します。<br>分かりにくい角度の単位のみ下記に纏めます。
<table border="1">
  <tbody>
    <tr>
      <th>DEGREE</th>
      <th>GRADIENT</th>
      <th colspan="2">RADIAN</th>
    </tr>
    <tr>
      <td align="right">0deg</td>
      <td align="right">0grad</td>
      <td align="right">0rad</td>
      <td align="right" style="font-size : x-small;">=0/2π</td>
    </tr>
    <tr>
      <td align="right">90deg</td>
      <td align="right">100grad</td>
      <td align="right">1.570796326794897rad</td>
      <td align="right" style="font-size : x-small;">=1/2π</td>
    </tr>
    <tr>
      <td align="right">180deg</td>
      <td align="right">200grad</td>
      <td align="right">3.141592653589793rad</td>
      <td align="right" style="font-size : x-small;">=2/2π</td>
    </tr>
    <tr>
      <td align="right">270deg</td>
      <td align="right">300grad</td>
      <td align="right">4.712388980384690rad</td>
      <td align="right" style="font-size : x-small;">=3/2π</td>
    </tr>
    <tr>
      <td align="right">360deg</td>
      <td align="right">400grad</td>
      <td align="right">6.283185307179586rad</td>
      <td align="right" style="font-size : x-small;">=4/2π</td>
    </tr>
  </tbody>
</table>
	 */
	public enum Unit {
		/**
		 * 指定なし。
		 */
		NON( "" ),
		/**
		 * 文字の高さ。em
		 */
		EM( "em" ),
		/**
		 * x の高さ。ex
		 */
		X_HEIGHT( "ex" ),
		/**
		 * ピクセル。px
		 */
		PIXEL( "px" ),
		/**
		 * インチ(2.54cm)。in
		 */
		INCHE( "in" ),
		/**
		 * センチメートル。cm
		 */
		CENTIMETER( "cm" ),
		/**
		 * ミリメートル(1/10cm)。mm
		 */
		MILLIMETER( "mm" ),
		/**
		 * ポイント(1/72インチ)。pt
		 */
		POINT( "pt" ),
		/**
		 * パイカ(12ポイント)。pc
		 */
		PICA( "pc" ),
		/**
		 * パーセント。%
		 */
		PERCENTAGE( "%" ),
		/**
		 * ディグリー(角度)。deg
		 */
		DEGREE( "deg" ),
		/**
		 * グラジエント(傾斜度)。grad
		 */
		GRADIENT( "grad" ),
		/**
		 * ラジアン。rad
		 */
		RADIAN( "rad" ),
		/**
		 * ミリ秒。ms
		 */
		MILLISECOND( "ms" ),
		/**
		 * 秒。s
		 */
		SECOND( "s" ),
		/**
		 * ヘルツ。hz
		 */
		HERTZ( "hz" ),
		/**
		 * キロヘルツ。khz
		 */
		KILOHERTZ( "khz" );
		String	str;
		private Unit( String u ) {
			str = u;
		}
		/**
		 * 単位の文字列表現を返します。
		 * @return 単位。
		 */
		public String toString() {
			return str;
		}
	}
	private Sign	sign_data;
	private double	val;
	private Unit	unit_data;

	/**
	 * 値の設定。
	 * @param sign	符号。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public void setNumber( Sign sign, double num, Unit unit ) {
		if ( num < 0 )	sign = Sign.MINUS;
		num = Math.abs( num );
		sign_data = sign;
		val = num;
		unit_data = unit;

		StringBuilder	buf = new StringBuilder( sign_data.str );
		String	vstr = Double.toString( num );
		String[]	part = vstr.split( "\\." );
		buf = buf.append( part[0] );
		if ( part.length > 1 ) {
			char[]	ch = part[1].toCharArray();
			int	len = 0;
			for ( int i = 0; i < ch.length; i++ ) {
				if ( ch[i] != '0' ) {
					len = i + 1;
				}
			}
			if ( len > 0 ) {
				buf = buf.append( "." );
				buf = buf.append( part[1].substring( 0, len ) );
			}
		}
		buf = buf.append( unit_data.str );
		super.setText( buf.toString() );
	}

	/**
	 * 値の設定。
	 * @param sign	符号。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public void setNumber( Sign sign, int num, Unit unit ) {
		setNumber( sign, (double)num, unit );
	}

	/**
	 * 値の設定。
	 * @param sign	符号。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public void setNumber( Sign sign, Number num, Unit unit ) {
		setNumber( sign, num.doubleValue(), unit );
	}

	/**
	 * 値の設定。
	 * @param sign	符号。
	 * @param num	値。
	 */
	public void setNumber( Sign sign, double num ) {
		setNumber( sign, num, Unit.NON );
	}

	/**
	 * 値の設定。
	 * @param sign	符号。
	 * @param num	値。
	 */
	public void setNumber( Sign sign, int num ) {
		setNumber( sign, (double)num, Unit.NON );
	}

	/**
	 * 値の設定。
	 * @param sign	符号。
	 * @param num	値。
	 */
	public void setNumber( Sign sign, Number num ) {
		setNumber( sign, num.doubleValue(), Unit.NON );
	}

	/**
	 * 値の設定。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public void setNumber( double num, Unit unit ) {
		setNumber( Sign.NON, num, unit );
	}

	/**
	 * 値の設定。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public void setNumber( int num, Unit unit ) {
		setNumber( Sign.NON, (double)num, unit );
	}

	/**
	 * 値の設定。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public void setNumber( Number num, Unit unit ) {
		setNumber( Sign.NON, num.doubleValue(), unit );
	}

	/**
	 * 値の設定。
	 * @param str	文字列。
	 */
	public void setText( String str ) {
		if ( INFINITE.toString().equalsIgnoreCase( str ) ) {
			setNumber( Sign.NON, INFINITE );
			super.setText( INFINITE.toString() );
			return;
		}
		if ( LEFTOVER.toString().equalsIgnoreCase( str ) ) {
			setNumber( Sign.NON, LEFTOVER );
			super.setText( LEFTOVER.toString() );
			return;
		}

		int	start = 1;
		char	ch = str.charAt( 0 );
		Sign	sign = Sign.NON;
		switch( ch ) {
		case '+':
			sign = Sign.PLUS;
			break;
		case '-':
			sign = Sign.MINUS;
			break;
		default:
			start = 0;
			break;
		}

		Unit	unit = Unit.NON;
		str = str.substring( start );
		int	len = str.length();
		Unit[]	dim = Unit.values();
		for ( int i = 0; i < dim.length; i++ ) {
			if ( dim[i] == Unit.NON )	continue;
			int	unit_len = dim[i].str.length();
			start = len - unit_len;
			if ( start < 1 )	continue;
			String	u = str.substring( start );
			if ( dim[i].str.equalsIgnoreCase( u ) ) {
				unit = dim[i];
				len = start;
				break;
			}
		}
		setNumber( sign, Double.parseDouble( str.substring( 0, len ) ), unit );
	}

	/**
	 * 値の設定。
	 * @param num	値。
	 */
	public void setNumber( double num ) {
		setNumber( Sign.NON, num, Unit.NON );
	}

	/**
	 * 値の設定。
	 * @param num	値。
	 */
	public void setNumber( int num ) {
		setNumber( Sign.NON, (double)num, Unit.NON );
	}

	/**
	 * 値の設定。
	 * @param num	値。
	 */
	public void setNumber( Number num ) {
		setNumber( Sign.NON, num.doubleValue(), Unit.NON );
	}

	/**
	 * 複製の作成。
	 * @return 複製。
	 */
	public HTMLPart getReplica() {
		return new NumberValue( sign_data, val, unit_data );
	}

	private NumberValue(){}
	/**
	 * コンストラクタ。
	 * @param sign	符号。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public NumberValue( Sign sign, double num, Unit unit ) {
		setNumber( sign, num, unit );
	}

	/**
	 * コンストラクタ。
	 * @param sign	符号。
	 * @param num	値。
	 */
	public NumberValue( Sign sign, double num ) {
		setNumber( sign, num );
	}

	/**
	 * コンストラクタ。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public NumberValue( double num, Unit unit ) {
		setNumber( num, unit );
	}

	/**
	 * コンストラクタ。
	 * @param num	値。
	 */
	public NumberValue( double num ) {
		setNumber( num );
	}

	/**
	 * コンストラクタ。
	 * @param str 文字列表現。
	 */
	public NumberValue( String str ) {
		setText( str );
	}


	/**
	 * コンストラクタ。
	 * @param sign	符号。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public NumberValue( Sign sign, int num, Unit unit ) {
		setNumber( sign, num, unit );
	}

	/**
	 * コンストラクタ。
	 * @param sign	符号。
	 * @param num	値。
	 */
	public NumberValue( Sign sign, int num ) {
		setNumber( sign, num );
	}

	/**
	 * コンストラクタ。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public NumberValue( int num, Unit unit ) {
		setNumber( num, unit );
	}

	/**
	 * コンストラクタ。
	 * @param num	値。
	 */
	public NumberValue( int num ) {
		setNumber( num );
	}


	/**
	 * コンストラクタ。
	 * @param sign	符号。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public NumberValue( Sign sign, Number num, Unit unit ) {
		setNumber( sign, num, unit );
	}

	/**
	 * コンストラクタ。
	 * @param sign	符号。
	 * @param num	値。
	 */
	public NumberValue( Sign sign, Number num ) {
		setNumber( sign, num );
	}

	/**
	 * コンストラクタ。
	 * @param num	値。
	 * @param unit	単位。
	 */
	public NumberValue( Number num, Unit unit ) {
		setNumber( num, unit );
	}

	/**
	 * コンストラクタ。
	 * @param num	値。
	 */
	public NumberValue( Number num ) {
		setNumber( num );
	}


	/**
	 * 値の取得。doubleで返します。
	 * @return 設定値。
	 */
	public double toDouble() {
		return val;
	}

	/**
	 * 値の取得。intで返します。
	 * @return 設定値。
	 */
	public int toInteger() {
		return (int)val;
	}

	/**
	 * 値の取得。BigDecimalで返します。
	 * @return 設定値。
	 */
	public BigDecimal toBigDecimal() {
		return new BigDecimal( val );
	}

	/**
	 * 符号の取得。
	 * @return 符号。
	 */
	public Sign getSign() {
		return sign_data;
	}

	/**
	 * 単位の取得。
	 * @return 単位。
	 */
	public Unit getUnit() {
		return unit_data;
	}

	/**
	 * 加算。this + p を設定します。thisとリターン値は同じものです。
	 * @param p 加算値。
	 * @return this。
	 */
	public NumberValue add( double p ) {
		setNumber( val + p );
		return this;
	}

	/**
	 * 加算。this + p を設定します。thisとリターン値は同じものです。
	 * @param p 加算値。
	 * @return this。
	 */
	public NumberValue add( int p ) {
		return add( (double)p );
	}

	/**
	 * 加算。this + p を設定します。thisとリターン値は同じものです。
	 * @param p 加算値。
	 * @return this。
	 */
	public NumberValue add( Number p ) {
		if ( p == null )	return this;
		return add( p.doubleValue() );
	}


	/**
	 * 減算。this - p を設定します。thisとリターン値は同じものです。
	 * @param p 減算値。
	 * @return this。
	 */
	public NumberValue subtract( double p ) {
		setNumber( val - p );
		return this;
	}

	/**
	 * 減算。this - p を設定します。thisとリターン値は同じものです。
	 * @param p 減算値。
	 * @return this。
	 */
	public NumberValue subtract( int p ) {
		return subtract( (double)p );
	}

	/**
	 * 減算。this - p を設定します。thisとリターン値は同じものです。
	 * @param p 減算値。
	 * @return this。
	 */
	public NumberValue subtract( Number p ) {
		if ( p == null )	return this;
		return subtract( p.doubleValue() );
	}


	/**
	 * 乗算。this * p を設定します。thisとリターン値は同じものです。
	 * @param p 乗算値。
	 * @return this。
	 */
	public NumberValue multiply( double p ) {
		setNumber( val * p );
		return this;
	}

	/**
	 * 乗算。this * p を設定します。thisとリターン値は同じものです。
	 * @param p 乗算値。
	 * @return this。
	 */
	public NumberValue multiply( int p ) {
		return multiply( (double)p );
	}

	/**
	 * 乗算。this * p を設定します。thisとリターン値は同じものです。
	 * @param p 乗算値。
	 * @return this。
	 */
	public NumberValue multiply( Number p ) {
		if ( p == null )	return this;
		return multiply( p.doubleValue() );
	}


	/**
	 * 除算。this / p を設定します。thisとリターン値は同じものです。
	 * pが0なら値は変化しません。
	 * @param p 除算値。
	 * @return this。
	 */
	public NumberValue divide( double p ) {
		if ( p == 0 )	return this;
		setNumber( val / p );
		return this;
	}

	/**
	 * 除算。this / p を設定します。thisとリターン値は同じものです。
	 * @param p 除算値。
	 * @return this。
	 */
	public NumberValue divide( int p ) {
		return divide( (double)p );
	}

	/**
	 * 除算。this / p を設定します。thisとリターン値は同じものです。
	 * @param p 除算値。
	 * @return this。
	 */
	public NumberValue divide( Number p ) {
		if ( p == null )	return this;
		return divide( p.doubleValue() );
	}
}


