/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.impl;

import java.util.*;
import org.jboss.tools.common.model.*;
import org.jboss.tools.common.model.impl.XModelImpl;

/**
 * This is a class for holding children of model object
 * which may be sorted by a specific comparator.
 * @author glory
 */
public class RegularChildren {
	protected static XModelObject[] EMPTY = new XModelObject[0];

	protected SMap objects = null;
	protected Comparator<XModelObject> comparator = null;

	public RegularChildren() {}
    
	/**
	 * Returns false if children may be sorted. Returns true if order of
	 * children is important (e.g. it is defined by storage).
	 * 
	 * @return
	 */
	public boolean areChildrenOrdered() {
		return false;
	}
    
	/**
	 * Removes all children. To save memory, object keeping children is set to
	 * null.
	 */
	public void clear() {
		objects = null;
	}

	/**
	 * Sets comparator to sort children if it is allowed.
	 * 
	 * @param c
	 */
	public void setComparator(Comparator<XModelObject> c) {
		comparator = c;
	}

	/**
	 * Returns true if there are no children.
	 * 
	 * @return
	 */
	public boolean isEmpty() {
		return size() == 0;
	}

	/**
	 * Returns the amount of children.
	 * 
	 * @return
	 */
	public int size() {
		return (objects == null) ? 0 : objects.size();
	}
	
	/**
	 * Returns the amount of children for specified entity.
	 * 
	 * @param entity
	 * @return
	 */
	public int getChildrenCount(String entity) {
		return (objects == null) ? 0 : objects.getChildrenCount(entity);
	}
	
	/**
	 * returns sorted values
	 * 
	 * @return
	 */
	public XModelObject[] getObjects() {
		return (isEmpty()) ? EMPTY : objects.getSortedValues(comparator);
	}
    
	/**
	 * Returns copy of object map, where keys are path parts of objects.
	 * 
	 * @return
	 */
	public Map<String, XModelObject> getObjectsMap() {
		Map<String, XModelObject> result = new HashMap<String, XModelObject>();
		if (objects != null) {
			Map<String, XModelObject> entries = objects.getMap();
			synchronized(entries) {
				result.putAll(entries);
			}
		}
		return result;
	}
    
	/**
	 * Returns object by key which should be equal to its path part.
	 * 
	 * @param key
	 * @return
	 */
	public XModelObject getObject(String key) {
		return (isEmpty()) ? null : objects.get(key);
	}

	/**
	 * Adds child object. If a child with the same path part exists the
	 * operation is rejected and false is returned.
	 * 
	 * @param o
	 * @return
	 */
	public boolean addObject(XModelObject o) {
		String pp = o.getPathPart();
		if (pp == null)
			return false;
		if (objects != null && objects.get(pp) != null) {
			if (objects.get(pp) == o)
				return false;
			((XModelObjectImpl) o).setParent_0(null);
			return false;
		}
		if (objects == null)
			objects = new SMap();
		objects.put(pp, o);
		return true;
	}
    
	/**
	 * Removes child object. To save memory, if children set becomes empty,
	 * object keeping children is set to null.
	 * 
	 * @param o
	 * @return
	 */
	public boolean removeObject(XModelObject o) {
		if (objects == null)
			return false;
		String s = o.getPathPart();
		if (objects.get(s) == null)
			return false;
		((XModelObjectImpl) o).setParent_0(null);
		objects.remove(s);
		if (objects.size() == 0)
			objects = null;
		return true;
	}

	/**
	 * Request for change of path part in specified child. If other object
	 * exists that has proposed new path part, change is rejected and that
	 * object is returned as cause for the failure. Otherwize, object is
	 * re-registered in children map with new key.
	 * 
	 * @param o
	 * @param opp
	 * @param npp
	 * @return
	 */
	public XModelObject change(XModelObject o, String opp, String npp) {
		if (opp != null && opp.equals(npp))
			return null;
		XModelObject c = getObject(npp);
		if (c != null && c != o)
			return c;
		if (objects == null)
			objects = new SMap();
		if (opp != null)
			objects.remove(opp);
		objects.put(npp, o);
		if (opp != null && o.getParent() != null) {
			XModelImpl m = (XModelImpl) o.getModel();
			m.fireStructureChanged(o.getParent());
		}
		return null;
	}
    
	/**
	 * Returns index of specified child in children array. If children can be
	 * sorted, index is computed by sorted array. If child does not belong to
	 * children, than -1 is returned.
	 * 
	 * @param o
	 * @return
	 */
	public int getIndex(XModelObject o) {
		XModelObject[] os = getObjects();
		for (int i = 0; i < os.length; i++)	if (os[i] == o)	return i;
		return -1;
	}

	/**
	 * Moves child from position 'from' to position 'to'. This method is not
	 * relevant for children that may be sorted by comparator. Returns true if
	 * change in the order of children did occure.
	 * 
	 * @param from
	 * @param to
	 * @return
	 */
	public boolean move(int from, int to) {
		return false;
	}

	public void replaceChildren(XModelObject[] objects) {
		if(objects.length == 0) {
			if(this.objects != null) this.objects = null;
		} else {
			SMap m = new SMap();
			for (int i = 0; i < objects.length; i++) {
				m.put(objects[i].getPathPart(), objects[i]);
			}
			this.objects = m;
		}
	}
	
}

/**
 * Keeps objects in a fast up-to-date map and in a sorted cache that is lazily
 * updated on requests.
 * 
 * @author glory
 */
class SMap {
	Map<String,XModelObject> entries = new HashMap<String, XModelObject>();
	XModelObject[] cache = RegularChildren.EMPTY;

	public int size() {
		return entries.size();
	}

	public XModelObject get(String key) {
		return entries.get(key);
	}

	public void put(String key, XModelObject value) {
		synchronized(entries) {
			entries.put(key, value);
		}
		if(cache != null) {
			synchronized(this) {
				cache = null;
			}
		}
	}
    
	/**
	 * Returns the stored map.
	 * 
	 * @return
	 */
	public Map<String, XModelObject> getMap() {
		return entries;
	}

	private XModelObject[] values() {
		synchronized(entries) {
			return entries.values().toArray(RegularChildren.EMPTY);
		}
	}

	/**
	 * Returns values of map sorted by the passed comparator.
	 * @param comparator
	 * @return
	 */
	public XModelObject[] getSortedValues(Comparator<XModelObject> comparator) {
		XModelObject[] c = cache;
		//Otherwise, cache can be made null between 'if' and 'return', but we 
		//avoid synchronizing this line for the most probable return to be very fast.
		if (c != null) return c;
		synchronized (this) {
			if (cache != null) return cache;
			if (size() == 0) {
				cache = RegularChildren.EMPTY;
			} else {
				cache = values();
				if (comparator != null)
					Arrays.sort(cache, comparator);
			}
			return cache;
		}
	}

	public void remove(String key) {
		synchronized(entries) {
			entries.remove(key);
		}
		if(cache != null) {
			synchronized(this) {
				cache = null;
			}
		}
	}

	/**
	 * Returns the amount of objects for specified entity.
	 * @param entity
	 * @return
	 */
	public int getChildrenCount(String entity) {
		int k = 0;
		synchronized(entries) {
			for (XModelObject r : entries.values()) {
				String e = r.getModelEntity().getName();
				if (entity.equals(e)) ++k;
			}
		}
		return k;
	}
	
}
