//Alphabet.java////////////////////////////////////////////////////////////////////////////////////
package edu.montana.cs.fafnir.cs550.hw2;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;

/**************************************************************************************************
 * Data structure holding all terminal and nonterminal symbols in the grammar, including lambda
 * but not end-of-file. Also keeps track of the start symbol. Terminals, nonterminals, and lambda
 * are all in separate namespaces. Note the alphabet always contains lambda, even if the file never
 * lists it.
 **************************************************************************************************/

final class Alphabet {
    interface Namespace {
        int TERMINAL = 1, NONTERMINAL = 2, LAMBDA = 3;
    }
    
    private HashMap terminals, nonterminals;
    private Lambda lambda;
    private Nonterminal start;
    
    
    Alphabet() {
        terminals = new HashMap();
        nonterminals = new HashMap();
        lambda = new Lambda();
        start = null;
    }
    
    
    Iterator getIterator(int namespace) throws FFException {
        if (namespace == Namespace.LAMBDA) {
            return Collections.singletonList(lambda).iterator();
        } else if (namespace == Namespace.TERMINAL) {
            return terminals.values().iterator();
        } else if (namespace == Namespace.NONTERMINAL) {
            return nonterminals.values().iterator();
        } else {
            throw new FFException("invalid namespace");
        }
    }
    
    
    Symbol getStart() {
    return start;
    }
    
    
    Lambda lambda() {
        return lambda;
    }
    
    
    Symbol lookupConstructively(String name, int namespace) throws FFException {
        /* Retrieve the named symbol in the given namespace, or create it if not found. Contrast with lookup.
         */
        if (namespace == Namespace.LAMBDA) {
            if (name.equals("lambda")) {
                return lambda;
            } else {
                throw new FFException("attempted to find non-lambda in lambda namespace");
            }
        } else if (namespace == Namespace.TERMINAL) {
            Object rawSymbol = terminals.get(name);
            if (rawSymbol != null) {
                return (Symbol) rawSymbol;
            } else {
                Symbol s = new Terminal(name);
                terminals.put(name, s);
                return s;
            }
        } else if (namespace == Namespace.NONTERMINAL) {
            Object rawSymbol = nonterminals.get(name);
            if (rawSymbol != null) {
                return (Symbol) rawSymbol;
            } else {
                Symbol s = new Nonterminal(name);
                nonterminals.put(name, s);
                return s;
            }
        } else {
            throw new FFException("invalid symbol type; neither lambda, terminal, or nonterminal");
        }
    }
    
    
    Symbol lookup(String name, int namespace) throws FFException {
        /* Retrieve the named symbol in the given namespace, or throw an exception if not found.
         * Contrast with lookupConstructively
         */
        if (namespace == Namespace.LAMBDA) {
            if (name.equals("lambda")) {
                return lambda;
            } else {
                throw new FFException("attempted to find non-lambda in lambda namespace");
            }
        } else if (namespace == Namespace.TERMINAL) {
            Object rawSymbol = terminals.get(name);
            if (rawSymbol != null) {
                return (Symbol) rawSymbol;
            } else {
                throw new FFException("could not find the terminal \"" + name + "\"");
            }
        } else if (namespace == Namespace.NONTERMINAL) {
            Object rawSymbol = nonterminals.get(name);
            if (rawSymbol != null) {
                return (Symbol) rawSymbol;
            } else {
                throw new FFException("could not find the nonterminal \"" + name + "\"");
            }
        } else {
            throw new FFException("invalid symbol type; neither lambda, terminal, or nonterminal");
        }
    }
    
    
    void makeStart(Nonterminal s) throws FFException {
        /* When assigning one symbol the start property, this takes care of un-start-ing any
         * old start symbol. Both the symbol itself and alphabet track the start symbol.
         */
        if (s instanceof Nonterminal) {
            if (start != null)
                start.setIsStart(false);
            start = s;
            start.setIsStart(true);
        } else {
            throw new FFException("a symbol can not be start if it isn't a nonterminal");
        }
    }
    
    
    public String toString() {
        StringBuffer result = new StringBuffer("alphabet:\n\n");
        result.append(lambda.toString() + '\n');
        
        for (Iterator i = terminals.values().iterator(); i.hasNext(); ) {
            result.append(((Terminal)i.next()).toString() + '\n');
        }
        for (Iterator i = nonterminals.values().iterator(); i.hasNext(); ) {
            Nonterminal non = (Nonterminal)i.next();
            if (non == start)
                result.append(non.toString() + "   START\n");
            else
                result.append(non.toString() + '\n');
        }
        
        return result.toString();
    }
}