/**
 * 
 */
package org.erlide.ui.internal.search;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.erlide.core.erlang.util.ErlangFunction;

public class ErlangSearchTreeContentProvider extends
		ErlangSearchContentProvider implements ITreeContentProvider {

	private final Object[] EMPTY_ARR = new Object[0];

	private final TreeViewer fTreeViewer;
	private final Map<Object, List<Object>> childMap;
	private final Map<Object, Object> parentMap;
	private final List<String> moduleNames;
	private ErlangSearchResult fResult;

	public ErlangSearchTreeContentProvider(final TreeViewer viewer,
			final ErlangSearchResultPage page) {
		super(page);
		fTreeViewer = viewer;
		childMap = new HashMap<Object, List<Object>>();
		parentMap = new HashMap<Object, Object>();
		moduleNames = new ArrayList<String>();
		// modules = new ArrayList<IErlModule>();
	}

	public Object[] getElements(final Object inputElement) {
		return moduleNames.toArray();
	}

	@Override
	public void dispose() {
		// nothing to do
	}

	@Override
	public void inputChanged(final Viewer viewer, final Object oldInput,
			final Object newInput) {
		if (newInput instanceof ErlangSearchResult) {
			fResult = (ErlangSearchResult) newInput;
			initialize(fResult);
		}
	}

	@Override
	protected void initialize(final ErlangSearchResult result) {
		super.initialize(result);
		initialize(result.getResult());
	}

	private void addChild(final Object parent, final Object child) {
		if (!parentMap.containsKey(child)) {
			parentMap.put(child, parent);
		}
		if (childMap.containsKey(parent)) {
			final List<Object> children = childMap.get(parent);
			if (!children.contains(child)) {
				children.add(child);
			}
		} else {
			final List<Object> children = new ArrayList<Object>(1);
			children.add(child);
			childMap.put(parent, children);
		}
	}

	private void removeChild(final Object parent, final Object child) {
		if (parentMap.containsKey(child)) {
			parentMap.remove(child);
		}
		if (childMap.containsKey(parent)) {
			List<Object> children = childMap.get(parent);
			if (children != null) {
				children.remove(child);
				if (children.isEmpty()) {
					childMap.remove(parent);
				} else {
					childMap.put(parent, children);
				}
			}
		}
	}

	protected synchronized void initialize(final List<ErlangSearchElement> eses) {
		if (eses == null) {
			return;
		}
		moduleNames.clear();
		parentMap.clear();
		childMap.clear();
		for (final ErlangSearchElement ese : eses) {
			addElement(ese);
		}

	}

	private void addElement(final ErlangSearchElement ese) {
		final String moduleName = ese.getModuleName();
		if (!moduleNames.contains(moduleName)) {
			moduleNames.add(moduleName);
		}
		if (ese.isSubClause()) {
			final ErlangFunction function = new ErlangFunction(ese.getName(),
					ese.getArity());
			addChild(moduleName, function);
			addChild(function, ese);
		} else {
			addChild(moduleName, ese);
		}
	}

	private void removeElement(final ErlangSearchElement ese) {
		final String moduleName = ese.getModuleName();
		if (ese.isSubClause()) {
			final ErlangFunction function = new ErlangFunction(ese.getName(),
					ese.getArity());
			removeChild(moduleName, function);
			removeChild(function, ese);
		} else {
			removeChild(moduleName, ese);
		}
		List<Object> moduleChildren = childMap.get(moduleName);
		if (moduleChildren == null && moduleNames.contains(moduleName)) {
			moduleNames.remove(moduleName);
		}
	}

	public Object[] getChildren(final Object parentElement) {
		final List<Object> l = childMap.get(parentElement);
		if (l == null) {
			return EMPTY_ARR;
		}
		return l.toArray();
	}

	public boolean hasChildren(final Object element) {
		return childMap.containsKey(element);
	}

	public Object getParent(final Object element) {
		return parentMap.get(element);
	}

	@Override
	public void clear() {
		initialize(fResult.getResult());
		fTreeViewer.refresh();
	}

	@Override
	public void elementsChanged(final Object[] updatedElements) {
		if (getSearchResult() == null) {
			return;
		}
		final TreeViewer viewer = getViewer();
		final int elementLimit = getElementLimit();
		final boolean treeLimited = elementLimit != -1;
		int n = updatedElements.length;
		List<Object> toRemove = new ArrayList<Object>(n);
		List<Object> toAdd = new ArrayList<Object>(n);
		List<Object> toUpdate = new ArrayList<Object>(n);
		for (Object element : updatedElements) {
			if (fResult.getMatchCount(element) > 0) {
				if (viewer.testFindItem(element) != null) {
					toUpdate.add(element);
				} else {
					if (!treeLimited
							|| viewer.getTree().getItemCount() < elementLimit) {
						toAdd.add(element);
					}
				}
			} else {
				toRemove.add(element);
			}
		}
		for (Object element : toRemove) {
			removeElement((ErlangSearchElement) element);
		}
		for (Object element : toAdd) {
			addElement((ErlangSearchElement) element);
		}
		viewer.update(toUpdate.toArray(), null);
		viewer.remove(toRemove.toArray());
		viewer.refresh();
	}

	private int getElementLimit() {
		return getPage().getElementLimit().intValue();
	}

	private TreeViewer getViewer() {
		return fTreeViewer;
	}
}