/*******************************************************************************
 * Copyright (c) 2006, 2008 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.lrparser.symboltable;

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

import org.eclipse.cdt.core.dom.ast.IBinding;


/**
 * Used to compute binding resolution during the parse.
 * 
 * Imperative style symbol table with destructive update.
 * 
 * Consists of two data structures, a hash table for fast lookup
 * of bindings given their names, and a stack used to keep track
 * of scopes.
 * 
 * 
 * @author Mike Kucera
 */
public class CImperativeSymbolTable {
	
	private static final int TABLE_SIZE = 256;
	
	private Bucket[] table = new Bucket[TABLE_SIZE];
	
	private LinkedList<SymbolScope> scopeStack = new LinkedList<SymbolScope>();
	
	
	
	/**
	 * Represents a scope in the C language.
	 */
	private static class SymbolScope {
		
		/** 
		 * List of buckets that have been modified in the current scope.
		 * When the scope is closed these buckets are popped, returning the 
		 * symbol table to the state it was in before the scope was opened.
		 */
		List<Integer> modifiedBuckets = new ArrayList<Integer>();
	}
	
	
	/**
	 * A bucket object used to hold elements in the hash table.
	 */
	private static class Bucket {
		String key;
		CNamespace namespace;
		IBinding binding;
		Bucket next;
		
		Bucket(Bucket next, CNamespace namespace, String key, IBinding binding) {
			this.key = key;
			this.namespace = namespace;
			this.binding = binding;
			this.next = next;
		}
	}
	
	
	public CImperativeSymbolTable() {
		openScope(); // open the global scope
		// TODO populate the global scope with built-ins
	}
	
	
	/**
	 * Hashes a key into an index in the hash table.
	 */
	private int index(String key) {
		return Math.abs(key.hashCode() % TABLE_SIZE);
	}
	
	
	/**
	 * Adds a binding to the symbol table in the current scope.
	 * 
	 * @param mask A bit mask used to identify the namespace of the identifier.
	 */
	public void put(CNamespace namespace, String ident, IBinding b) {		
		int index = index(ident);
		table[index] = new Bucket(table[index], namespace, ident, b);
		
		SymbolScope scope = scopeStack.getLast();
		scope.modifiedBuckets.add(index);
	}
	

	/**
	 * Returns the binding associated with the given identifier, or
	 * null if there is none.
	 * 
	 * @param mask A bit mask used to identify the namespace of the identifier.
	 */
	public IBinding get(CNamespace namespace, String ident) {
		Bucket b = table[index(ident)];
		while(b != null) {
			if(namespace == b.namespace && ident.equals(b.key))
				return b.binding;
			b = b.next;
		}
		return null;
	}
	
	
	/**
	 * Opens a new inner scope for identifiers.
	 * 
	 * If an identifier is added that already exists in an outer scope 
	 * then it will be shadowed.
	 */
	public void openScope() {
		scopeStack.add(new SymbolScope());
	}
	
	
	/**
	 * Remove all the symbols defined in the scope that is being closed.
	 * 
	 * @param scope An IScope object that will be used to represent this scope.
	 * @throws SymbolTableException If the global scope has already been closed or if bindingScope is null.
	 */
	public void closeScope() {		
		SymbolScope poppedScope = scopeStack.removeLast(); // pop the scopeStack
			
		// pop each bucket that was modified in the scope
		for(int index : poppedScope.modifiedBuckets)
			table[index] = table[index].next;
	}
	
	
	@SuppressWarnings("nls")
	@Override
	public String toString() {
		StringBuilder buff = new StringBuilder('[');
		for(Bucket b : table) {
			while(b != null) {
				buff.append('<').append(b.key).append(": ").append(b.binding).append(">, ");
				b = b.next;
			}
		}
		return buff.append(']').toString();
	}
}
