/*
 * Created on 2004/07/28
 *
 * To change the template for this generated file go to
 * Window>Preferences>Java>Code Generation>Code and Comments
 */
package jp.riken.brain.ni.samuraigraph.base;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.ParseException;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;


/**
 * @author kuromaru
 *
 * To change the template for this generated type comment go to
 * Window>Preferences>Java>Code Generation>Code and Comments
 */
public class SGSpinner extends JSpinner implements SGIConstants, PropertyChangeListener
{


	/**
	 * 
	 */
	public static final String SPINNER_UNIT_CM = " "+cm;


	/**
	 * 
	 */
	public static final String SPINNER_UNIT_MM = " "+mm;


	/**
	 * 
	 */
	public static final String SPINNER_UNIT_PT = " "+pt;


	/**
	 * 
	 */
	public static final String SPINNER_UNIT_INCH = " "+inch;


	/**
	 * 
	 */
	public static final String SPINNER_UNIT_DEGREE = degree;



	/**
	 * 
	 */
	public static final int DEFAULT_DIGIT_FOR_ROUND_OUT = -3;


	/**
	 *
	 */
	private int mDigitForRoundOut = DEFAULT_DIGIT_FOR_ROUND_OUT;




	/**
	 * 
	 */
	public SGSpinner()
	{
		super();
	}


	/**
	 * 
	 */
	public void propertyChange( PropertyChangeEvent e )
	{
		Object source = e.getSource();
		
		// modify the value after committed
		if( source.equals( this.getFormattedTextField() ) )
		{
			String tmp = this.mTempString;
			if( tmp!=null )
			{
				JFormattedTextField ftf = (JFormattedTextField)source;
				String str = ftf.getText();
				if( tmp.equals( str ) == false )
				{
					String unit = this.getUnit();
					if( this.isInteger() == false )
					{
						String strOld = SGUtilityNumber.removeUnit( tmp, unit );
						String strNew = SGUtilityNumber.removeUnit( str, unit );
						Float valueOld = Float.valueOf( strOld );
						Float valueNew = Float.valueOf( strNew );

						// get the step value between two values
						Number num = this.getValueOnStepInside(
							valueOld.floatValue(), valueNew.floatValue() );
						if( num==null )
						{
							num = valueNew;
						}

						// set new value to the spinner
						this.setValue( num );
					}
				}

				// clear the temporary string
				this.mTempString = null;
			}
		}

	}
	
	
	
	/**
	 * 
	 * @param model
	 * @param unit
	 * @param spinner
	 * @return
	 */
	public boolean initProperties(
		final SpinnerNumberModel model,
		final String unit )
	{

		// set the spinner model
		this.setModel(model);

		// set the suffix of unit
		if( unit!=null )
		{
			this.setSuffix( unit );
		}

		// set the property of the formatted-text-field
		JFormattedTextField ftf = this.getFormattedTextField();
		ftf.setFocusLostBehavior( JFormattedTextField.PERSIST );
		ftf.setHorizontalAlignment( JTextField.LEFT );

		ftf.addPropertyChangeListener(this);
		
		//
		this.setMinimumFractionDigits(1);
		this.setMaximumFractionDigits(2);

		return true;
	}



	/**
	 * 
	 * @return
	 */
	public boolean isEditable()
	{
		return this.getFormattedTextField().isEditable();
	}


	/**
	 * 
	 * @param b
	 */
	public void setEditable( final boolean b )
	{
		this.getFormattedTextField().setEditable(b);
	}


	/**
	 * 
	 * @return
	 */
	public DecimalFormat getDecimalFormat()
	{
		final JSpinner.NumberEditor editor
			= (JSpinner.NumberEditor)this.getEditor();
		DecimalFormat df = editor.getFormat();
		return df;		
	}


	/**
	 * 
	 * @param newValue
	 */
	public boolean setMinimumFractionDigits( final int newValue )
	{
		this.getDecimalFormat().setMinimumFractionDigits(newValue);
		return true;
	}


	/**
	 * 
	 * @param newValue
	 */
	public boolean setMaximumFractionDigits( final int newValue )
	{
		this.getDecimalFormat().setMaximumFractionDigits(newValue);
		return true;
	}


	/**
	 * 
	 * @return
	 */
	public JFormattedTextField getFormattedTextField()
	{
		final JSpinner.DefaultEditor editor
			= (JSpinner.DefaultEditor)this.getEditor();
		final JFormattedTextField ftf = editor.getTextField();
		return ftf;
	}


	/**
	 * 
	 * @param spinner
	 * @param suffix
	 * @return
	 */
	public boolean setSuffix( final String suffix )
	{
		final JSpinner.NumberEditor editor
			= (JSpinner.NumberEditor)this.getEditor();
		final DecimalFormat df = editor.getFormat();
		df.setPositiveSuffix(suffix);
		df.setNegativeSuffix(suffix);
		return true;
	}


	/**
	 * 
	 * @return
	 */
	public String getSuffix()
	{
		final JSpinner.NumberEditor editor
			= (JSpinner.NumberEditor)this.getEditor();
		final DecimalFormat df = editor.getFormat();
		return df.getPositiveSuffix();
	}



	/**
	 * 
	 * @return
	 */
	public int getDigitForRoundingOut()
	{
		return this.mDigitForRoundOut;
	}



	/**
	 * 
	 * @param n
	 * @return
	 */
	public boolean setDigitForRoundingOut( final int n )
	{
		this.mDigitForRoundOut = n;
		return true;
	}



	/**
	 * 
	 * @return
	 */
	public boolean isInteger()
	{
		final DecimalFormat format = this.getDecimalFormat();
		final int min = format.getMinimumFractionDigits();
		final int max = format.getMaximumFractionDigits();
		if( min==0 && max==0 )
		{
			return true;
		}
		else
		{
			return false;
		}
	}



	/**
	 * 
	 * @param spinner
	 * @return
	 */
	public Number getNumber()
	{
		try
		{
			this.commitEditByDefault();
		}
		catch( Exception ex )
		{
			return null;
		}
		Object obj = this.getValue();
		return (Number)obj;
	}



	/**
	 * 
	 * @return
	 */
	public String getText()
	{
		return this.getFormattedTextField().getText();
	}


	/**
	 * 
	 * @param str
	 * @return
	 */
	public boolean setText( final String str )
	{
		this.getFormattedTextField().setText(str);
		return true;
	}


	/**
     * Commits the value to the <code>SpinnerModel</code> after rounding up the value.
	 */
	public void commitEdit() throws ParseException
	{
		this.commit(MODE_ON_STEP);
		super.commitEdit();

		// record the temporary string after commited
		this.mTempString = this.getText();
	}



	/**
	 * 
	 * @return
	 */
	public boolean hasValidValue()
	{
		Number num = this.getNumber();
		if( num==null )
		{
			String str = this.getText();
			if( str.equals("") == false )
			{
				return false;
			}
		}
		
		return true;
	}

	

	/**
	 * Clear the temporary String object.
	 *
	 */
	void clearTemporaryValues()
	{
		this.mTempString = null;
	}
	
	
	
	/**
	 * 
	 * @return
	 */
	public void commitEditByDefault() throws ParseException
	{
		this.commit(MODE_DEFAULT);
		super.commitEdit();
	}


	private static final int MODE_DEFAULT = 0;
	private static final int MODE_ON_STEP = 1;


	private String mTempString = null;

	/**
	 * 
	 * @return
	 */
	private boolean commit( final int mode ) throws ParseException
	{
		final JFormattedTextField ftf = this.getFormattedTextField();
		final String str = ftf.getText();

		// parse the inputted string
		String strNew = this.parseString( str, mode );
		if( strNew != null )
		{
			ftf.setText( strNew );
			try
			{
				ftf.commitEdit();
			}
			catch( ParseException ex )
			{
				throw ex;
			}
		}
		else
		{
			throw new ParseException("parseSting failed.",0);
		}

		return true;
	}



	/**
	 * 
	 * @param str
	 * @return
	 */
	private String parseString( final String str, final int mode )
	{

		if( str==null )
		{
			return null;
		}
		if( str.length()==0 )
		{
			return null;
		}

		final JFormattedTextField ftf = this.getFormattedTextField();
		final String unit = this.getUnit();
		if( unit==null )
		{
			Number num = null;
			try
			{
				num = Float.valueOf( str );
			}
			catch( NumberFormatException ex )
			{
				return null;
			}

			if( this.isInteger() )
			{
				final float fValue = num.floatValue();
				int value = (int)Math.rint(fValue);
				final int min = this.getMinimumValue().intValue();
				final int max = this.getMaximumValue().intValue();
				if( value<min )
				{
					value = min;
				}
				if( value>max )
				{
					value = max;
				}
				String strNew = Integer.toString(value);
				return strNew;
			}

			return num.toString();
		}


		String str_ = null;
		Number num = null;
		try
		{
			if( this.isInteger() )
			{
				num = Integer.valueOf( str );
			}
			else
			{
				num = Float.valueOf( str );
			}
			str_ = num.toString() + this.getSpinnerUnit(unit);
		}
		catch( NumberFormatException ex )
		{
			str_ = str;
		}

		// in case of the unit of length
		String strValue = null;
		if( SGUtilityNumber.isLengthUnit( unit ) )
		{
			// convert the string to the number in the default unit
			strValue = SGUtilityNumber.convertString( str_, unit );
		}
		else
		{
			// remove the unit
			strValue = SGUtilityNumber.removeUnit( str_, unit );
		}
		if( strValue==null )
		{
			return null;
		}

		// check the range
		if( this.isInteger() )
		{
			int value = Integer.valueOf( strValue ).intValue();
			final int min = this.getMinimumValue().intValue();
			final int max = this.getMaximumValue().intValue();
			if( value<min )
			{
				value = min;
			}
			if( value>max )
			{
				value = max;
			}
			String strNew = new Integer( value ).toString() + this.getSpinnerUnit(unit);
			return strNew;
		}


		// get the value
		float value = Float.valueOf(strValue).floatValue();
		final float min = this.getMinimumValue().floatValue();
		final float max = this.getMaximumValue().floatValue();
		if( value<min )
		{
			value = min;
		}
		if( value>max )
		{
			value = max;
		}


		// round up the number
		float valueNew = 0.0f;
		switch( mode )
		{
			case MODE_DEFAULT :
			{
				valueNew = (float)SGUtilityNumber.roundOffNumber(
					value, this.mDigitForRoundOut );
				break;
			}

			case MODE_ON_STEP :
			{
				valueNew = value;
				break;
			}
			
			default :
			{
				throw new IllegalArgumentException();
			}
		}

		String strNew = new Float( valueNew ).toString() + this.getSpinnerUnit(unit);

		return strNew;
	}


	/**
	 * 
	 * @return
	 */
	public String getSpinnerUnit( final String unit )
	{
		if( unit==null )
		{
			return null;
		}

		String sUnit = null;
		if( unit.equals( SGUtilityNumber.cm ) )
		{
			sUnit = SPINNER_UNIT_CM;
		}
		else if( unit.equals( SGUtilityNumber.mm ) )
		{
			sUnit = SPINNER_UNIT_MM;
		}
		else if( unit.equals( SGUtilityNumber.pt ) )
		{
			sUnit = SPINNER_UNIT_PT;
		}
		else if( unit.equals( SGUtilityNumber.inch ) )
		{
			sUnit = SPINNER_UNIT_INCH;
		}
		else if( unit.equals( SGUtilityNumber.degree ) )
		{
			sUnit = SPINNER_UNIT_DEGREE;
		}

		return sUnit;
	}


	/**
	 * 
	 * @return
	 */
	public String getUnit()
	{
		final JFormattedTextField ftf = this.getFormattedTextField();
		String suffix = this.getSuffix();
		String unit = null;
		if( suffix.equals( SPINNER_UNIT_CM ) )
		{
			unit = SGUtilityNumber.cm;
		}
		else if( suffix.equals( SPINNER_UNIT_MM ) )
		{
			unit = SGUtilityNumber.mm;
		}
		else if( suffix.equals( SPINNER_UNIT_INCH ) )
		{
			unit = SGUtilityNumber.inch;
		}
		else if( suffix.equals( SPINNER_UNIT_PT ) )
		{
			unit = SGUtilityNumber.pt;
		}
		else if( suffix.equals( SPINNER_UNIT_DEGREE ) )
		{
			unit = SGUtilityNumber.degree;
		}

		return unit;
	}


	/**
	 * 
	 * @return
	 */
	public Number getMinimumValue()
	{
		final SpinnerNumberModel model = (SpinnerNumberModel)this.getModel();
		return (Number)model.getMinimum();
	}


	/**
	 * 
	 * @return
	 */
	public Number getMaximumValue()
	{
		final SpinnerNumberModel model = (SpinnerNumberModel)this.getModel();
		return (Number)model.getMaximum();
	}


	/**
	 * 
	 * @return
	 */
	public Number getStepSize()
	{
		final SpinnerNumberModel model = (SpinnerNumberModel)this.getModel();
		return model.getStepSize();
	}

	

	/**
	 * 
	 * @param v1
	 * @param v2
	 * @return
	 */
	private Number getValueOnStepInside( final float v1, final float v2 )
	{
		float smaller;
		float larger;
		if( v1 < v2 )
		{
			smaller = v1;
			larger = v2;
		}
		else if( v2 < v1 )
		{
			smaller = v2;
			larger = v1;
		}
		else
		{
			return null;
		}
		final float min = this.getMinimumValue().floatValue();
		final float max = this.getMaximumValue().floatValue();
		final float step = this.getStepSize().floatValue();
		if( step < Float.MIN_VALUE )
		{
			throw new Error();
		}

		Number num = null;
		int cnt = 0;
		final int digit = this.mDigitForRoundOut;
		while(true)
		{
			final float valueOld = min + cnt*step;
			final float value = (float)SGUtilityNumber.roundOffNumber( valueOld, digit );
			if( smaller<value )
			{
				if( value<larger )
				{
					num = new Float(value);
					break;
				}
				else
				{
					break;
				}
			}
			cnt++;
		}

		return num;
	}
	
}



