/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/08/08, 19:22
!  AUTHOR(S): KOGA, Junichiro
!  File : ValueSlider.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.primitiveguis;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Vector;

import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.log4j.Logger;

import ciss.phase_viewer.acviewer.colormap.BaseColorMap;

/**
 * テキストフィールドとスライダーからなる, "値を選択する"ためのGUI
 * 
 * @author
 */
public class ValueSlider extends JPanel {
	private Logger logger = Logger.getLogger(ValueSlider.class.getName());
	public static int LOG = 0;
	public static int LINEAR = 1;
	public static int FLOAT = 2;
	public static int INT = 3;
	private float minval;
	private float maxval;
	private float initval;
	private int valtype = LINEAR;
	private int valprecision = FLOAT;

	private JSlider jSlider;
	private JLabel minLabel;
	private JLabel maxLabel;
	// private DecimalFormat format = new DecimalFormat("0.00");
	private JTextField textField;

	private Vector listeners;

	private float incre = -1;

	/**
	 * 最小値と最大値を指定する. 値の種類は, 線形であるとする.
	 * 
	 * @param minval
	 *            最小値
	 * @param maxval
	 *            最大値
	 */
	public ValueSlider(float minval, float maxval) {
		this.minval = minval;
		this.maxval = maxval;
		initval = (minval + maxval) * 0.5f;
		init();
	}

	/**
	 * 最小値と最大値を指定する. 値の種類は, 線形であるとする. また, "incrementの値"も指定可
	 * 
	 * @param minval
	 *            最小値
	 * @param maxval
	 *            最大値
	 * @param incre
	 *            刻み幅
	 */
	public ValueSlider(float minval, float maxval, float incre) {
		this.minval = minval;
		this.maxval = maxval;
		this.incre = incre;
		initval = (minval + maxval) * 0.5f;
		init();
	}

	/**
	 * 最小値, 最大値のほか, 値が対数的なのか線形なのかを選ぶことができる.
	 * 
	 * @param minval
	 *            最小値
	 * @param maxval
	 *            最大値
	 * @param valtype
	 *            値の種類; LINEARないしLOG.
	 */
	public ValueSlider(float minval, float maxval, int valtype) {
		this.minval = minval;
		this.maxval = maxval;
		this.valtype = valtype;
		initval = (minval + maxval) * 0.5f;
		init();
	}

	/**
	 * このコンストラクターを利用すると, 整数でいろいろ制御するようになる. 整数なら多分LINEARだろう.
	 * 
	 * @param minval
	 *            最小値
	 * @param maxval
	 *            最大値
	 */
	public ValueSlider(int minval, int maxval) {
		this.minval = (float) minval;
		this.maxval = (float) maxval;
		initval = (float) (minval + maxval) * 0.5f;
		this.valprecision = INT;
		init();
	}

	/**
	 * このコンストラクターを利用すると, 整数でいろいろ制御するようになる. 整数なら多分LINEARだろう. 初期値も選択できる.
	 * 
	 * @param minval
	 *            最小値
	 * @param maxval
	 *            最大値
	 * @param initval
	 *            初期値
	 */
	public ValueSlider(int minval, int maxval, int initval) {
		this.minval = (float) minval;
		this.maxval = (float) maxval;
		this.initval = (float) initval;
		this.valprecision = INT;
		init();
	}

	public void addValueSliderListener(ValueSliderListener list) {
		if (listeners == null) {
			listeners = new Vector();
		}
		listeners.addElement(list);
	}

	public void removeValueSliderListener(ValueSliderListener list) {
		if (listeners == null) {
			return;
		}
		listeners.remove(list);
	}

	public ValueSliderListener[] getValueSliderListener() {
		if (listeners == null) {
			return null;
		}
		ValueSliderListener[] list = new ValueSliderListener[listeners.size()];
		listeners.copyInto(list);
		return list;
	}

	private void notifyListeners() {
		if (listeners == null || listeners.size() == 0) {
			return;
		}
		for (int i = 0; i < listeners.size(); i++) {
			((ValueSliderListener) listeners.get(i)).valueSliderValueChanged();
		}
	}

	private TitledBorder titledBorder;
	private float factor;
	private float positiveFloat = 1f;

	private void genFactor() {
		float ma = (float) (jSlider.getMaximum() - jSlider.getMinimum());
		if (valtype == LOG) {
			double lmin = Math.log((double) positiveFloat);
			double lmax = Math.log((double) maxval - minval + positiveFloat);
			factor = (float) ((lmax - lmin) / ma);
		} else {
			factor = (maxval - minval) / ma;
		}
	}

	private float getVal() {
		float val = 0f;
		float ma = (float) (jSlider.getMaximum() - jSlider.getMinimum());
		if (valtype == LOG) {
			double scaled = ((double) jSlider.getValue() / ma);
			val = (float) BaseColorMap.getValueFromLogScaledValue((double) minval, (double) maxval, scaled);
			// double lmin = Math.log((double)positiveFloat);
			// float tmpval = jSlider.getValue() * factor + (float)lmin;
			// val = (float)Math.exp((double)tmpval)+minval-positiveFloat;
		} else {
			val = jSlider.getValue() * factor + minval;
		}
		return val;
	}

	private int getIntVal(float val) {
		if (val > maxval) {
			val = maxval;
		}
		if (val < minval) {
			val = minval;
		}
		if (valprecision == INT) {
			return (int) val;
		}

		int ival = 0;
		if (valtype == LOG) {
			// float tmpfloat = val-(minval-positiveFloat);
			// double tmplog = Math.log((double)tmpfloat);
			// ival = (int)
			// Math.round((tmplog-Math.log((double)positiveFloat))/factor);
			double tmp = BaseColorMap.getLogScaledValue(minval, maxval, val);
			ival = (int) Math.round(tmp * ((double) (jSlider.getMaximum() - jSlider.getMinimum())));
		} else {
			ival = Math.round((val - minval) * ((jSlider.getMaximum() - jSlider.getMinimum())) / (maxval - minval));
		}
		return ival;
	}

	/**
	 * このGUIが持っているJSliderのminor tick spacingをセットする.
	 * 
	 * @param n
	 *            JSliderのminor tick spacing
	 */
	public void setMinorTickSpacing(int n) {
		if (jSlider == null)
			return;
		jSlider.setMinorTickSpacing(n);
	}

	/**
	 * このGUIが持っているJSliderのmajor tick spacingをセットする.
	 * 
	 * @param n
	 *            JSliderのmajor tick spacing
	 */
	public void setMajorTickSpacing(int n) {
		if (jSlider == null)
			return;
		jSlider.setMajorTickSpacing(n);
	}

	public static int VERTICAL = 0;
	public static int HORIZONTAL = 1;

	/**
	 * テキストフィールドとJSliderの位置関係を設定する. デフォルトはVERTICAL; HORIZONTALにすれば横向き
	 * 
	 * @param orientation
	 *            VERTICALで縦, HORIZONTALで横にテキストフィールドとスライダーを並べる.
	 */
	public void setOrientation(int orientation) {
		if (orientation == VERTICAL) {
			panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
		} else if (orientation == HORIZONTAL) {
			panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
		}
	}

	/**
	 * このクラスに関連付けられたJSliderを取得する
	 * 
	 * @return 上記の通り
	 */
	public JSlider getSlider() {
		return this.jSlider;
	}

	private JPanel panel;

	private void init() {
		titledBorder = new TitledBorder("value");
		setBorder(titledBorder);

		if (valprecision == FLOAT) {
			int min = 0;
			int max = 100;
			if (incre > 0) {
				max = (int) ((maxval - minval) / incre);
			}
			jSlider = new JSlider(min, max);
		} else {
			jSlider = new JSlider((int) minval, (int) maxval);
			jSlider.setMinorTickSpacing(1);
		}
		int ima = jSlider.getMaximum();
		genFactor();

		if (valprecision == FLOAT) {
			minLabel = new JLabel(String.valueOf(minval));
			maxLabel = new JLabel(String.valueOf(maxval));
		} else {
			minLabel = new JLabel(String.valueOf((int) minval));
			maxLabel = new JLabel(String.valueOf((int) maxval));
		}
		JPanel sliderPanel = new JPanel();
		sliderPanel.setLayout(new GridBagLayout());
		GridBagConstraints gc = new GridBagConstraints();

		gc.anchor = GridBagConstraints.WEST;
		gc.fill = GridBagConstraints.NONE;
		gc.gridx = 0;
		gc.gridy = 0;
		gc.gridwidth = 1;
		sliderPanel.add(minLabel, gc);

		gc.anchor = GridBagConstraints.CENTER;
		gc.fill = GridBagConstraints.REMAINDER;
		gc.gridx = 0;
		gc.gridy = 1;
		gc.gridwidth = 3;
		sliderPanel.add(jSlider, gc);

		gc.gridx = 3;
		gc.gridy = 0;
		gc.anchor = GridBagConstraints.EAST;
		gc.gridwidth = 1;
		sliderPanel.add(maxLabel, gc);

		textField = new JTextField(10);
		if (valprecision == FLOAT) {
			textField.setText(String.valueOf(initval));
		} else {
			textField.setText(String.valueOf((int) initval));
		}
		JPanel textPanel = new JPanel();
		textPanel.add(textField);

		panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
		panel.add(textPanel);
		panel.add(sliderPanel);

		add(panel);

		jSlider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				CaretListener[] listeners = textField.getCaretListeners();
				if (listeners != null) {
					for (int i = 0; i < listeners.length; i++) {
						textField.removeCaretListener(listeners[i]);
					}
				}
				if (valprecision == FLOAT) {
					float val = getVal();
					textField.setText(String.valueOf(val));
				} else {
					int val = getValueInt();
					textField.setText(String.valueOf(val));
				}
				notifyListeners();
				if (listeners != null) {
					for (int i = 0; i < listeners.length; i++) {
						textField.addCaretListener(listeners[i]);
					}
				}
			}
		});

		textField.addCaretListener(new CaretListener() {
			public void caretUpdate(CaretEvent ce) {
				ChangeListener[] listeners = jSlider.getChangeListeners();
				if (listeners != null) {
					for (int i = 0; i < listeners.length; i++) {
						jSlider.removeChangeListener(listeners[i]);
					}
				}
				try {
					float val = Float.parseFloat(textField.getText().trim());
					if (val > maxval) {
						val = maxval;
					}
					if (val < minval) {
						val = minval;
					}
					int ival = getIntVal(val);
					jSlider.setValue(ival);
				} catch (Exception exc) {
				}
				notifyListeners();
				if (listeners != null) {
					for (int i = 0; i < listeners.length; i++) {
						jSlider.addChangeListener(listeners[i]);
					}
				}
			}
		});

		textField.setText(textField.getText());
	}

	public float[] getMinMax() {
		return new float[] { minval, maxval };
	}

	public void setMinMax(float minval, float maxval) {
		this.minval = minval;
		this.maxval = maxval;
		this.valprecision = FLOAT;
		genFactor();
		minLabel.setText(String.valueOf(minval));
		maxLabel.setText(String.valueOf(maxval));
		textField.setText(textField.getText());
	}

	public void setMinMax(int minval, int maxval) {
		this.minval = (float) minval;
		this.maxval = (float) maxval;
		this.valprecision = INT;
		genFactor();
		minLabel.setText(String.valueOf(minval));
		maxLabel.setText(String.valueOf(maxval));
		try {
			textField.setText(String.valueOf((int) Float.parseFloat(textField.getText())));
		} catch (Exception exc) {
		}
	}

	public void setValue(float val) {
		try {
			textField.setText(String.valueOf(val));
		} catch (IllegalStateException e) {
		}
	}

	public void setValueInt(int val) {
		// textField.setText(String.valueOf((int)val));
		try {
			jSlider.setValue(val);
		} catch (IllegalStateException e) {
		}
	}

	public float getValue() {
		float ret = 0f;
		try {
			ret = Float.parseFloat(textField.getText());
		} catch (Exception exc) {
			ret = getVal();
		}
		return ret;
	}

	public int getValueInt() {
		return jSlider.getValue();
	}

	public void setTitleForBorder(String ti) {
		titledBorder.setTitle(ti);
		repaint();
	}

}
