package ij.macro;
import ij.*;
import java.util.Hashtable;

/** An object of this type is a tokenized macro file and the associated symbol table. */
public class Program implements MacroConstants {

	private int maxSymbols = 800; // will be increased as needed
	private int maxProgramSize = 2000;  // well be increased as needed
	private int pc = -1;
	
	int stLoc = -1;
	int symTabLoc;
	Symbol[] table = new Symbol[maxSymbols];
    static Symbol[] systemTable;
	int[] code = new int[maxProgramSize];
	int[] lineNumbers = new int[maxProgramSize];
	Variable[] globals;
	boolean hasVars, hasFunctions;
	int macroCount;
    Hashtable menus;
    // run keyboard shortcut macros on event dispatch thread?
	boolean queueCommands; 
	Hashtable extensionRegistry;
	
	
	public Program() {
		if (systemTable!=null) {
			stLoc = systemTable.length - 1;
			for (int i=0; i<=stLoc; i++)
			table[i] = systemTable[i];
		} else {
			//IJ.log("make table");
			addKeywords();
			addFunctions();
			addNumericFunctions();
			addStringFunctions();
			addArrayFunctions();
			systemTable = new Symbol[stLoc+1];
			for (int i=0; i<=stLoc; i++)
				systemTable[i] = table[i];
			IJ.register(Program.class);
		}
	}
	
	public int[] getCode() {
		return code;
	}
	
	public Symbol[] getSymbolTable() {
		return table;
	}
	
	void addKeywords() {
		for (int i=0; i<keywords.length; i++)
			addSymbol(new Symbol(keywordIDs[i], keywords[i]));
	}

	void addFunctions() {
		for (int i=0; i<functions.length; i++)
			addSymbol(new Symbol(functionIDs[i], functions[i]));
	}

	void addNumericFunctions() {
		for (int i=0; i<numericFunctions.length; i++)
			addSymbol(new Symbol(numericFunctionIDs[i], numericFunctions[i]));
	}
	
	void addStringFunctions() {
		for (int i=0; i<stringFunctions.length; i++)
			addSymbol(new Symbol(stringFunctionIDs[i], stringFunctions[i]));
	}

	void addArrayFunctions() {
		for (int i=0; i<arrayFunctions.length; i++)
			addSymbol(new Symbol(arrayFunctionIDs[i], arrayFunctions[i]));
	}

	void addSymbol(Symbol sym) {
		stLoc++;
		if (stLoc==table.length) {
			Symbol[] tmp = new Symbol[maxSymbols*2];
			System.arraycopy(table, 0, tmp, 0, maxSymbols);
			table = tmp;
			maxSymbols *= 2;
		}
		table[stLoc] = sym;
	}
	
	void addToken(int tok, int lineNumber) {//n__
		pc++;
		if (pc==code.length) {
			int[] tmp = new int[maxProgramSize*2];
			System.arraycopy(code, 0, tmp, 0, maxProgramSize);
			code = tmp;

            tmp = new int[maxProgramSize*2];  //n__
			System.arraycopy(lineNumbers, 0, tmp, 0, maxProgramSize);
			lineNumbers = tmp;

			maxProgramSize *= 2;
        }
		code[pc] = tok;
        lineNumbers[pc] = lineNumber; //n__
	}

	/** Looks up a word in the symbol table. Returns null if the word is not found. */
	Symbol lookupWord(String str) {
        //IJ.log("lookupWord: "+str);
		Symbol symbol;
		String symStr;
		for (int i=0; i<=stLoc; i++) {
			symbol = table[i];
			if (symbol.type!=STRING_CONSTANT && str.equals(symbol.str)) {
				symTabLoc = i;
				return symbol;
			}
		}
		return null;
	}

	void saveGlobals(Interpreter interp) {
		//IJ.log("saveGlobals: "+interp.topOfStack);
		if (interp.topOfStack==-1)
			return;
		int n = interp.topOfStack+1;
		globals = new Variable[n];
		for (int i=0; i<n; i++)
			globals[i] = interp.stack[i];
	}
	
	public void dumpSymbolTable() {
		IJ.log("");
		IJ.log("Symbol Table");
		for (int i=0; i<=maxSymbols; i++) {
			Symbol symbol = table[i];
			if (symbol==null)
				break;
			IJ.log(i+" "+symbol);
		}
	}

	public void dumpProgram() {
		IJ.log("");
		IJ.log("Tokenized Program");
		String str;
		int token, address;
		for (int i=0; i<=pc; i++) 
			IJ.log(i+"	 "+lineNumbers[i]+"   "+(code[i]&TOK_MASK)+"   "+decodeToken(code[i]));
	}
	
	public Variable[] getGlobals() {
		return globals;
	}

	public boolean hasVars() {
		return hasVars;
	}

	public int macroCount() {
		return macroCount;
	}

	public String decodeToken(int token) {
		return decodeToken(token&TOK_MASK, token>>TOK_SHIFT);
	}

	String decodeToken(int token, int address) {
		String str;
		switch (token) {
			case WORD:
			case PREDEFINED_FUNCTION:
			case NUMERIC_FUNCTION:
			case STRING_FUNCTION:
			case ARRAY_FUNCTION:
			case USER_FUNCTION:
				str = table[address].str;
				break;
			case STRING_CONSTANT:
				str = "\""+table[address].str+"\"";
				break;
			case NUMBER:
				double v = table[address].value;
				if ((int)v==v)
					str = IJ.d2s(v,0);
				else
					str = ""+v;
				break;
			case EOF:
				str = "EOF";
				break;
			default:
				if (token<32) {
					switch (token) {
					case PLUS_PLUS:
						str="++";
						break;
					case MINUS_MINUS:
						str="--";
						break;
					case PLUS_EQUAL:
						str="+=";
						break;
					case MINUS_EQUAL:
						str="-=";
						break;
					case MUL_EQUAL:
						str="*=";
						break;
					case DIV_EQUAL:
						str="/=";
						break;
					case LOGICAL_AND:
						str="&&";
						break;
					case LOGICAL_OR:
						str="||";
						break;
					case EQ:
						str="==";
						break;
					case NEQ:
						str="!=";
						break;
					case GT:
						str=">";
						break;
					case GTE:
						str=">=";
						break;
					case LT:
						str="<";
						break;
					case LTE:
						str="<=";
						break;
					default:
						str="";
						break;
					}
				} else if (token>=200) {
					str = table[address].str;
				} else {
					char s[] = new char[1];
					s[0] = (char)token;
					str = new String(s);
				}
				break;
		}
		return str;
	}
    
    public Hashtable getMenus() {
        return menus;
    }

	// Returns 'true' if this macro program contains the specified word. */
	public boolean hasWord(String word) {
		int token, tokenAddress;
		for (int i=0; i<code.length; i++) {
			token = code[i];
			if (token<=127) continue;
			if (token==EOF) return false;
			tokenAddress = token>>TOK_SHIFT;
			String str = table[tokenAddress].str;
			if (str!=null && str.equals(word)) return true;
		}
		return false;
	}
	
} // Program
