/*
 * The MIT License
 *
 * Copyright 2013 Dra0211.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package kinugasa.ui;

import java.awt.Graphics2D;
import kinugasa.util.TimeCounter;

/**
 * 1つ以上の選択肢から内容を選ばせることができるメッセージウインドウの拡張クラスです.
 * <br>
 * 選択メッセージウインドウは1つの質問文と0こ以上の選択肢を持ちます。<br>
 * 質問文は改行"\n"を利用可能です。選択肢は改行できません。<br>
 * 選択中の選択肢を指し示すにはCHoiceEffectクラスを使用します。<br>
 * ChoiceEffectはメッセージウインドウ内のラベルの選択中インデックスの要素に適用されます。<br>
 * 選択された選択肢は選択肢のテキストやラベル配列のインデックスを取得することで
 * 区別することができます。<br>
 * <br>
 * このメッセージウインドウは選択エフェクトがnullの場合は、選択エフェクトを表示しません。<br>
 * したがって、選択肢のない普通のメッセージであっても表示可能です。<br>
 * これを利用して、途中で選択肢を与える会話イベントをこの選択メッセージウインドウを使用して
 * 作成することで、ウインドウを切り替えずに選択イベントを発生させることができます。<br>
 * <br>
 * このウインドウの実装では、すべてのラベルが表示されるまで選択エフェクトが描画されません。<br>
 * これは一部のメソッドによる選択された値を取得に影響します。<br>
 * <br>
 *
 * @version 1.0.0 - 2012/11/23_21:19:46.<br>
 * @author Dra0211<br>
 * <br>
 */
public class SelectableMessageWindow extends MessageWindow {

	private static final long serialVersionUID = 1982543255115530212L;

	/**
	 * 新しい選択メッセージウインドウを作成します.
	 * インデックスは配列のインデックスです。通常は、質問文が0番目となり、初期値は1、最少も1、最大は
	 * 選択肢の数になります。<br>
	 * ただし、選択肢のインデックスは質問文の行数の影響を受けます。質問文が2行ある場合は初期値は2となります。<br>
	 * このコンストラクタではラベルの表示遅延時間は0に設定されます。したがって、すべてのラベルが最初から表示されます。<br>
	 *
	 * @param x            ウインドウのX座標を指定します。<br>
	 * @param y            ウインドウのY座標を指定します。<br>
	 * @param width        ウインドウの幅を指定します。<br>
	 * @param height       ウインドウの高さを指定します。<br>
	 * @param wm           ウインドウの表示モデルを指定します。<Br>
	 * @param lm           ウインドウ内のラベルに適用されるラベルモデルを指定します。<br>
	 * @param text         質問文です。改行"\n"を利用できます。<br>
	 * @param initialIdx   最初に選択されている選択肢のインデックスを指定します。<br>
	 * @param minIdx       選択可能なインデックスの最小値を指定します。<br>
	 * @param maxIdx       選択可能なインデックスの最小値を指定します。<br>
	 * @param choiceEffect 選択エフェクトを指定します。nullを許容します。<br>
	 *
	 * @throws NullPointerException     テキストがnullである場合に投げられます。<br>
	 * @throws IllegalArgumentException minIdx &gt; maxIdxのときに投げられます。<br>
	 */
	public SelectableMessageWindow(float x, float y, float width, float height,
			MessageWindowModel wm, LabelModel lm, String text,
			int initialIdx, int minIdx, int maxIdx, ChoiceEffect choiceEffect)
			throws NullPointerException, IllegalArgumentException {
		super(x, y, width, height, wm, lm, text);
		if (minIdx > maxIdx) {
			throw new IllegalArgumentException("! > SelectableMessageWindow : minIdx > maxIdx : min=[" + minIdx + "] max=[" + maxIdx + "]");
		}
		this.selectedIndex = initialIdx;
		this.minIndex = minIdx;
		this.maxIndex = maxIdx;
		this.choiceEffect = choiceEffect;
		select(selectedIndex);
	}

	/**
	 * 新しい選択メッセージウインドウを作成します.
	 * インデックスは配列のインデックスです。通常は、質問文が0番目となり、初期値は1、最少も1、最大は
	 * 選択肢の数になります。<br>
	 * ただし、選択肢のインデックスは質問文の行数の影響を受けます。質問文が2行ある場合は初期値は2となります。<br>
	 *
	 * @param x            ウインドウのX座標を指定します。<br>
	 * @param y            ウインドウのY座標を指定します。<br>
	 * @param width        ウインドウの幅を指定します。<br>
	 * @param height       ウインドウの高さを指定します。<br>
	 * @param wm           ウインドウの表示モデルを指定します。<Br>
	 * @param lm           ウインドウ内のラベルに適用されるラベルモデルを指定します。<br>
	 * @param tc           ラベルに適用するタイムカウンタを指定します。実際はクローンが使用されます。<br>
	 * @param text         質問文です。改行"\n"を利用できます。<br>
	 * @param initialIdx   最初に選択されている選択肢のインデックスを指定します。<br>
	 * @param minIdx       選択可能なインデックスの最小値を指定します。<br>
	 * @param maxIdx       選択可能なインデックスの最小値を指定します。<br>
	 * @param choiceEffect 選択エフェクトを指定します。nullを許容します。<br>
	 *
	 * @throws NullPointerException     テキストがnullである場合に投げられます。<br>
	 * @throws IllegalArgumentException minIdx &gt; maxIdxのときに投げられます。<br>
	 */
	public SelectableMessageWindow(float x, float y, float width, float height,
			MessageWindowModel wm, LabelModel lm, TimeCounter tc, String text,
			int initialIdx, int minIdx, int maxIdx, ChoiceEffect choiceEffect)
			throws NullPointerException, IllegalArgumentException {
		super(x, y, width, height, wm, lm, tc, text);
		if (minIdx > maxIdx) {
			throw new IllegalArgumentException("! > SelectableMessageWindow : minIdx > maxIdx : min=[" + minIdx + "] max=[" + maxIdx + "]");
		}
		this.selectedIndex = initialIdx;
		this.minIndex = minIdx;
		this.maxIndex = maxIdx;
		this.choiceEffect = choiceEffect;
		select(selectedIndex);
	}
	/** この選択メッセージウインドウが使用する選択エフェクトです. */
	private ChoiceEffect choiceEffect;
	/** 現在選択中のラベルのインデックスです. */
	private int selectedIndex = 0;
	/** 選択可能な最少のインデックスです. */
	private int minIndex;
	/** 選択可能な最大のインデックスです. */
	private int maxIndex;

	/**
	 * 選択可能な最少のインデックスを取得します.
	 *
	 * @return 選択可能な最少のインデックスを返します。<br>
	 */
	public int getMinIndex() {
		return minIndex;
	}

	/**
	 * 選択可能な最少のインデックスを設定します.
	 *
	 * @param minIndex 選択可能な最少のインデックスを返します。<br>
	 */
	public void setMinIndex(int minIndex) {
		this.minIndex = minIndex;
	}

	/**
	 * 選択可能な最大のインデックスを取得します.
	 *
	 * @return 選択可能な最大のインデックスを返します。<br>
	 */
	public int getMaxIndex() {
		return maxIndex;
	}

	/**
	 * 選択可能な最大のインデックスを設定します.
	 *
	 * @param maxIndex 選択可能な最大のインデックスを返します。<br>
	 */
	public void setMaxIndex(int maxIndex) {
		this.maxIndex = maxIndex;
	}

	/**
	 * この選択メッセージウインドウに新しい選択エフェクトを設定します.
	 *
	 * @param choiceEffect 選択エフェクトの実装を送信します。nullを許容します。<br>
	 */
	public void setChoiceEffect(ChoiceEffect choiceEffect) {
		this.choiceEffect = choiceEffect;
	}

	/**
	 * この選択メッセージウインドウに設定されているエフェクトを取得します.
	 *
	 * @return 設定されている選択エフェクトの実装を返します。設定されていない場合は
	 *            nullが設定されている場合は、nullをを返します。<br>
	 */
	public ChoiceEffect getChoiceEffect() {
		return choiceEffect;
	}

	/**
	 * 選択中の選択肢を1つ次の選択肢へと移します.
	 * このメソッドは現在選択されている選択肢が最後の(最大のインデックスを持つ)選択肢である場合は
	 * 何も行いません。<br>
	 */
	public void nextChoice() {
		selectedIndex++;
		clamp();
	}

	/**
	 * 選択中の選択肢を1つ次の選択肢へと移します.
	 * このメソッドは現在選択されている選択肢が最後の(最大のインデックスを持つ)選択肢である場合は
	 * 最初の(最少のインデックスを持つ)選択肢を選択します。<br>
	 */
	public void nextChoiceLoop() {
		if (selectedIndex == maxIndex) {
			selectedIndex = minIndex;
		} else {
			nextChoice();
		}
	}

	/**
	 * 選択中の選択肢を1つ前の選択肢へと移します.
	 * このメソッドは現在選択されている選択肢が最初の(最少のインデックスを持つ)選択肢である場合は
	 * 何も行いません。<br>
	 */
	public void prevChoice() {
		selectedIndex--;
		clamp();
	}

	/**
	 * 選択中の選択肢を1つ前の選択肢へと移します.
	 * このメソッドは現在選択されている選択肢が最初の(最少のインデックスを持つ)選択肢である場合は
	 * 最後の(最大のインデックスを持つ)選択肢を選択します。<br>
	 */
	public void prevChoiceLoop() {
		if (selectedIndex == minIndex) {
			selectedIndex = maxIndex;
		} else {
			prevChoice();
		}
	}

	/**
	 * あるインデックスの選択肢を選択状態にします.
	 *
	 * @param idx 選択するインデックスを送信します。<br>
	 *
	 * @throws IllegalArgumentException idx &gt;= labels.length || minIndex &gt; idx || maxIndex &lt; idxのときに投げられます。<br>
	 */
	public final void select(int idx) throws IllegalArgumentException {
		if (idx >= labels.length || minIndex > idx || maxIndex < idx) {
			throw new IllegalArgumentException("! > SelectableMessageWindow : select :  idx >= labels.length || minIndex > idx || maxIndex < idx : idx=["
					+ idx + "] labelsLength=[" + labels.length + "] min=[" + minIndex + "] max=[" + maxIndex + "]");
		}
		this.selectedIndex = idx;
		clamp();
	}

	/**
	 * インデックスを有効範囲内に切り詰めます.
	 */
	protected final void clamp() {
		if (selectedIndex > maxIndex || selectedIndex > labels.length - 1) {
			selectedIndex = maxIndex;
		} else if (selectedIndex < minIndex || selectedIndex < 0) {
			selectedIndex = minIndex;
		}
	}

	/**
	 * 現在選択されているラベルを取得します.
	 *
	 * @return 選択中のラベルを返します。全てのラベルが表示されていない場合(!isAllTextVisible()のとき)
	 *            はnullを返します。<br>
	 */
	public TextLabel getSelectedLabel() {
		if (!isAllTextVisible()) {
			return null;
		}
		return labels[selectedIndex];
	}
	/** 全てのラベルが表示されていないために、何も選択されていない状態を表すインデックスの定数です. */
	public static final int NOT_YET_SELECTED = -1;

	/**
	 * 現在選択されているラベルのインデックスを取得します.
	 *
	 * @return 選択中のラベルのインデックスを返します。全てのラベルが表示されていない場合(!isAllTextVisible()のとき)
	 *            はNOT_YET_SELECTEDを返します。<br>
	 */
	public int getSelectedIndex() {
		if (!isAllTextVisible()) {
			return NOT_YET_SELECTED;
		}
		return selectedIndex;
	}

	@Override
	public void draw(Graphics2D g) {
		super.draw(g);
		if (isAllTextVisible() && choiceEffect != null) {
			choiceEffect.draw(g, getSelectedLabel());
		}
	}

	@Override
	public SelectableMessageWindow clone() {
		SelectableMessageWindow result = (SelectableMessageWindow) super.clone();
		result.choiceEffect = this.choiceEffect.clone();
		return result;
	}
}
