/*******************************************************************************
 * Copyright (c) 2006, 2010 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are 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:
 *     QNX - Initial API and implementation
 *     Markus Schorn (Wind River Systems)
 *     Andrew Ferguson (Symbian)
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.dom;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMVisitor;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorUndefStatement;
import org.eclipse.cdt.core.dom.ast.IMacroBinding;
import org.eclipse.cdt.core.index.IIndexFile;
import org.eclipse.cdt.core.index.IIndexMacro;
import org.eclipse.cdt.core.index.IndexLocationFactory;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.internal.core.index.IIndexBindingConstants;
import org.eclipse.cdt.internal.core.index.IIndexFragment;
import org.eclipse.cdt.internal.core.index.IIndexFragmentBinding;
import org.eclipse.cdt.internal.core.index.IIndexFragmentName;
import org.eclipse.cdt.internal.core.index.IIndexScope;
import org.eclipse.cdt.internal.core.parser.scanner.CharArray;
import org.eclipse.cdt.internal.core.parser.scanner.MacroDefinitionParser;
import org.eclipse.cdt.internal.core.pdom.PDOM;
import org.eclipse.cdt.internal.core.pdom.db.Database;
import org.eclipse.cdt.internal.core.pdom.db.IString;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

/**
 * Represents macro definitions. They are stored with the file and with a PDOMMacroContainer.
 * The latter also contains the references to all macros with the same name.
 */
public class PDOMMacro implements IIndexMacro, IPDOMBinding, IASTFileLocation {
	private static final int CONTAINER = 0;
	private static final int FILE = 4;
	private static final int PARAMETERS= 8;
	private static final int EXPANSION = 12;
	private static final int NEXT_IN_FILE = 16;
	private static final int NEXT_IN_CONTAINER = 20;
	private static final int PREV_IN_CONTAINER = 24;
	private static final int NAME_OFFSET = 28;
	private static final int NAME_LENGTH = 32; // short
	
	private static final int RECORD_SIZE = 34;  
	private static final char[][] UNINITIALIZED= {};
	private static final char[]   UNINITIALIZED1= {};

	private final PDOMLinkage fLinkage;
	private final long fRecord;

	private char[][] fParameterList= UNINITIALIZED;
	private char[] fExpansion= UNINITIALIZED1;
	private PDOMMacroContainer fContainer;
	private PDOMMacroDefinitionName fDefinition;

	public PDOMMacro(PDOMLinkage linkage, long record) {
		fLinkage = linkage;
		fRecord = record;
	}
	
	public PDOMMacro(PDOMLinkage linkage, PDOMMacroContainer container, IASTPreprocessorMacroDefinition macro,
			PDOMFile file) throws CoreException {
		this(linkage, container, file, macro.getName());

		final IASTName name = macro.getName();
		final IMacroBinding binding= (IMacroBinding) name.getBinding();
		final char[][] params= binding.getParameterList();
		
		final Database db= linkage.getDB();
		db.putRecPtr(fRecord + EXPANSION, db.newString(binding.getExpansionImage()).getRecord());		
		if (params != null) {
			StringBuilder buf= new StringBuilder();
			for (char[] param : params) {
				buf.append(param);
				buf.append(',');
			}
			db.putRecPtr(fRecord + PARAMETERS, db.newString(buf.toString().toCharArray()).getRecord());
		}
	}
	
	public PDOMMacro(PDOMLinkage linkage, PDOMMacroContainer container, IASTPreprocessorUndefStatement undef,
			PDOMFile file) throws CoreException {
		this(linkage, container, file, undef.getMacroName());
	}

	private PDOMMacro(PDOMLinkage linkage, PDOMMacroContainer container, PDOMFile file, IASTName name)
			throws CoreException {
		final Database db= linkage.getDB();
		fLinkage = linkage;
		fRecord = db.malloc(RECORD_SIZE);
		fContainer= container;

		final IASTFileLocation fileloc = name.getFileLocation();
		db.putRecPtr(fRecord + CONTAINER, container.getRecord());
		db.putRecPtr(fRecord + FILE, file.getRecord());
		db.putInt(fRecord + NAME_OFFSET, fileloc.getNodeOffset());
		db.putShort(fRecord + NAME_LENGTH, (short) fileloc.getNodeLength());

		container.addDefinition(this);
	}

	@Override
	public PDOM getPDOM() {
		return fLinkage.getPDOM();
	}

	@Override
	public long getRecord() {
		return fRecord;
	}
	
	@Override
	public void delete(PDOMLinkage linkage) throws CoreException {
		// Delete from the binding chain
		PDOMMacro prevName = getPrevInContainer();
		PDOMMacro nextName = getNextInContainer();
		if (prevName != null)
			prevName.setNextInContainer(nextName);
		else {
			PDOMMacroContainer container= getContainer();
			container.setFirstDefinition(nextName);
			if (nextName == null && container.isOrphaned()) {
				container.delete(linkage);
			}
		}
		if (nextName != null)
			nextName.setPrevInContainer(prevName);

		final IString expansion = getExpansionInDB();
		if (expansion != null) {
			expansion.delete();
		}
		final IString params = getParamListInDB();
		if (params != null) {
			params.delete();
		}
		linkage.getDB().free(fRecord);
	}
	
	public PDOMMacroContainer getContainer() throws CoreException {
		if (fContainer == null) {
			fContainer= new PDOMMacroContainer(fLinkage, fLinkage.getDB().getRecPtr(fRecord + CONTAINER));
		}
		return fContainer;
	}
		
	private IString getExpansionInDB() throws CoreException {
		Database db = fLinkage.getDB();
		long rec = db.getRecPtr(fRecord + EXPANSION);
		return rec == 0 ? null : db.getString(rec);
	}

	private IString getParamListInDB() throws CoreException {
		Database db = fLinkage.getDB();
		long rec = db.getRecPtr(fRecord + PARAMETERS);
		return rec == 0 ? null : db.getString(rec);
	}

	public PDOMMacro getNextMacro() throws CoreException {
		long rec = fLinkage.getDB().getRecPtr(fRecord + NEXT_IN_FILE);
		return rec != 0 ? new PDOMMacro(fLinkage, rec) : null;
	}
	
	public void setNextMacro(PDOMMacro macro) throws CoreException {
		setNextMacro(macro != null ? macro.getRecord() : 0);
	}

	private void setNextMacro(long rec) throws CoreException {
		fLinkage.getDB().putRecPtr(fRecord + NEXT_IN_FILE, rec);
	}
				
	private PDOMMacro getPrevInContainer() throws CoreException {
		return getMacroField(PREV_IN_CONTAINER);
	}

	void setPrevInContainer(PDOMMacro macro) throws CoreException {
		setMacroField(PREV_IN_CONTAINER, macro);
	}

	public PDOMMacro getNextInContainer() throws CoreException {
		return getMacroField(NEXT_IN_CONTAINER);
	}
	
	void setNextInContainer(PDOMMacro macro) throws CoreException {
		setMacroField(NEXT_IN_CONTAINER, macro);
	}

	private void setMacroField(int offset, PDOMMacro macro) throws CoreException {
		long namerec = macro != null ? macro.getRecord() : 0;
		fLinkage.getDB().putRecPtr(fRecord + offset, namerec);
	}
	
	private PDOMMacro getMacroField(int offset) throws CoreException {
		long namerec= fLinkage.getDB().getRecPtr(fRecord + offset);
		return namerec != 0 ? new PDOMMacro(fLinkage, namerec) : null;
	}

	@Override
	public char[][] getParameterList() {
		if (fParameterList == UNINITIALIZED) {
			fParameterList= null;
			try {
				IString plist= getParamListInDB();
				if (plist != null) {
					List<char[]> paramList = new ArrayList<char[]>();
					final char[] cplist= plist.getChars();
					final int end = cplist.length;
					int from= 0;
					int to= CharArrayUtils.indexOf(',', cplist, from, end);
					while (to > from) {
						paramList.add(CharArrayUtils.extract(cplist, from, to-from));
						from= to+1;
						to= CharArrayUtils.indexOf(',', cplist, from, end);
					}
					fParameterList= paramList.toArray(new char[paramList.size()][]);
				}
			} catch (CoreException e) {
				CCorePlugin.log(e);
			}
		}
		return fParameterList;
	}

	public boolean isMacroDefinition() throws CoreException {
		if (fExpansion == UNINITIALIZED1) {
			return fLinkage.getDB().getRecPtr(fRecord + EXPANSION) != 0;
		}
		return fExpansion != null;
	}

	@Override
	public char[] getExpansionImage() {
		if (fExpansion == UNINITIALIZED1) {
			try {
				final IString expansionInDB = getExpansionInDB();
				fExpansion= expansionInDB == null ? null : expansionInDB.getChars();
			} catch (CoreException e) {
				CCorePlugin.log(e);
				fExpansion= CharArrayUtils.EMPTY;
			}
		}
		return fExpansion;
	}

	@Override
	public char[] getNameCharArray() {
		try {
			return getContainer().getNameCharArray();
		} catch (CoreException e) {
			CCorePlugin.log(e);
			return new char[] {' '};
		}
	}
	
	@Override
	public String getName() {
		return new String(getNameCharArray());
	}
	
	@Override
	public PDOMFile getFile() throws CoreException {
		long filerec = fLinkage.getDB().getRecPtr(fRecord + FILE);
		return filerec != 0 ? new PDOMFile(fLinkage, filerec) : null;
	}

	public long getFileRecord() throws CoreException {
		return fLinkage.getDB().getRecPtr(fRecord + FILE);
	}

	void setFile(PDOMFile file) throws CoreException {
		fLinkage.getDB().putRecPtr(fRecord + FILE, file != null ? file.getRecord() : 0);
	}

	@Override
	public String getFileName() {
		try {
			IIndexFile file = getFile();
			if (file == null) {
				return null;
			}
			// We need to specify what this method can return to know
			// how to implement this. Existing implementations return
			// the absolute path, so here we attempt to do the same.
			IPath location = IndexLocationFactory.getAbsolutePath(file.getLocation());
			return location != null ? location.toOSString() : null;
		} catch (CoreException e) {
			CCorePlugin.log(e);
		}
		return null;
	}

	@Override
	public int getStartingLineNumber() {
		return 0;
	}

	@Override
	public int getEndingLineNumber() {
		return 0;
	}

	@Override
	public IASTPreprocessorIncludeStatement getContextInclusionStatement() {
		return null;
	}

	@Override
	public IASTFileLocation asFileLocation() {
		return this;
	}
	
	@Override
	public IASTFileLocation getFileLocation() {
		return this;
	}

	@Override
	public int getNodeLength() {
		try {
			return fLinkage.getDB().getShort(fRecord + NAME_LENGTH);
		} catch (CoreException e) {
			CCorePlugin.log(e);
			return 0;
		}
	}

	@Override
	public int getNodeOffset() {
		try {
			return fLinkage.getDB().getInt(fRecord + NAME_OFFSET);
		} catch (CoreException e) {
			CCorePlugin.log(e);
			return 0;
		}
	}

	@Override
	public char[] getExpansion() {
		char[] expansionImage= getExpansionImage();
		return MacroDefinitionParser.getExpansion(new CharArray(expansionImage), 0, expansionImage.length);
	}

	@Override
	public char[][] getParameterPlaceholderList() {
		char[][] params= getParameterList();
		if (params != null && params.length > 0) {
			char[] lastParam= params[params.length - 1];
			if (CharArrayUtils.equals(lastParam, 0, Keywords.cpELLIPSIS.length, Keywords.cpELLIPSIS)) {
				char[][] result= new char[params.length][];
				System.arraycopy(params, 0, result, 0, params.length - 1);
				result[params.length - 1]= lastParam.length == Keywords.cpELLIPSIS.length ?
					Keywords.cVA_ARGS : 
					CharArrayUtils.extract(lastParam, Keywords.cpELLIPSIS.length, lastParam.length - Keywords.cpELLIPSIS.length);
				return result;
			}
		}
		return params;
	}

	@Override
	public boolean isFunctionStyle() {
		return getParameterList() != null;
	}
	
	@Override
	public boolean isDynamic() {
		return false;
	}

	@Override
	public PDOMLinkage getLinkage() {
		return fLinkage;
	}

	@Override
	public IIndexScope getScope() {
		return null;
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object getAdapter(Class adapter) {
		if (adapter.isAssignableFrom(PDOMMacro.class)) {
			return this;
		}
		return null;
	}

	@Override
	public IIndexFragmentName getDefinition() throws CoreException {
		if (!isMacroDefinition()) {
			return null;
		}
		if (fDefinition == null) {
			fDefinition= new PDOMMacroDefinitionName(this);
		}
		return fDefinition;
	}

	@Override
	public IIndexFile getLocalToFile() throws CoreException {
		return null;
	}

	@Override
	public String[] getQualifiedName() {
		return new String[] { getName() };
	}

	@Override
	public boolean isFileLocal() throws CoreException {
		return false;
	}

	@Override
	public int getBindingConstant() {
		return IIndexBindingConstants.MACRO_DEFINITION;
	}

	@Override
	public IIndexFragment getFragment() {
		return fLinkage.getPDOM();
	}

	@Override
	public boolean hasDeclaration() throws CoreException {
		return false;
	}

	@Override
	public boolean hasDefinition() throws CoreException {
		return true;
	}

	@Override
	public IIndexFragmentBinding getOwner() {
		return null;
	}

	@Override
	public void accept(IPDOMVisitor visitor) {
	}

	@Override
	public long getBindingID() {
		return fRecord;
	}
}
