/*
 * Copyright (c) 2006-2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.chart.ui.screen;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import jp.sf.orangesignal.chart.ChartSettings;
import jp.sf.orangesignal.chart.event.ChartListener;
import jp.sf.orangesignal.chart.event.ChartScreenListener;
import jp.sf.orangesignal.chart.event.SideScreenEvent;
import jp.sf.orangesignal.chart.event.SideScreenListener;
import jp.sf.orangesignal.chart.ui.MAType;
import jp.sf.orangesignal.chart.ui.ChartScreenType;

/**
 * サイド画面の基底クラスを提供します。
 * 
 * @author 杉澤 浩二
 */
public abstract class SideScreen extends JPanel implements ChartListener, ChartScreenListener, ActionListener, ItemListener, ChangeListener {

	private static final long serialVersionUID = 2348198897483327747L;

	public static final int SIDE_SCREEN_BASE_WIDTH = 190;
	public static final int SIDE_SCREEN_WIDTH = SIDE_SCREEN_BASE_WIDTH + 13 * 2;

	/**
	 * サイド画面リスナーのリストを保持します。
	 */
	protected final List<SideScreenListener> listeners = new ArrayList<SideScreenListener>(1);

	/**
	 * イベントを通知するかどうかを保持します。
	 * 画面コントロールなどの初期化中にイベント通知を無効にしたい場合は <code>false</code> に設定します。
	 */
	protected boolean notify = false;

	/**
	 * 指定されたサイド画面リスナーをリストへ追加します。
	 * 
	 * @param listener サイド画面リスナー
	 */
	public void addSideScreenListener(final SideScreenListener listener) {
		this.listeners.add(listener);
	}

	/**
	 * サイド画面リスナーの設定変更イベントを通知します。
	 * サブクラスはイベントを通知する必要がある場合このメソッドを呼び出します。
	 */
	protected final void send() {
		if (this.notify) {
			final SideScreenEvent event = new SideScreenEvent(this);
			for (final SideScreenListener listener : this.listeners)
				listener.optionChanged(event);
		}
	}

	/**
	 * この画面の種類を保持します。既定の値は <code>null</code> です。
	 */
	private ChartScreenType screenType = null;

	/**
	 * この画面の種類を返します。不明な場合は <code>null</code> を返します。
	 * 
	 * @return 画面の種類
	 */
	public final ChartScreenType getScreenType() {
		return screenType;
	}

	/**
	 * この画面の種類を設定します。
	 * 
	 * @param screenType 画面の種類
	 */
	public final void setScreenType(final ChartScreenType screenType) {
		this.screenType = screenType;
	}

	// ------------------------------------------------------------

	private static final MAType[] ma = MAType.values();

	protected static final JComboBox createMovingAverageJComboBox() { return new JComboBox(ma); }

	/**
	 * 指定されたキャンバスを画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 * @param canvas キャンバス
	 */
	protected final void addCanvas(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final Canvas canvas) {

		c.fill = GridBagConstraints.BOTH;

		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 2;
		c.weighty = 100;
		c.anchor = GridBagConstraints.NORTH;
		c.insets = new Insets(0, 0, 0, 0);
		layout.setConstraints(canvas, c);
		add(canvas);
	}

	/**
	 * 指定されたラベルを画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 * @param label ラベル
	 */
	protected final void addLabel(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final JLabel label) {

		c.fill = GridBagConstraints.NONE;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 2;
		c.weighty = 0;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(0, 0, 2, 0);	// 下 2px 空ける
		layout.setConstraints(label, c);
		add(label);
	}

	/**
	 * 指定されたチェックボックスを画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 * @param check チェックボックス
	 */
	protected final void addCheckBox(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final JCheckBox check) {

		check.setOpaque(false);
		check.addItemListener(this);

		c.fill = GridBagConstraints.NONE;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 2;
		c.weighty = 0;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(0, 0, 0, 0);
		layout.setConstraints(check, c);
		add(check);
	}

	/**
	 * 指定されたラベルとコンボボックスを画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 * @param label ラベル
	 * @param combo コンボボックス
	 */
	protected final void addComboBox(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final JLabel label,
		final JComboBox combo) {

		combo.setOpaque(false);
		combo.addActionListener(this);

		c.fill = GridBagConstraints.NONE;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 1;
		c.weighty = 0;
		c.anchor = GridBagConstraints.EAST;
		c.insets = new Insets(0, 0, 0, 4);
		layout.setConstraints(label, c);
		add(label);

		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 1;
		c.gridy = y;
		c.gridwidth = 1;
		c.weighty = 0;
		c.anchor = GridBagConstraints.WEST;
		c.insets = new Insets(1, 0, 1, 0);
		layout.setConstraints(combo, c);
		add(combo);
	}

	/**
	 * 指定されたパネルを画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 * @param panel パネル
	 */
	protected final void addPanel(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final JPanel panel) {
			
		c.fill = GridBagConstraints.NONE;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 2;
		c.weighty = 0;
		c.anchor = GridBagConstraints.EAST;
		c.insets = new Insets(0, 0, 0, 0);
		layout.setConstraints(panel, c);
		add(panel);
	}

	/**
	 * 水平線を画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 */
	protected final void addSeparator(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y) {

		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 2;
		c.weighty = 0;
		c.anchor = GridBagConstraints.CENTER;
		c.insets = new Insets(2, 0, 2, 0);

		final JSeparator separator = new JSeparator();
		separator.setPreferredSize(new Dimension(SIDE_SCREEN_BASE_WIDTH, 1));

		layout.setConstraints(separator, c);
		add(separator);
	}

	/**
	 * 自動拡張する下詰め余白を画面に追加します。
	 * 
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行
	 */
	protected final void addLastSpace(
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y) {

		final JPanel panel = new JPanel();

		c.fill = GridBagConstraints.BOTH;

		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 2;
		c.weighty = 100;
		c.anchor = GridBagConstraints.SOUTH;
		c.insets = new Insets(0, 0, 0, 0);
		layout.setConstraints(panel, c);
		add(panel);
	}

	// ------------------------------------------------------------

	/**
	 * 指定されたパネルに、コンボボックスを追加します。
	 * 
	 * @param panel パネル
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行数
	 * @param label ラベル
	 * @param combo コンボボックス
	 */
	protected final void addSubComboBox(
		final JPanel panel,
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final JLabel label,
		final JComboBox combo) {

		combo.setOpaque(false);
		combo.addActionListener(this);

		final JPanel _panel = new JPanel();
		final GridBagLayout _layout = new GridBagLayout();
		final GridBagConstraints _c = new GridBagConstraints();
		_panel.setLayout(_layout);

		_c.gridx = 0;
		_c.insets = new Insets(0, 0, 0, 4);
		_layout.setConstraints(label, _c);
		_panel.add(label);

		_c.gridx = 1;
		_layout.setConstraints(combo, _c);
		_panel.add(combo);

		c.fill = GridBagConstraints.NONE;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 1;
		c.weighty = 0;
		c.anchor = GridBagConstraints.EAST;
		c.insets = new Insets(1, 0, 1, 0);
		layout.setConstraints(_panel, c);
		panel.add(_panel);
	}

	/**
	 * 指定されたパネルに、数値入力ボックスを追加します。
	 * 
	 * @param panel パネル
	 * @param layout GridBagLayout
	 * @param c GridBagConstraints
	 * @param y 行数
	 * @param label ラベル
	 * @param spinner 数値入力ボックス
	 */
	protected final void addSubSpinner(
		final JPanel panel,
		final GridBagLayout layout,
		final GridBagConstraints c,
		final int y,
		final JLabel label,
		final JSpinner spinner) {

		spinner.setOpaque(false);
		spinner.addChangeListener(this);

		final JPanel _panel = new JPanel();
		final GridBagLayout _layout = new GridBagLayout();
		final GridBagConstraints _c = new GridBagConstraints();
		_panel.setLayout(_layout);

		// ラベルを処理します。
		_c.gridx = 0;
		_c.insets = new Insets(0, 0, 0, 4);
		_layout.setConstraints(label, _c);
		_panel.add(label);

		// 数値入力ボックスを処理します。
		spinner.setPreferredSize(new Dimension(60, 20));
		spinner.setFont(new Font("Verdana", Font.PLAIN, 10));

		_c.gridx = 1;
		_layout.setConstraints(spinner, _c);
		_panel.add(spinner);

		c.fill = GridBagConstraints.NONE;
		c.gridx = 0;
		c.gridy = y;
		c.gridwidth = 1;
		c.weighty = 0;
		c.anchor = GridBagConstraints.EAST;
		c.insets = new Insets(1, 0, 1, 0);
		layout.setConstraints(_panel, c);
		panel.add(_panel);
	}

	/**
	 * コンボボックスの選択値が変更されると呼び出されます。
	 */
	@Override public void actionPerformed(final ActionEvent e) { send(); }

	/**
	 * チェックボックスの値が変更されると呼び出されます。
	 */
	@Override public void itemStateChanged(final ItemEvent e) { send(); }

	/**
	 * スライダーまたはスピナーの値が変更されると呼び出されます。
	 */
	@Override public void stateChanged(final ChangeEvent e) { send(); }

	/**
	 * 各コントロールの状態を指定された設定情報へ設定します。
	 * 
	 * @param settings 設定情報
	 */
	public abstract void save(ChartSettings settings);

	/**
	 * 指定された設定情報から各コントロールの状態を設定します。
	 * 
	 * @param settings 設定情報
	 */
	public abstract void load(ChartSettings settings);

	/**
	 * 上級者向けオプションの表示/非表示を切り替えます。
	 * 
	 * @param visible
	 */
	public void setVisibleAdvancedOptions(final boolean visible) {};

}
