/*
!=======================================================================
!
!  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 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();
    }

}
