/*******************************************************************************
 * Copyright (c) 2007, 2016 QNX Software Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Bryan Wilkinson (QNX) - Initial API and implementation
 *     Andrew Ferguson (Symbian)
 *     Markus Schorn (Wind River Systems)
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.dom.cpp;

import java.util.ArrayList;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.ASTTypeUtil;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplatePartialSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateArgument;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateNonTypeParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTypeParameter;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.internal.core.dom.parser.ProblemBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInstanceCache;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPTemplates;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
import org.eclipse.cdt.internal.core.index.IIndexCPPBindingConstants;
import org.eclipse.cdt.internal.core.pdom.db.Database;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMLinkage;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMNode;
import org.eclipse.core.runtime.CoreException;

/**
 * Implementation for class templates in the index, base class for partial specializations.
 */
public class PDOMCPPClassTemplate extends PDOMCPPClassType
		implements ICPPClassTemplate, ICPPInstanceCache, IPDOMCPPTemplateParameterOwner {
	private static final int PARAMETERS = PDOMCPPClassType.RECORD_SIZE + 0;
	private static final short RELEVANT_PARAMETERS = PDOMCPPClassType.RECORD_SIZE + 4;
	private static final int FIRST_PARTIAL = PDOMCPPClassType.RECORD_SIZE + 6;

	/**
	 * The size in bytes of a PDOMCPPClassTemplate record in the database.
	 */
	@SuppressWarnings("hiding")
	protected static final int RECORD_SIZE = PDOMCPPClassType.RECORD_SIZE + 10;

	private volatile ICPPTemplateParameter[] params; // Cached template parameters.

	public PDOMCPPClassTemplate(PDOMCPPLinkage linkage, PDOMNode parent, ICPPClassTemplate template,
			boolean visibleToAdlOnly) throws CoreException, DOMException {
		super(linkage, parent, template, visibleToAdlOnly);

		final Database db = getDB();
		ICPPTemplateParameter[] origParams = template.getTemplateParameters();
		IPDOMCPPTemplateParameter[] params = PDOMTemplateParameterArray.createPDOMTemplateParameters(linkage, this,
				origParams);
		long rec = PDOMTemplateParameterArray.putArray(db, params);
		db.putRecPtr(record + PARAMETERS, rec);
		db.putShort(record + RELEVANT_PARAMETERS, (short) params.length);
		linkage.new ConfigureTemplateParameters(origParams, params);
	}

	public PDOMCPPClassTemplate(PDOMLinkage linkage, long bindingRecord) {
		super(linkage, bindingRecord);
	}

	@Override
	protected int getRecordSize() {
		return RECORD_SIZE;
	}

	@Override
	public int getNodeType() {
		return IIndexCPPBindingConstants.CPP_CLASS_TEMPLATE;
	}

	@Override
	public ICPPTemplateParameter[] getTemplateParameters() {
		if (params == null) {
			try {
				final Database db = getDB();
				long rec = db.getRecPtr(record + PARAMETERS);
				int count = Math.max(0, db.getShort(record + RELEVANT_PARAMETERS));
				if (rec == 0 || count == 0) {
					params = ICPPTemplateParameter.EMPTY_TEMPLATE_PARAMETER_ARRAY;
				} else {
					IPDOMCPPTemplateParameter[] allParams = PDOMTemplateParameterArray.getArray(this, rec);
					count = Math.min(count, allParams.length);
					if (count == allParams.length) {
						params = allParams;
					} else {
						params = new ICPPTemplateParameter[count];
						System.arraycopy(allParams, 0, params, 0, count);
					}
				}
			} catch (CoreException e) {
				CCorePlugin.log(e);
				params = ICPPTemplateParameter.EMPTY_TEMPLATE_PARAMETER_ARRAY;
			}
		}
		return params;
	}

	@Override
	public void update(PDOMLinkage linkage, IBinding newBinding) throws CoreException {
		super.update(linkage, newBinding);
		if (newBinding instanceof ICPPClassTemplate) {
			ICPPClassTemplate ct = (ICPPClassTemplate) newBinding;
			try {
				updateTemplateParameters(linkage, ct.getTemplateParameters());
			} catch (DOMException e) {
				CCorePlugin.log(e);
			}
		}
	}

	private void updateTemplateParameters(PDOMLinkage linkage, ICPPTemplateParameter[] newParams)
			throws CoreException, DOMException {
		final Database db = getDB();
		long rec = db.getRecPtr(record + PARAMETERS);
		IPDOMCPPTemplateParameter[] allParams;
		if (rec == 0) {
			allParams = IPDOMCPPTemplateParameter.EMPTY_ARRAY;
		} else {
			allParams = PDOMTemplateParameterArray.getArray(this, rec);
		}

		final int newParamLength = newParams.length;
		int[] props = new int[allParams.length];
		int[] result = new int[newParamLength];
		int additionalPars = 0;
		boolean reorder = false;
		for (int i = 0; i < props.length; i++) {
			final IPDOMCPPTemplateParameter par = allParams[i];
			props[i] = getProperty(par);
		}

		outer: for (int i = 0; i < newParamLength; i++) {
			ICPPTemplateParameter newPar = newParams[i];
			int prop = getProperty(newPar);
			for (int j = 0; j < props.length; j++) {
				if (props[j] == prop) {
					// Reuse param
					result[i] = j;
					props[j] = -1;
					allParams[j].update(linkage, newPar);
					if (j != i)
						reorder = true;
					continue outer;
				}
			}
			result[i] = -1;
			additionalPars++;
		}

		if (additionalPars > 0 || reorder) {
			params = null;
			IPDOMCPPTemplateParameter[] newAllParams = new IPDOMCPPTemplateParameter[allParams.length + additionalPars];
			for (int j = 0; j < newParamLength; j++) {
				int idx = result[j];
				if (idx >= 0) {
					newAllParams[j] = allParams[idx];
					allParams[idx] = null;
				} else {
					newAllParams[j] = PDOMTemplateParameterArray.createPDOMTemplateParameter(getLinkage(), this,
							newParams[j]);
				}
			}
			int pos = newParamLength;
			for (IPDOMCPPTemplateParameter unused : allParams) {
				if (unused != null)
					newAllParams[pos++] = unused;
			}
			if (rec != 0)
				db.free(rec);
			rec = PDOMTemplateParameterArray.putArray(db, newAllParams);
			db.putRecPtr(record + PARAMETERS, rec);
		}
		db.putShort(record + RELEVANT_PARAMETERS, (short) newParamLength);
	}

	private int getProperty(ICPPTemplateParameter par) {
		int result = par.getParameterPosition() & 0xffff;
		if (par instanceof ICPPTemplateTypeParameter)
			return result;
		if (par instanceof ICPPTemplateNonTypeParameter)
			return result | 0x10000;
		return result | 0x20000;
	}

	private PDOMCPPClassTemplatePartialSpecialization getFirstPartial() throws CoreException {
		long value = getDB().getRecPtr(record + FIRST_PARTIAL);
		return value != 0 ? new PDOMCPPClassTemplatePartialSpecialization(getLinkage(), value) : null;
	}

	public void addPartial(PDOMCPPClassTemplatePartialSpecialization partial) throws CoreException {
		PDOMCPPClassTemplatePartialSpecialization first = getFirstPartial();
		partial.setNextPartial(first);
		getDB().putRecPtr(record + FIRST_PARTIAL, partial.getRecord());
	}

	@Override
	public ICPPClassTemplatePartialSpecialization[] getPartialSpecializations() {
		try {
			ArrayList<PDOMCPPClassTemplatePartialSpecialization> partials = new ArrayList<>();
			for (PDOMCPPClassTemplatePartialSpecialization partial = getFirstPartial(); partial != null; partial = partial
					.getNextPartial()) {
				partials.add(partial);
			}

			return partials.toArray(new ICPPClassTemplatePartialSpecialization[partials.size()]);
		} catch (CoreException e) {
			CCorePlugin.log(e);
			return ICPPClassTemplatePartialSpecialization.EMPTY_ARRAY;
		}
	}

	@Override
	public boolean isSameType(IType type) {
		if (type instanceof ITypedef) {
			return type.isSameType(this);
		}

		if (type instanceof PDOMNode) {
			PDOMNode node = (PDOMNode) type;
			if (node.getPDOM() == getPDOM()) {
				return node.getRecord() == getRecord();
			}
		}

		// Need a class template.
		if (!(type instanceof ICPPClassTemplate) || type instanceof ProblemBinding)
			return false;

		// Exclude other kinds of class templates.
		if (type instanceof ICPPClassTemplatePartialSpecialization || type instanceof ICPPTemplateTemplateParameter
				|| type instanceof ICPPClassSpecialization) {
			return false;
		}

		ICPPClassType ctype = (ICPPClassType) type;
		if (ctype.getKey() != getKey())
			return false;
		char[] nchars = ctype.getNameCharArray();
		if (nchars.length == 0) {
			nchars = ASTTypeUtil.createNameForAnonymous(ctype);
		}
		if (nchars == null || !CharArrayUtils.equals(nchars, getNameCharArray()))
			return false;

		return SemanticUtil.haveSameOwner(this, ctype);
	}

	@Override
	public ICPPTemplateInstance getInstance(ICPPTemplateArgument[] arguments) {
		return PDOMInstanceCache.getCache(this).getInstance(arguments);
	}

	@Override
	public void addInstance(ICPPTemplateArgument[] arguments, ICPPTemplateInstance instance) {
		PDOMInstanceCache.getCache(this).addInstance(arguments, instance);
	}

	@Override
	public ICPPTemplateInstance[] getAllInstances() {
		return PDOMInstanceCache.getCache(this).getAllInstances();
	}

	@Override
	public ICPPTemplateParameter adaptTemplateParameter(ICPPTemplateParameter param) {
		// Template parameters are identified by their position in the parameter list.
		int pos = param.getParameterPosition();
		ICPPTemplateParameter[] pars = getTemplateParameters();

		if (pars == null || pos >= pars.length)
			return null;

		ICPPTemplateParameter result = pars[pos];
		if (param instanceof ICPPTemplateTypeParameter) {
			if (result instanceof ICPPTemplateTypeParameter)
				return result;
		} else if (param instanceof ICPPTemplateNonTypeParameter) {
			if (result instanceof ICPPTemplateNonTypeParameter)
				return result;
		} else if (param instanceof ICPPTemplateTemplateParameter) {
			if (result instanceof ICPPTemplateTemplateParameter)
				return result;
		}
		return null;
	}

	@Override
	public final ICPPDeferredClassInstance asDeferredInstance() {
		PDOMInstanceCache cache = PDOMInstanceCache.getCache(this);
		synchronized (cache) {
			ICPPDeferredClassInstance dci = cache.getDeferredInstance();
			if (dci == null) {
				dci = CPPTemplates.createDeferredInstance(this);
				cache.putDeferredInstance(dci);
			}
			return dci;
		}
	}
}
