package charactermanaj.model;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;

import javax.swing.event.EventListenerList;

/**
 * 監視可能なリスト
 *
 * @param <E>
 */
public class ObservableList<E> extends AbstractList<E> {

	/**
	 * 要素がリストに追加または削除されるときに呼び出されるフック
	 * @param <E>
	 */
	public interface Hook<E> {

		/**
		 * リストに追加された場合
		 * @param item
		 */
		void add(E item);

		/**
		 * リストから除去された場合
		 * @param item
		 */
		void remove(E item);
	}

	/**
	 * リストの実体
	 */
	private final List<E> parent;

	/**
	 * フック
	 */
	private Hook<E> hook;

	/**
	 * イベントリスナのリスト
	 */
	private final EventListenerList listeners = new EventListenerList();

	/**
	 * 空のArrayListを使って構築する
	 */
	public ObservableList() {
		this(new ArrayList<E>());
	}

	/**
	 * 指定したリストを使って構築する
	 * @param parent 元となるリスト
	 */
	public ObservableList(List<E> parent) {
		if (parent == null) {
			throw new NullPointerException();
		}
		this.parent = parent;
	}

	/**
	 * 指定したリストを使って構築した監視可能リストを返す
	 * @param parent 元となるリスト
	 * @return 監視可能リスト
	 */
	public static <E> ObservableList<E> wrap(List<E> parent) {
		return new ObservableList<E>(parent);
	}

	/**
	 * 現在のフックを取得する。なればnull
	 * @return
	 */
	public Hook<E> getHook() {
		return hook;
	}

	/**
	 * フックを設定する。解除する場合はnull
	 * @param hook
	 */
	public void setHook(Hook<E> hook) {
		this.hook = hook;
	}

	/**
	 * 元となるリストを取得する
	 * @return 元となるリスト
	 */
	public List<E> unwrap() {
		return parent;
	}

	/**
	 * 変更通知リスナを登録する
	 * @param l リスナ
	 */
	public void addListChangeListener(ListChangeListener<E> l) {
		listeners.add(ListChangeListener.class, l);
	}

	/**
	 * 変更通知リスナを登録解除する
	 * @param l リスナ
	 */
	public void removeListChangeListener(ListChangeListener<E> l) {
		listeners.remove(ListChangeListener.class, l);
	}

	/**
	 * 全ての変更通知リスナに対して変更イベントを通知する。
	 * @param type 変更タイプ
	 * @param index リストのインデックス
	 */
	@SuppressWarnings("unchecked")
	public void fireEvent(ListChangeListener.ChangeType type, int index, E oldValue, E newValue) {
		// Guaranteed to return a non-null array
		Object[] ll = listeners.getListenerList();
		// Process the listeners last to first, notifying
		// those that are interested in this event
		// ※ 逆順で通知するのがSwingの作法らしい。
		ListChangeListener.Change<E> event = null;
		for (int i = ll.length - 2; i >= 0; i -= 2) {
			if (ll[i] == ListChangeListener.class) {
				// Lazily create the event:
				if (event == null) {
					event = new ListChangeListener.Change<E>(this, type, index, oldValue, newValue);
				}
				((ListChangeListener<E>) ll[i + 1]).onChanged(event);
			}
		}
	}

	@Override
	public E get(int index) {
		return parent.get(index);
	}

	@Override
	public int size() {
		return parent.size();
	}

	@Override
	public E set(int index, E element) {
		E ret = parent.set(index, element);
		fireEvent(ListChangeListener.ChangeType.MODIFIY, index, ret, element);
		if (hook != null) {
			if (ret != null && !ret.equals(element)) {
				hook.remove(ret);
			}
			if (element != null && !element.equals(ret)) {
				hook.add(element);
			}
		}
		return ret;
	}

	public void setAll(List<E> items) {
		clear();
		if (items != null) {
			addAll(items);
		}
	}

	@Override
	public void add(int index, E element) {
		parent.add(index, element);
		if (element != null && hook != null) {
			hook.add(element);
		}
		fireEvent(ListChangeListener.ChangeType.ADD, index, null, element);
	}

	@Override
	public E remove(int index) {
		E ret = parent.remove(index);
		fireEvent(ListChangeListener.ChangeType.REMOVE, index, ret, null);
		if (ret != null && hook != null) {
			hook.remove(ret);
		}
		return ret;
	}
}