/*******************************************************************************
 * Copyright (c) 2005, 2019 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.common.internal.emfworkbench;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;

import org.eclipse.wst.common.internal.emfworkbench.integration.EditModel;

/**
 * This class will be used to cache the recently used edit models.  Loading and unloading of edit models can be
 * costly, especially if operations and/or post operations have a need to reload the same edit model repeatedly.
 * This will allow those repeatedly used edit models to be cached, thereby improving performance.  By design, the
 * edit models load resources and these resources are stored in memory, so we don't want to cache every edit
 * model accessed, so this is a least used cache mechanism, where the max size is 10.  If an edit model is used
 * it is put to the back of the stack, and new edit models accessed are put to the back of the stack, so that the
 * top of the stack is the first edit model disposed when the cache is higher than the threshhold.
 *
 */
public class EditModelLeastUsedCache {
	
	/**
	 * Provide a singleton instance.
	 */
	private static EditModelLeastUsedCache INSTANCE = new EditModelLeastUsedCache();	
	
	/**
	 * The threshold, or most edit models we will keep open at a time is 10.  This is low enough to not
	 * overwhelm workbench memory and high enough to aid in operations which continually reload 3-4 
	 * edit models.
	 */
	private final static int threshhold = 10;
	
	/**
	 *  A LHS is required to ensure the order of the items is maintained. Other
	 * Set implementations (HashSet, TreeSet) do not preserve the order. This 
	 * is critical to the implementation. DO NOT CHANGE THIS. 
	 */
	private LinkedHashSet lru = new LinkedHashSet(threshhold);
	
	/**
	 * Accessor for the EditModelLeastUsedCache INSTANCE
	 * 
	 * @return the EditModelLeastUsedCache INSTANCE
	 */
	public static EditModelLeastUsedCache getInstance() {
		return INSTANCE;
	}
	
	/**
	 * Remove the all elements from the lru that are contained
	 * in <code>aCollection</code>.  This method assumes the
	 * EditModels in the aCollection will be discarded and it
	 * will not attempt to decrememt its reference count.
	 * @param aCollection - A {@link Collection} of {@link EditModel}.
	 */
	public void removeAllCached(Collection aCollection) {
		if (aCollection != null) { 
			lru.removeAll(aCollection);
		}
	}

	/**
	 * An {@link EditModel} is being accessed so we will want
	 * to update the lru and access the editModel which will hold
	 * a reference count.
	 * @param editModel - The {@link EditModel} that we want to place
	 * 	in the least used cache.
	 */
	public void access(EditModel editModel) {
		boolean shouldAccess = true;
		synchronized (lru) {
			if (lru.contains(editModel)) {
				moveToEnd(editModel);
				shouldAccess = false;
			}
		}
		if (shouldAccess) {
			editModel.access(this);
			synchronized (lru) {
				lru.add(editModel);
			}
		}
	}
	
	/**
	 * If we hit the capacity of the lru then remove the first one
	 * and release access.
	 */
	public void optimizeLRUSizeIfNecessary() {
		EditModel model = null;
		
		synchronized (lru) {
			if (lru.size() > threshhold) {
				// remove oldest element and release the edit model.
				Iterator iterator = lru.iterator();
				model = (EditModel) iterator.next();
				if (model != null) {
					lru.remove(model);	
				}
			}
		}
		if (model != null)
			model.releaseAccess(this);
	}

	/**
	 * Move the editModel to the end of the list 
	 * @param editModel -- EditModel to be moved
	 */
	private void moveToEnd(EditModel editModel) {
		lru.remove(editModel);
		lru.add(editModel);
	}	
}
