package com.ix.adpoint.gui.model;

import java.awt.EventQueue;
import java.util.*;
import javax.annotation.Nullable;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import com.ix.shared.edt.EDTSafe;
import com.ix.shared.util.CollectionUtils;

/** Only allows for single selection model */
public abstract class ComboBoxModelExtended<I> extends AbstractListModel implements ComboBoxModel {
	private final ArrayList<I> list = new ArrayList<I>();
	protected I selected;
	private final boolean firstItemShouldBeNull;
	private boolean updateInProgress;

	public ComboBoxModelExtended(boolean firstItemShouldBeNull) {
		this.firstItemShouldBeNull = firstItemShouldBeNull;
	}

	public final I getElementAt(int index) {
		return list.get(index);
	}

	public final int getSize() {
		return list.size();
	}

	public final I getSelectedItem() {
		return selected;
	}

	@SuppressWarnings({"unchecked", "null"})
	/** Ensure in a subclass the type safety */
	public void setSelectedItem(Object item) {
		assert item == null || getItemClass() == null
			|| getItemClass().isAssignableFrom(item.getClass()) : getClass().getSimpleName()
			+ " requires " + getItemClass().getSimpleName() + ", but got: " + item
			+ (item == null? "": "  (" + item.getClass().getName() + ")");

		if (item == null)
			selected = null;
		else if (item.equals(selected)) {/**/
		} else if (list.contains(item)) {
			selected = (I) item;
			fireContentsChanged(this, -1, -1);
		}
		//	normal in editable models:
		//	else System.err.println("The model does not contains object: " + item + "  (" + list + ")");
	}

	/** Override to force checking when selecting an item */
	protected Class<I> getItemClass() {
		return null;
	}

	/** Empties the list */
	public final void clear() {
		selected = null;
		if (list.size() > 0 || list.size() > 1 && firstItemShouldBeNull) {
			int firstIndex = 0;
			int lastIndex = list.size() - 1;
			list.clear();
			if (firstItemShouldBeNull)
				list.add(null);
			fireIntervalRemoved(this, firstIndex, lastIndex);
		}
	}

	/**
	 * Returns the index-position of the specified object in the list.
	 * @return an int representing the index position, where 0 is the first position
	 */
	public final int getIndexOf(I item) {
		return list.indexOf(item);
	}

	public final void addElement(I item) {
		list.add(item);
		fireIntervalAdded(this, list.size() - 1, list.size() - 1);
		if (list.size() == 1 && selected == null && item != null)
			setSelectedItem(item);
	}

	public final void addAll(Collection<I> items) {
		if (items == null || items.isEmpty())
			return;
		list.addAll(items);
		fireIntervalAdded(this, list.size() - items.size(), list.size() - 1);
		if (list.size() == items.size() && selected == null)
			for(I item : items)
				if (item != null) {
					setSelectedItem(item);
					break;
				}
	}

	public final void insert_MoveAllTo(List<I> items, int index) {
		if (CollectionUtils.containsAny(list, items)) {
			for(int i = 0; i < index; i++) {
				I item = list.get(i);
				if (item != null && items.contains(item))
					index--;
			}
			list.removeAll(items);
		}
		list.addAll(index, items);
		fireContentsChanged(this, 0, list.size() - 1);
	}

	//	public final void insertElementAt(I item, int index) {
	//		list.insertElementAt(item, index);
	//		fireIntervalAdded(this, index, index);
	//	}

	@Override
	protected void fireIntervalRemoved(Object source, int index0, int index1) {
		if (!updateInProgress)
			super.fireIntervalRemoved(source, index0, index1);
	}

	public final void removeElementAt(int index) {
		if (getElementAt(index) == selected)
			if (index == 0)
				setSelectedItem(getSize() == 1? null: getElementAt(index + 1));
			else
				setSelectedItem(getElementAt(index - 1));
		list.remove(index);
		if (list.isEmpty() && firstItemShouldBeNull)
			list.add(null);
		fireIntervalRemoved(this, index, index);
	}

	public final void removeAll(int[] indices) {
		updateInProgress = true;
		for(int index : indices)
			removeElementAt(index);
		updateInProgress = false;
		fireIntervalRemoved(this, indices[0], indices[indices.length - 1]);
	}

	// implements javax.swing.MutableComboBoxModel
	public final void removeElement(I item) {
		int index = list.indexOf(item);
		if (index != -1)
			removeElementAt(index);
	}

	public final void removeAll(Collection<I> items) {
		updateInProgress = true;
		for(I item : items)
			removeElement(item);
		updateInProgress = false;
		fireIntervalRemoved(this, 0, getSize());
	}

	public final List<I> getData() {
		return Collections.unmodifiableList(list);
	}

	@EDTSafe
	protected final List<I> setData_intern(Collection<I> datas) {
		synchronized(list) {
			list.clear();
			if (firstItemShouldBeNull)
				list.add(null);
			list.addAll(datas);
		}
		if (!list.contains(selected))
			selected = null;
		return list;
	}

	@EDTSafe
	public final void setData(@Nullable Collection<I> datas) {
		if (datas == null)
			clear();
		else {
			setData_intern(datas);
			if (EventQueue.isDispatchThread())
				fireIntervalAdded(this, 0, getSize());
			else
				EventQueue.invokeLater(new Runnable() {
					@Override
					public void run() {
						fireIntervalAdded(this, 0, getSize());
					}
				});
		}
	}
}