package de.marcelpociot.collectionview;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.AsyncResult;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.kroll.common.TiMessenger;
import org.appcelerator.titanium.TiApplication;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.proxy.TiViewProxy;
import org.appcelerator.titanium.util.TiConvert;
import org.appcelerator.titanium.view.TiCompositeLayout;
import org.appcelerator.titanium.view.TiUIView;

import de.marcelpociot.collectionview.CollectionView.TiBaseAdapter;
import de.marcelpociot.collectionview.CollectionViewTemplate.DataItem;
import ti.modules.titanium.ui.UIModule;
import ti.modules.titanium.ui.ViewProxy;
import ti.modules.titanium.ui.widget.TiView;
import android.app.Activity;
import android.os.Message;
import android.view.View;

@Kroll.proxy(creatableInModule = AndroidcollectionviewModule.class, propertyAccessors = {
})
public class CollectionSectionProxy extends TiViewProxy{

	private static final String TAG = "CollectionSectionProxy";
	private ArrayList<CollectionItemData> listItemData;
	private int itemCount;
	private TiBaseAdapter adapter;
	private ArrayList<Object> itemProperties;
	private ArrayList<Integer> filterIndices;
	private boolean preload;
	
	private WeakReference<CollectionView> listView;
	public DefaultCollectionViewTemplate builtInTemplate;
	
	private String headerTitle;
	private String footerTitle;
	
	private TiViewProxy headerView;
	private TiViewProxy footerView;

	
	private static final int MSG_FIRST_ID = TiViewProxy.MSG_LAST_ID + 1;

	private static final int MSG_SET_ITEMS = MSG_FIRST_ID + 700;
	private static final int MSG_APPEND_ITEMS = MSG_FIRST_ID + 701;
	private static final int MSG_INSERT_ITEMS_AT = MSG_FIRST_ID + 702;
	private static final int MSG_DELETE_ITEMS_AT = MSG_FIRST_ID + 703;
	private static final int MSG_GET_ITEM_AT = MSG_FIRST_ID + 704;
	private static final int MSG_REPLACE_ITEMS_AT = MSG_FIRST_ID + 705;
	private static final int MSG_UPDATE_ITEM_AT = MSG_FIRST_ID + 706;
	private static final int MSG_GET_ITEMS = MSG_FIRST_ID + 707;
	private static final int MSG_SET_HEADER_TITLE = MSG_FIRST_ID + 708;
	private static final int MSG_SET_FOOTER_TITLE = MSG_FIRST_ID + 709;
	private static final int MSG_SET_HEADER_VIEW = MSG_FIRST_ID + 710;
	private static final int MSG_SET_FOOTER_VIEW = MSG_FIRST_ID + 711;



	
	@Override
	public TiUIView createView(Activity activity)
	{
		TiUIView view = new TiView(this);
		view.getLayoutParams().autoFillsHeight = true;
		view.getLayoutParams().autoFillsWidth = true;
		return view;
	}
	
	
	public class CollectionItemData {
		private KrollDict properties;
		private CollectionViewTemplate template;
		private String searchableText = "";
		public CollectionItemData (KrollDict properties, CollectionViewTemplate template) {
			this.properties = properties;
			this.template = template;
			//set searchableText
			if (properties.containsKey(TiC.PROPERTY_PROPERTIES)) {
				Object props = properties.get(TiC.PROPERTY_PROPERTIES);
				if (props instanceof HashMap) {
					HashMap<String, Object> propsHash = (HashMap<String, Object>) props;
					Object searchText = propsHash.get(TiC.PROPERTY_SEARCHABLE_TEXT);
					if (propsHash.containsKey(TiC.PROPERTY_SEARCHABLE_TEXT) && searchText != null) {
						searchableText = TiConvert.toString(searchText);
					}
				}
			}

		}

		public KrollDict getProperties() {
			return properties;
		}
		
		public String getSearchableText() {
			return searchableText;
		}
		
		public CollectionViewTemplate getTemplate() {
			return template;
		}
	}
	
	public CollectionSectionProxy () {
		//initialize variables
		listItemData = new ArrayList<CollectionItemData>();
		filterIndices = new ArrayList<Integer>();
		itemCount = 0;
		preload = false;
	}
	
	public void handleCreationDict(KrollDict dict) {
		//getting header/footer titles from creation dictionary
		if (dict.containsKey(TiC.PROPERTY_HEADER_TITLE)) {
			headerTitle = TiConvert.toString(dict, TiC.PROPERTY_HEADER_TITLE);
		}
		if (dict.containsKey(TiC.PROPERTY_FOOTER_TITLE)) {
			footerTitle = TiConvert.toString(dict, TiC.PROPERTY_FOOTER_TITLE);
		}
		if (dict.containsKey(TiC.PROPERTY_HEADER_VIEW)) {
			Object obj = dict.get(TiC.PROPERTY_HEADER_VIEW);
			if (obj instanceof TiViewProxy) {
				headerView = (TiViewProxy) obj;
			}
		}
		if (dict.containsKey(TiC.PROPERTY_FOOTER_VIEW)) {
			Object obj = dict.get(TiC.PROPERTY_FOOTER_VIEW);
			if (obj instanceof TiViewProxy) {
				footerView = (TiViewProxy) obj;
			}
		}

		if (dict.containsKey(TiC.PROPERTY_ITEMS)) {
			handleSetItems(dict.get(TiC.PROPERTY_ITEMS));
		}
	}
	
	public void setAdapter(TiBaseAdapter a) {
		adapter = a;
	}

	@Kroll.method @Kroll.setProperty
	public void setHeaderView(TiViewProxy headerView) {
		if (TiApplication.isUIThread()) {
			handleSetHeaderView(headerView);
		} else {
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SET_HEADER_VIEW), headerView);
		}
	}
	
	@Kroll.method @Kroll.getProperty
	public TiViewProxy getHeaderView() {
		return headerView;
	}
	
	@Kroll.method @Kroll.setProperty
	public void setFooterView(TiViewProxy footerView) {
		if (TiApplication.isUIThread()) {
			handleSetFooterView(footerView);
		} else {
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SET_FOOTER_VIEW), footerView);
		}
	}
	
	@Kroll.method @Kroll.getProperty
	public TiViewProxy getFooterView() {
		return footerView;
	}

	@Kroll.method @Kroll.setProperty
	public void setHeaderTitle(String headerTitle) {
		if (TiApplication.isUIThread()) {
			handleSetHeaderTitle(headerTitle);
		} else {
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SET_HEADER_TITLE), headerTitle);
		}
	}
	
	@Kroll.method @Kroll.getProperty
	public String getHeaderTitle() {
		return headerTitle;
	}
	
	@Kroll.method @Kroll.setProperty
	public void setFooterTitle(String footerTitle) {
		if (TiApplication.isUIThread()) {
			handleSetFooterTitle(footerTitle);
		} else {
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SET_FOOTER_TITLE), footerTitle);
		}
	}
	
	@Kroll.method @Kroll.getProperty
	public String getFooterTitle() {
		return footerTitle;
	}
	
	public String getHeaderOrFooterTitle(int index) {
		if (isHeaderTitle(index)) {
			return headerTitle;
		} else if (isFooterTitle(index)) {
			return footerTitle;
		}
		return "";
	}

	public View getHeaderOrFooterView(int index) {
		if (isHeaderView(index)) {
			return getListView().layoutHeaderOrFooterView(headerView);
		} else if (isFooterView(index)) {
			return getListView().layoutHeaderOrFooterView(footerView);
		}
		return null;
	}



	@Override
	public boolean handleMessage(Message msg) 
	{
		switch (msg.what) {

			case MSG_SET_ITEMS: {
				AsyncResult result = (AsyncResult) msg.obj;
				handleSetItems(result.getArg());
				result.setResult(null);
				return true;
			}
			
			case MSG_SET_HEADER_TITLE: {
				AsyncResult result = (AsyncResult) msg.obj;
				handleSetHeaderTitle(TiConvert.toString(result.getArg()));
				result.setResult(null);
				return true;
			}

			case MSG_SET_FOOTER_TITLE: {
				AsyncResult result = (AsyncResult) msg.obj;
				handleSetFooterTitle(TiConvert.toString(result.getArg()));
				result.setResult(null);
				return true;
			}

			case MSG_SET_HEADER_VIEW: {
				AsyncResult result = (AsyncResult) msg.obj;
				handleSetHeaderView((TiViewProxy)result.getArg());
				result.setResult(null);
				return true;
			}

			case MSG_SET_FOOTER_VIEW: {
				AsyncResult result = (AsyncResult) msg.obj;
				handleSetFooterView((TiViewProxy)result.getArg());
				result.setResult(null);
				return true;
			}


			case MSG_GET_ITEMS: {
				AsyncResult result = (AsyncResult) msg.obj;
				result.setResult(itemProperties.toArray());
				return true;
			}

			case MSG_APPEND_ITEMS: {
				AsyncResult result = (AsyncResult) msg.obj;
				handleAppendItems(result.getArg());
				result.setResult(null);
				return true;
			}

			case MSG_INSERT_ITEMS_AT: {
				AsyncResult result = (AsyncResult) msg.obj;
				KrollDict data = (KrollDict) result.getArg();
				int index = data.getInt(TiC.EVENT_PROPERTY_INDEX);
				handleInsertItemsAt(index, data.get(TiC.PROPERTY_DATA));
				result.setResult(null);
				return true;
			}

			case MSG_DELETE_ITEMS_AT: {
				AsyncResult result = (AsyncResult) msg.obj;
				KrollDict data = (KrollDict) result.getArg();
				int index = data.getInt(TiC.EVENT_PROPERTY_INDEX);
				int count = data.getInt(TiC.PROPERTY_COUNT);
				handleDeleteItemsAt(index, count);
				result.setResult(null);
				return true;
			}

			case MSG_REPLACE_ITEMS_AT: {
				AsyncResult result = (AsyncResult) msg.obj;
				KrollDict data = (KrollDict) result.getArg();
				int index = data.getInt(TiC.EVENT_PROPERTY_INDEX);
				int count = data.getInt(TiC.PROPERTY_COUNT);
				handleReplaceItemsAt(index, count, data.get(TiC.PROPERTY_DATA));
				result.setResult(null);
				return true;
			}

			case MSG_GET_ITEM_AT: {
				AsyncResult result = (AsyncResult) msg.obj;
				KrollDict item = handleGetItemAt(TiConvert.toInt(result.getArg()));
				result.setResult(item);
				return true;
			}
			
			case MSG_UPDATE_ITEM_AT: {
				AsyncResult result = (AsyncResult) msg.obj;
				KrollDict data = (KrollDict) result.getArg();
				int index = data.getInt(TiC.EVENT_PROPERTY_INDEX);
				handleUpdateItemAt(index, data.get(TiC.PROPERTY_DATA));
				result.setResult(null);
				return true;
			}

			default : {
				return super.handleMessage(msg);
			}

		}
	}

	@Kroll.method
	public KrollDict getItemAt(int index) {
		if (TiApplication.isUIThread()) {
			return handleGetItemAt(index);
		} else {
			return (KrollDict) TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_GET_ITEM_AT), index);
		}
	}
	
	private KrollDict handleGetItemAt(int index) {
		if (itemProperties != null && index >= 0 && index < itemProperties.size()) {
			return new KrollDict((HashMap)itemProperties.get(index));
		}
		return null;
	}

	@Kroll.method @Kroll.setProperty
	public void setItems(Object data) {
		if (TiApplication.isUIThread()) {
			handleSetItems(data);
		} else {
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SET_ITEMS), data);
		}
	}
	
	@Kroll.method @Kroll.getProperty
	public Object[] getItems() {
		if (itemProperties == null) {
			return new Object[0];
		} else if (TiApplication.isUIThread()) {
			return itemProperties.toArray();
		} else {
			return (Object[]) TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_GET_ITEMS));
		}
	}
	
	@Kroll.method
	public void appendItems(Object data) {
		if (TiApplication.isUIThread()) {
			handleAppendItems(data);
		} else {
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_APPEND_ITEMS), data);
		}
	}
	
	public boolean isIndexValid(int index) {
		return (index >= 0) ? true : false;
	}
	
	@Kroll.method
	public void insertItemsAt(int index, Object data) {
		if (!isIndexValid(index)) {
			return;
		}
		
		if (TiApplication.isUIThread()) {
			handleInsertItemsAt(index, data);
		} else {
			KrollDict d = new KrollDict();
			d.put(TiC.PROPERTY_DATA, data);
			d.put(TiC.EVENT_PROPERTY_INDEX, index);
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_INSERT_ITEMS_AT), d);
		}
	}
	
	@Kroll.method
	public void deleteItemsAt(int index, int count) {
		if (!isIndexValid(index)) {
			return;
		}

		if (TiApplication.isUIThread()) {
			handleDeleteItemsAt(index, count);
		} else {
			KrollDict d = new KrollDict();
			d.put(TiC.EVENT_PROPERTY_INDEX, index);
			d.put(TiC.PROPERTY_COUNT, count);
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_DELETE_ITEMS_AT), d);
		}
	}
	
	@Kroll.method
	public void replaceItemsAt(int index, int count, Object data) {
		if (!isIndexValid(index)) {
			return;
		}

		if (TiApplication.isUIThread()) {
			handleReplaceItemsAt(index, count, data);
		} else {
			KrollDict d = new KrollDict();
			d.put(TiC.EVENT_PROPERTY_INDEX, index);
			d.put(TiC.PROPERTY_COUNT, count);
			d.put(TiC.PROPERTY_DATA, data);
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_REPLACE_ITEMS_AT), d);
		}
	}
	
	@Kroll.method
	public void updateItemAt(int index, Object data) {
		if (!isIndexValid(index) || !(data instanceof HashMap)) {
			return;
		}

		if (TiApplication.isUIThread()) {
			handleUpdateItemAt(index,  new Object[]{data});
		} else {
			KrollDict d = new KrollDict();
			d.put(TiC.EVENT_PROPERTY_INDEX, index);
			d.put(TiC.PROPERTY_DATA, new Object[]{data});
			TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_UPDATE_ITEM_AT), d);
		}
	}

	
	public void processPreloadData() {
		if (itemProperties != null && preload) {
			handleSetItems(itemProperties.toArray());
			preload = false;
		}
	}
	
	public void refreshItems() {
		handleSetItems(itemProperties.toArray());
	}

	private void processData(Object[] items, int offset) {
		if (listItemData == null) {
			return;
		}
		
		CollectionViewTemplate[] temps = new CollectionViewTemplate[items.length];
		//First pass through data, we process template and update
		//default properties based data given
		for (int i = 0; i < items.length; i++) {
			Object itemData = items[i];
			if (itemData instanceof HashMap) {
				KrollDict d = new KrollDict((HashMap)itemData);
				CollectionViewTemplate template = processTemplate(d, i + offset);
				template.updateOrMergeWithDefaultProperties(d, true);
				temps[i] = template;
			}
		}
		//Second pass we would merge properties
		for (int i = 0; i < items.length; i++) {
			Object itemData = items[i];
			if (itemData instanceof HashMap) {
				KrollDict d = new KrollDict((HashMap)itemData);
				CollectionViewTemplate template = temps[i];
				if (template != null) {
					template.updateOrMergeWithDefaultProperties(d, false);
				}
				CollectionItemData itemD = new CollectionItemData(d, template);
				d.remove(TiC.PROPERTY_TEMPLATE);
				listItemData.add(i+offset, itemD);
			}
		}
		//Notify adapter that data has changed.
		adapter.notifyDataSetChanged();
	}

	private void handleSetItems(Object data) {

		if (data instanceof Object[]) {
			Object[] items = (Object[]) data;
			itemProperties =  new ArrayList<Object>(Arrays.asList(items));
			listItemData.clear();
			//only process items when listview's properties is processed.
			if (getListView() == null) {
				preload = true;
				return;
			}
			itemCount = items.length;
			processData(items, 0);

		} else {
			Log.e(TAG, "Invalid argument type to setData", Log.DEBUG_MODE);
		}
	}
	
	private void handleSetHeaderTitle(String headerTitle) {
		this.headerTitle = headerTitle;
		if (adapter != null) {
			adapter.notifyDataSetChanged();
		}
	}

	private void handleSetFooterTitle(String footerTitle) {
		this.footerTitle = footerTitle;
		if (adapter != null) {
			adapter.notifyDataSetChanged();
		}
	}

	private void handleSetHeaderView(TiViewProxy headerView) {
		this.headerView = headerView;
		if (adapter != null) {
			adapter.notifyDataSetChanged();
		}
	}

	private void handleSetFooterView(TiViewProxy footerView) {
		this.footerView = footerView;
		if (adapter != null) {
			adapter.notifyDataSetChanged();
		}
	}

	
	private void handleAppendItems(Object data) {
		if (data instanceof Object[]) {
			Object[] views = (Object[]) data;
			if (itemProperties == null) {
				itemProperties = new ArrayList<Object>(Arrays.asList(views));
			} else {
				for (Object view: views) {
					itemProperties.add(view);
				}
			}
			//only process items when listview's properties is processed.
			if (getListView() == null) {
				preload = true;
				return;
			}
			//we must update the itemCount before notify data change. If we don't, it will crash
			int count = itemCount;
			itemCount += views.length;

			processData(views, count);
			
		} else {
			Log.e(TAG, "Invalid argument type to setData", Log.DEBUG_MODE);
		}
	}
	
	private void handleInsertItemsAt(int index, Object data) {
		if (data instanceof Object[]) {
			Object[] views = (Object[]) data;
			
			if (itemProperties == null) {
				itemProperties = new ArrayList<Object>(Arrays.asList(views));
			} else {
				if (index < 0 || index > itemProperties.size()) {
					Log.e(TAG, "Invalid index to handleInsertItem", Log.DEBUG_MODE);
					return;
				}
				int counter = index;
				for (Object view: views) {
					itemProperties.add(counter, view);
					counter++;
				}
			}
			//only process items when listview's properties is processed.
			if (getListView() == null) {
				preload = true;
				return;
			}
			
			itemCount += views.length;
			processData(views, index);
		} else {
			Log.e(TAG, "Invalid argument type to insertItemsAt", Log.DEBUG_MODE);
		}
	}
	
	private boolean deleteItems(int index, int count) {
		boolean delete = false;
		while (count > 0) {
			if (index < itemProperties.size()) {
				itemProperties.remove(index);
				itemCount--;
				delete = true;
			}
			if (index < listItemData.size()) {
				listItemData.remove(index);
			}
			count--;
		}
		return delete;
	}
	
	private void handleDeleteItemsAt(int index, int count) {
		deleteItems(index, count);
		if (adapter != null) {
			adapter.notifyDataSetChanged();
		}
	}
	
	private void handleReplaceItemsAt(int index, int count, Object data) {
		if (count == 0) {
			handleInsertItemsAt(index, data);
		} else if (deleteItems(index, count)) {
			handleInsertItemsAt(index, data);
		}
	}
	
	private void handleUpdateItemAt(int index, Object data) {
		handleReplaceItemsAt(index, 1, data);
		setProperty(TiC.PROPERTY_ITEMS, itemProperties.toArray());
	}
	
	private CollectionViewTemplate processTemplate(KrollDict itemData, int index) {
		
		CollectionView listView = getListView();
		String defaultTemplateBinding = null;
		if (listView != null) {
			defaultTemplateBinding = listView.getDefaultTemplateBinding();
		}
		//if template is specified in data, we look it up and if one exists, we use it.
		//Otherwise we check if a default template is specified in ListView. If not, we use builtInTemplate.
		String binding = TiConvert.toString(itemData.get(TiC.PROPERTY_TEMPLATE));
		if (binding != null) {
			//check if template is default
			if (binding.equals(UIModule.LIST_ITEM_TEMPLATE_DEFAULT)) {
				return processDefaultTemplate(itemData, index);
			}

			CollectionViewTemplate template = listView.getTemplateByBinding(binding);
			//if template is successfully retrieved, bind it to the index. This is b/c
			//each row can have a different template.
			if (template == null) {
				Log.e(TAG, "Template undefined");
			}
						
			return template;
			
		} else {
			//if a valid default template is specify, use that one
			if (defaultTemplateBinding != null && !defaultTemplateBinding.equals(UIModule.LIST_ITEM_TEMPLATE_DEFAULT)) {
				CollectionViewTemplate defTemplate = listView.getTemplateByBinding(defaultTemplateBinding);
				if (defTemplate != null) {
					return defTemplate;
				}
			}
			return processDefaultTemplate(itemData, index);
		}	
		
	}
	
	private CollectionViewTemplate processDefaultTemplate(KrollDict data, int index) {

		if (builtInTemplate == null){
		
			//Create template and generate default properties
			builtInTemplate = new DefaultCollectionViewTemplate(UIModule.LIST_ITEM_TEMPLATE_DEFAULT, null, getActivity());
			//Each template is treated as an item type, so we can reuse views efficiently.
			//Section templates are given a type in CollectionView.processSections(). Here we
			//give default template a type if possible.
			CollectionView listView = getListView();
			if (listView != null) {
				builtInTemplate.setType(CollectionView.BUILT_IN_TEMPLATE_ITEM_TYPE);
				builtInTemplate.setRootParent(listView.getProxy());
			}
		}

		return builtInTemplate;
	}

	/**
	 * This method creates a new cell and fill it with content. getView() calls this method
	 * when a view needs to be created.
	 * @param index Entry's index relative to its section
	 * @return
	 */
	public void generateCellContent(int sectionIndex, KrollDict data, CollectionViewTemplate template, BaseCollectionViewItem itemContent, int itemPosition, View item_layout) {
		//Here we create an item content and populate it with data
		//Get item proxy
		TiViewProxy itemProxy = template.getRootItem().getViewProxy();
		//Create corresponding TiUIView for item proxy
		CollectionItem item = new CollectionItem(itemProxy, (TiCompositeLayout.LayoutParams)itemContent.getLayoutParams(), itemContent, item_layout);		
		//Connect native view with TiUIView so we can get it from recycled view.
		itemContent.setTag(item);
	
		if (data != null && template != null) {
			generateChildContentViews(template.getRootItem(), null, itemContent, true);
			populateViews(data, itemContent, template, itemPosition, sectionIndex, item_layout);
		}
	}
	
	
	public void generateChildContentViews(DataItem item, TiUIView parentContent, BaseCollectionViewItem rootItem, boolean root) {

		ArrayList<DataItem> childrenItem = item.getChildren();
		for (int i = 0; i < childrenItem.size(); i++) {
			DataItem child = childrenItem.get(i);
			TiViewProxy proxy = child.getViewProxy();
			TiUIView view = proxy.createView(proxy.getActivity());
			view.registerForTouch();
			proxy.setView(view);
			generateChildContentViews(child, view, rootItem, false);
			//Bind view to root.
			
			ViewItem viewItem = new ViewItem(view, new KrollDict());
			rootItem.bindView(child.getBindingId(), viewItem);
			//Add it to view hierarchy
			if (root) {
				rootItem.addView(view.getNativeView(), view.getLayoutParams());
			} else {
				parentContent.add(view);
			}

		}
	}
	
	public void appendExtraEventData(TiUIView view, int itemIndex, int sectionIndex, String bindId, String itemId) {
		KrollDict existingData = view.getAdditionalEventData();
		if (existingData == null) {
			existingData = new KrollDict();
			view.setAdditionalEventData(existingData);
		}

		existingData.put(TiC.PROPERTY_SECTION, this);
		existingData.put(TiC.PROPERTY_SECTION_INDEX, sectionIndex);
		int realItemIndex = itemIndex;
		if (isFilterOn()) {
			realItemIndex =  filterIndices.get(itemIndex);
		}
		existingData.put(TiC.PROPERTY_ITEM_INDEX, realItemIndex);

		if (!bindId.startsWith(CollectionViewTemplate.GENERATED_BINDING) && !bindId.equals(TiC.PROPERTY_PROPERTIES)) {
			existingData.put(TiC.PROPERTY_BIND_ID, bindId);
		} else if (existingData.containsKey(TiC.PROPERTY_BIND_ID)){
			existingData.remove(TiC.PROPERTY_BIND_ID);
		}

		if (itemId != null) {
			existingData.put(TiC.PROPERTY_ITEM_ID, itemId);
		} else if (existingData.containsKey(TiC.PROPERTY_ITEM_ID)){
			existingData.remove(TiC.PROPERTY_ITEM_ID);
		}

	}
	
	public void populateViews(KrollDict data, BaseCollectionViewItem cellContent, CollectionViewTemplate template, int itemIndex, int sectionIndex, View item_layout) {
		Object cell = cellContent.getTag();
		//Handling root item, since that is not in the views map.
		if (!(cell instanceof CollectionItem)) {
			Log.e(TAG, "Cell is not CollectionItem. Something is wrong..", Log.DEBUG_MODE);
			return;
		}
		
		CollectionItem listItem = (CollectionItem) cell;
		KrollDict listItemProperties;
		String itemId = null;

		if (data != null && data.containsKey(TiC.PROPERTY_PROPERTIES)) {
			listItemProperties = new KrollDict((HashMap)data.get(TiC.PROPERTY_PROPERTIES));
		} else {
			listItemProperties = template.getRootItem().getDefaultProperties(); 
		}
		
		//find out if we need to update itemId
		if (listItemProperties.containsKey(TiC.PROPERTY_ITEM_ID)) {
			itemId = TiConvert.toString(listItemProperties.get(TiC.PROPERTY_ITEM_ID));
		}
		
		//update extra event data for list item
		appendExtraEventData(listItem, itemIndex, sectionIndex, TiC.PROPERTY_PROPERTIES, itemId);
		
		HashMap<String, ViewItem> views = (HashMap<String, ViewItem>) cellContent.getViewsMap();
		//Loop through all our views and apply default properties
		for (String binding: views.keySet()) {
			DataItem dataItem = template.getDataItem(binding);
			ViewItem viewItem = views.get(binding);
			TiUIView view = viewItem.getView();
			//update extra event data for views
			if (view != null) {
				appendExtraEventData(view, itemIndex, sectionIndex, binding, itemId);
			}
			//if binding is contain in data given to us, process that data, otherwise
			//apply default properties.
			if (data != null && data.containsKey(binding) && view != null) {
				KrollDict properties = new KrollDict((HashMap)data.get(binding));
				KrollDict diffProperties = viewItem.generateDiffProperties(properties);
				if (!diffProperties.isEmpty()) {
					view.processProperties(diffProperties);
				}

			} else if (dataItem != null && view != null) {
				KrollDict diffProperties = viewItem.generateDiffProperties(dataItem.getDefaultProperties());
				if (!diffProperties.isEmpty()) {
					view.processProperties(diffProperties);
				}
			} else {
				Log.w(TAG, "Sorry, " + binding + " isn't a valid binding. Perhaps you made a typo?", Log.DEBUG_MODE);
			}
			
		}
		
		//process listItem properties
		KrollDict listItemDiff = cellContent.getViewItem().generateDiffProperties(listItemProperties);
		if (!listItemDiff.isEmpty()) {
			listItem.processProperties(listItemDiff);
		}

	}
	
	public CollectionViewTemplate getTemplateByIndex(int index) {
		if (headerTitle != null || headerView != null) {
			index -= 1;
		}

		
		if (isFilterOn()) {
			return listItemData.get(filterIndices.get(index)).getTemplate();
		} else {
			return listItemData.get(index).getTemplate();
		}
	}

	public int getContentCount() {
		if (isFilterOn()) {
			return filterIndices.size();
		} else {
			return itemCount;
		}
	}

	/**
	 * @return number of entries within section
	 */
	public int getItemCount() {
		int totalCount = 0;

		if (isFilterOn()) {
			totalCount = filterIndices.size();
		} else { 
			totalCount = itemCount;
		}
		
		if (!hideHeaderOrFooter()) {
			if (headerTitle != null || headerView != null) {
				totalCount += 1;
			}
			if (footerTitle != null || footerView != null) {
				totalCount +=1;
			}
		}


		return totalCount;
	}
	
	private boolean hideHeaderOrFooter() {
		CollectionView listview = getListView();
		return (listview.getSearchText() != null && filterIndices.isEmpty());
	}
	
	public boolean isHeaderView(int pos) {
		return (headerView != null && pos == 0);
	}
	
	public boolean isFooterView(int pos) {
		return (footerView != null && pos == getItemCount() - 1);
	}

	public boolean isHeaderTitle(int pos) {
		return (headerTitle != null && pos == 0) ;
	}
	
	public boolean isFooterTitle(int pos) {
		return (footerTitle != null && pos == getItemCount() - 1);
	}
	

	
	public void setListView(CollectionView l) {
		listView = new WeakReference<CollectionView>(l);
	}
	
	public CollectionView getListView() {
		if (listView != null) {
			return listView.get();
		}
		return null;
	}
	
	/**
	 * Attempt to give each existing template a type, if possible
	 */
	public void setTemplateType() {
		
		for (int i = 0; i < listItemData.size(); i++) {
			CollectionViewTemplate temp = listItemData.get(i).getTemplate();
			CollectionView listView = getListView();
			if (temp.getType() == -1) {
				temp.setType(listView.getItemType());
			}
		}
	}
	
	public KrollDict getCollectionItemData(int position) {		
		if (isFilterOn()) {
			return listItemData.get(filterIndices.get(position)).getProperties();
		} else if (position >= 0 && position < listItemData.size()) {
			return listItemData.get(position).getProperties();
		} 
		return null;
	}
	
	public boolean isFilterOn() {
		if (getListView().getSearchText() != null) {
			return true;
		}
		return false;
	}

	public void applyFilter(String searchText) {
		//Clear previous result
		filterIndices.clear();
		boolean caseInsensitive = getListView().getCaseInsensitive();
		//Add new results
		for (int i = 0; i < listItemData.size(); ++i) {
			String searchableText = listItemData.get(i).getSearchableText();
			//Handle case sensitivity
			if (caseInsensitive) {
				searchText = searchText.toLowerCase();
				searchableText = searchableText.toLowerCase();
			}
			//String comparison
			if (searchableText.contains(searchText)) {
				filterIndices.add(i);
			}
		}
	}
	
	public void release() {
		if (listItemData != null) {
			listItemData.clear();
			listItemData = null;
		}
		
		if (itemProperties != null) {
			itemProperties.clear();
			itemProperties = null;
		}
		
		if (builtInTemplate != null) {
			builtInTemplate.release();
			builtInTemplate = null;
		}
		super.release();
	}
	
	public void releaseViews()
	{
		listView = null;
	}
	
	@Override
	public String getApiName()
	{
		return "ti.CollectionSection";
	}
}