/*******************************************************************************
 * Copyright (c) 2011-2018 The University of York.
 * 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License, v. 2.0 are satisfied: GNU General Public License, version 3.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-3.0
 *
 * Contributors:
 *     Antonio Garcia-Dominguez - initial API and implementation
 ******************************************************************************/
package org.eclipse.hawk.graph.updater;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;

import org.eclipse.hawk.core.IMetaModelResourceFactory;
import org.eclipse.hawk.core.IModelIndexer;
import org.eclipse.hawk.core.graph.IGraphDatabase;
import org.eclipse.hawk.core.graph.IGraphEdge;
import org.eclipse.hawk.core.graph.IGraphIterable;
import org.eclipse.hawk.core.graph.IGraphNode;
import org.eclipse.hawk.core.model.IHawkClass;
import org.eclipse.hawk.core.model.IHawkClassifier;
import org.eclipse.hawk.core.model.IHawkMetaModelResource;
import org.eclipse.hawk.graph.Slot;
import org.eclipse.hawk.graph.TypeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Cache that can be shared across multiple {@link GraphModelBatchInjector}
 * classes to avoid retrieving the same type node again and again. Designed to
 * be simply GC'ed during the {@link GraphModelUpdater#updateProxies()} stage.
 */
public class TypeCache {

	private static final Logger LOGGER = LoggerFactory.getLogger(TypeCache.class);

	private Map<IHawkClass, IGraphNode> hashedEClasses = new HashMap<>();
	private Map<IGraphNode, Map<String, Slot>> hashedEClassSlots = new HashMap<>();
	private IModelIndexer hawk;

	public TypeCache(IModelIndexer hawk) {
		this.hawk = hawk;
	}

	public IGraphNode getEClassNode(IGraphDatabase graph, IHawkClassifier e) throws Exception {
		IHawkClass eClass = null;

		if (e instanceof IHawkClass)
			eClass = ((IHawkClass) e);
		else
			System.err.println("getEClassNode called on a non-class classifier:\n" + e);

		IGraphNode classnode = hashedEClasses.get(eClass);

		if (classnode == null) {
			final String packageNSURI = eClass.getPackageNSURI();
			IGraphNode ePackageNode = null;

			final IGraphIterable<? extends IGraphNode> iterMM = graph.getMetamodelIndex().get("id", packageNSURI);
			if (!iterMM.iterator().hasNext()) {
				// Metamodel is not in the store, try to find it
				LOGGER.info("Metamodel {} is not in the graph, trying to locate it", packageNSURI);
				for (IMetaModelResourceFactory mmp : hawk.getMetaModelParsers()) {
					IHawkMetaModelResource mr = mmp.getMetamodel(packageNSURI);
					if (mr != null) {
						LOGGER.info("Found metamodel for {}, inserting", packageNSURI);
						final HashSet<IHawkMetaModelResource> setMR = new HashSet<>(Arrays.asList(mr));
						hawk.getMetaModelUpdater().insertMetamodels(setMR, hawk);
					}
				}
			}

			try {
				ePackageNode = iterMM.getSingle();
			} catch (NoSuchElementException ex) {
				throw new Exception(String.format(
					"Metamodel %s does not have a Node associated with it in the store, please make sure it has been inserted",
					packageNSURI));
			} catch (Exception e2) {
				LOGGER.error("Error while finding metamodel node", e2);
			}

			for (IGraphEdge r : ePackageNode.getEdges()) {
				final IGraphNode otherNode = r.getStartNode();
				if (otherNode.equals(ePackageNode)) {
					continue;
				}

				final Object id = otherNode.getProperty(IModelIndexer.IDENTIFIER_PROPERTY);
				if (id.equals(eClass.getName())) {
					classnode = otherNode;
					break;
				}
			}

			if (classnode != null) {
				hashedEClasses.put(eClass, classnode);
			} else {
				throw new Exception(String.format(
						"eClass: %s (%s) does not have a Node associated with it in the store, please make sure the metamodel %s has been inserted",
						eClass.getName(), eClass.getUri(), packageNSURI));
			}

			hashedEClassSlots.put(classnode, new TypeNode(classnode).getSlots());
		}

		return classnode;
	}

	public Map<String, Slot> getEClassNodeSlots(IGraphDatabase graph, IHawkClassifier e) throws Exception {
		return hashedEClassSlots.get(getEClassNode(graph, e));
	}
}
