/*
 * Copyright 2009-2015 Jose Luis Martin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jdal.vaadin.data;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdal.beans.PropertyUtils;
import org.springframework.beans.BeanUtils;

import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Container.PropertySetChangeNotifier;
import com.vaadin.data.Container.Sortable;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.AbstractContainer;
import com.vaadin.data.util.DefaultItemSorter;
import com.vaadin.data.util.ItemSorter;

/**
 * Bean container that uses a Integer as itemId and {@link BeanWrapperItem} as Item.
 * Hold data in a List.
 * 
 * @author Jose Luis Martin
 * @since 2.1
 */
public class ListBeanContainer extends AbstractContainer implements Indexed, Sortable,
		ItemSetChangeNotifier, PropertySetChangeNotifier {
	
	/** Hold all beans in container */
	private transient List<Item> items = new ArrayList<Item>();
	/** Bean type */
	private Class<?> beanClass;
	/** Bean properties used by container */
	private List<String> properties = new ArrayList<String>();
	private Map<String, PropertyDescriptor> propertyDescriptors = 
			new HashMap<String, PropertyDescriptor>();

	public ListBeanContainer(Class<?> beanClass) {
		this(beanClass, null, null);
	}
	
	public ListBeanContainer(Class<?> beanClass, Collection<?> data) {
		this(beanClass, data, null);
	}
	
	public ListBeanContainer(Class<?> beanClass, Collection<?> data,  List<String> properties) {
		this.beanClass = beanClass;
		if (properties == null) {
			initDefaultProperties();
		}
		else {
			setProperties(properties);
		}
		
		if (data != null)
			init(data);
	}

	private void setProperties(List<String> properties) {
		this.properties.clear();
		this.propertyDescriptors.clear();
		
		for (String propertyName : properties) {
			addProperty(propertyName);
		}
	
		fireContainerPropertySetChange();
	}

	private void initDefaultProperties() {
		for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(beanClass)) { 
			this.properties.add(pd.getName());
			this.propertyDescriptors.put(pd.getName(), pd);
		}
	}

	private void init(Collection<?> data) {
		for (Object bean : data) {
			this.items.add(createItem(bean));
		}
		
		fireItemSetChange();
	}

	@Override
	public Integer nextItemId(Object itemId) {
		Integer index = getAsIndex(itemId);
		return items.size() > index - 1 ? index + 1 : null;
	}

	/**
	 * Gets an itemId as Long
	 * @param itemId itemId to check
	 * @return long value
	 */
	private Integer getAsIndex(Object itemId) {
		if (itemId instanceof Integer)
			return (Integer) itemId;
		else if (itemId instanceof Long)
			return ((Long) itemId).intValue();
		
		String clazzName = itemId != null ? itemId.getClass().getName() : "null";
		
		throw new IllegalArgumentException("Expected Integer, found + [" + clazzName + "]");
	}

	@Override
	public Integer prevItemId(Object itemId) {
		Integer index = getAsIndex(itemId);
		
		return index > 1 ? index - 1 : null;
	}

	@Override
	public Integer firstItemId() {
		return items.isEmpty() ? null : 0;
	}

	@Override
	public Integer lastItemId() {
		return items.isEmpty() ? null : items.size() - 1;
	}

	@Override
	public boolean isFirstId(Object itemId) {
		return getAsIndex(itemId) == 0;
	}

	@Override
	public boolean isLastId(Object itemId) {
		return getAsIndex(itemId) == items.size() - 1;
	}

	@Override
	public Object addItemAfter(Object previousItemId)
			throws UnsupportedOperationException {
		return null;
	}

	@Override
	public Item addItemAfter(Object previousItemId, Object newItemId)
			throws UnsupportedOperationException {

		return null;
	}

	@Override
	public Item getItem(Object itemId) {
		return items.get(getAsIndex(itemId));
	}

	@Override
	public Collection<?> getContainerPropertyIds() {
		return this.properties;
	}
	
	@Override
	public Collection<?> getItemIds() {
		List<Integer> itemIds = new ArrayList<Integer>();
		
		for (int i = 0; i < this.items.size(); i++)
			itemIds.add(new Integer(i));
		
		return itemIds;
	}
	
	@Override
	@SuppressWarnings("rawtypes")
	public Property getContainerProperty(Object itemId, Object propertyId) {
		int index = getAsIndex(itemId);
		
		if (index > this.items.size() - 1)
			return null;
		
		return this.items.get(index).getItemProperty(propertyId);
	}

	@Override
	public Class<?> getType(Object propertyId) {
		return getPropertyDescriptor(propertyId).getPropertyType();

	}

	private PropertyDescriptor getPropertyDescriptor(Object propertyId) {
		return propertyDescriptors.get(propertyId);
	}

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

	@Override
	public boolean containsId(Object itemId) {
		if (this.items.isEmpty())
			return false;
		
		int index = getAsIndex(itemId);
		
		return index >= 0  && index < this.items.size();
	}

	@Override
	public Item addItem(Object itemId) throws UnsupportedOperationException {
		Item item = createItem(itemId);
		if (this.items.add(item)) {
			fireItemSetChange();
			return item;
		}
		
		return null;
	}

	@Override
	public Object addItem() throws UnsupportedOperationException {
		if (this.items.add(createItem(newBean()))) {
			fireItemSetChange();
			return this.items.size() - 1;
		}
		
		return null;
	}

	/**
	 * Create item from bean.
	 * @param bean bean for item
	 * @return new item.
	 */
	protected Item createItem(Object bean) {
		return new BeanWrapperItem(bean, this.properties);
	}

	private Object newBean() {
		return BeanUtils.instantiate(this.beanClass);
	}

	@Override
	public boolean removeItem(Object itemId)
			throws UnsupportedOperationException {
		
		boolean result = this.items.remove(getAsIndex(itemId));
		fireItemSetChange();
		
		return result;
	}

	@Override
	public boolean addContainerProperty(Object propertyId, Class<?> type,
			Object defaultValue) throws UnsupportedOperationException {
		
		if (!this.properties.contains((String) propertyId)) {
			addProperty((String) propertyId);
			
			return true;
		}
		
		return false;
	}

	@Override
	public boolean removeContainerProperty(Object propertyId)
			throws UnsupportedOperationException {
		this.properties.remove(propertyId);
		this.propertyDescriptors.remove(propertyId);
		
		return true;
	}

	@Override
	public boolean removeAllItems() throws UnsupportedOperationException {
		this.items.clear();
		fireItemSetChange();
		
		return true;
	}

	@Override
	public int indexOfId(Object itemId) {
		return getAsIndex(itemId);
	}

	@Override
	public Object getIdByIndex(int index) {
		return index;
	}

	@Override
	public List<?> getItemIds(int startIndex, int numberOfItems) {
		List<Integer> indexes = new ArrayList<Integer>();
		for (int i = startIndex; i < startIndex + numberOfItems;  i++)
			indexes.add(i);
		
		return indexes;
	}

	@Override
	public Object addItemAt(int index) throws UnsupportedOperationException {
		throw new UnsupportedOperationException(); 
	}

	@Override
	public Item addItemAt(int index, Object newItemId)
			throws UnsupportedOperationException {
		throw new UnsupportedOperationException();
	}
	
	public void addAll(Collection<?> data) {
		init(data);
	}

	public void addProperty(String name) {
		this.properties.add(name);
		this.propertyDescriptors.put(name, 
				PropertyUtils.getPropertyDescriptor(this.beanClass, name));
	}

	@Override
	public void addPropertySetChangeListener(
			PropertySetChangeListener listener) {

		super.addPropertySetChangeListener(listener);
	}

	@Override
	public void addItemSetChangeListener(ItemSetChangeListener listener) {
		super.addItemSetChangeListener(listener);
	}

	@Override
	public void addListener(PropertySetChangeListener listener) {
		super.addListener(listener);
	}

	@Override
	public void addListener(ItemSetChangeListener listener) {
		super.addListener(listener);
	}

	@Override
	public void removePropertySetChangeListener(
			PropertySetChangeListener listener) {
		super.removePropertySetChangeListener(listener);
	}

	@Override
	public void removeListener(PropertySetChangeListener listener) {
		super.removeListener(listener);
	}

	@Override
	public void removeItemSetChangeListener(ItemSetChangeListener listener) {
		super.removeItemSetChangeListener(listener);
	}

	@Override
	public void removeListener(ItemSetChangeListener listener) {
		super.removeListener(listener);
	}

	@Override
	public void sort(Object[] propertyId, boolean[] ascending) {
		ItemSorter itemSorter = new DefaultItemSorter();
        itemSorter.setSortProperties((Sortable) this, propertyId,ascending);
        Collections.sort(this.items, itemSorter);
	}

	@Override
	public Collection<?> getSortableContainerPropertyIds() {
		return this.properties;
	}

}