package code.table;

import code.stuff.Logger;
import code.stuff.Tracer;
import code.symbols.Symbol;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * An object of this class holds all the symbols of a module.
 * Each symbol is accessed by its name or identifier.
 * <P>
 * Since symbols definitions can be mutually recursive,
 * some symbols can be referenced before they are defined.
 * Therefore, symbols can be either installed or announced.
 * Symbols are installed at the point of the definition.
 * Symbols are announced at the point of reference.
 * It is OK to annouce a symbol multiple times before
 * and/or after it is installed.
 * <P>
 * This class could be an inner class of ModuleTable.
 * It is separated to keep the size of programs small.
 * The class and all its members are not public.
 * <P>
 * The numeric ids of symbols are assigned by the class MapTable
 * which also holds the mapping from ids to symbols.
 *
 * @author Sergio Antoy
 * @since June 21, 2004
 */

class SymbolTable {

    Hashtable nameToSymbol = new Hashtable();
    /**
     * The module whose symbols this table holds.
     */
    private String moduleName;

    /**
     * A table stores both a symbol and the symbol's numeric id.
     */
    private static class Pair {
        private Symbol symbol;
        private int id;

        private Pair(Symbol symbol, int id) {
            this.symbol = symbol;
            this.id = id;
        }
    }

    SymbolTable(String moduleName) {
        this.moduleName = moduleName;
    }

    void installSymbol(Symbol symbol) {
        String symbolName = symbol.symbolName;
        Pair pair = (Pair) nameToSymbol.get(symbolName);
        if (pair == null) {
            // Not announced before
            announceSymbol(symbolName);
            installSymbol(symbol);
        } else if (pair.symbol == null) {
            // announced before
            if (Tracer.symbols)
                Logger.logln("installing symbol \"" + symbolName +
                        "\" of module \"" + moduleName + "\"" +
                        " with id " + pair.id);
            pair.symbol = symbol;
            MapTable.installSymbol(symbol, pair.id);
        } else {
            if (Tracer.symbols)
                Logger.logln("installation of symbol \"" + symbolName +
                        "\" aborted, symbol is duplicated");
            throw new DuplicatedSymbol(moduleName, symbolName);
        }
    }

    Symbol getSymbol(String symbolName) {
        Pair pair = (Pair) nameToSymbol.get(symbolName);
        if(pair!=null)
            return (Symbol) pair.symbol;
        return null;
    }

    void announceSymbol(String symbolName) {
        Pair pair = (Pair) nameToSymbol.get(symbolName);
        if (pair == null) {
            // Not announced before
            int id = MapTable.nextId();
            if (Tracer.symbols)
                Logger.logln("announcing symbol \"" + symbolName +
                        "\" of module \"" + moduleName + "\"" +
                        " with id " + id);
            nameToSymbol.put(symbolName, new Pair(null, id));
        } else
        // Announced or installed already
            if (Tracer.symbols)
                Logger.logln("installation of symbol \"" + symbolName +
                        "\" cancelled, symbol was announced earlier");

    }

    int getId(String symbolName) {
	Pair pair = (Pair) nameToSymbol.get(symbolName);
        if (pair == null) {
            // Not announced before
            announceSymbol(symbolName);
            pair = (Pair) nameToSymbol.get(symbolName);
        }
        return pair.id;
    }

    int sureGetId(String symbolName) {
	Pair pair = (Pair) nameToSymbol.get(symbolName);
        if (pair == null) 
	    throw new UndefinedSymbol(moduleName+"."+symbolName);
        return pair.id;
    }

    String getSymbolName(int id) {
	Enumeration keys = nameToSymbol.keys();
	while (keys.hasMoreElements()) {
	    String symbolName = (String) keys.nextElement();
	    Pair pair = (Pair) nameToSymbol.get(symbolName);
	    if (pair.id == id) return moduleName+"."+symbolName;
	}
	return null;
    }

    // ------------------------------------------------------------------
    // Enumerations --- should be optimized by a special purpose enumerator
    // rather than constructing a vector of all symbols.

    private Vector symbolVector() {
        Vector vector = new Vector(nameToSymbol.size());
        int i = 0;
        for (Enumeration e = nameToSymbol.elements(); e.hasMoreElements(); ++i) {
            Pair pair = (Pair) e.nextElement();
            Object symbol = pair.symbol;
            if (symbol != null) vector.add(symbol);
        }
        return vector;
    }

    Enumeration getAllSymbols() {
        return symbolVector().elements();
    }

    Enumeration getUndefinedSymbols() {
        Vector undefined = new Vector();
        Enumeration symbolKeys = nameToSymbol.keys();
        while (symbolKeys.hasMoreElements()) {
            String symbolName = (String) symbolKeys.nextElement();
            Pair pair = (Pair) nameToSymbol.get(symbolName);
            if (pair.symbol == null) undefined.add(symbolName);
        }
        return undefined.elements();
    }
}

