/*
 * Copyright (C) 2005, 2006 Mikhail A. Kryshen
 *
 * $Id: TemplateParser.java,v 1.1.1.1 2006/05/16 14:04:09 mikhail Exp $
 */

package kryshen.tema;

import java.io.*;
import java.util.*;

/**
 * Parser for DbReader templates.
 *
 * @author Mikhail A. Kryshen
 */
public class TemplateParser {
    static final String SUPER = "SUPER.";

    /* Brackets. */
    static final char[] BR_LEFT = {'<', '['};
    static final char[] BR_RIGHT = {'>', ']'};

    static final char BR_IN = '%';
    
    /* Separators. */
    static final char[] REC_DATA_SEPARATORS = {':'};
    static final char[] NONREC_DATA_SEPARATORS = {'\\', '`'}; 
    static final char[] LIST_SEPARATORS = {' ', '\t', '\r', '\n'};

    static enum FunctionData {
	SUBFUNCTION, RECURSIVE, NONRECURSIVE;
    };

    static enum Terminator {
	EOF, BRACKET, SEPARATOR;
    };

    static class Result {
	Terminator terminator;
	int substitutions;

        /** No text had been parsed. */
        boolean empty; 

	Result(Terminator terminator, int substitutions, boolean empty) {
            this.terminator = terminator;
            this.substitutions = substitutions;
            this.empty = empty;
        }

        Result() {
            this(null, 0, true);
        }       
    };    

    private Map<String, Function> functions = new HashMap<String, Function>();

    private Map<String, Object> variables = new HashMap<String, Object>();
    private TemplateParser superParser;

    private int termBracket = -1;

    public TemplateParser() {
        this(null);
    }

    public TemplateParser(TemplateParser superParser) {
	this.superParser = superParser;

        Functions.registerAllFunctions(this);
    }

    public void registerFunction(Function f) {
        functions.put(f.name, f);
    }

    public int parse(Reader in, Writer out) 
	throws IOException, TemplateException {
	
	return parse(in, out, true, null, null).substitutions;
    }

    /**
     *  Parse template.
     *
     * @param in Reader to read template data.
     * @param out Writer to write processed data.
     * @param recursive enables recursive processing.
     * @param separators characters which terminate parsing block.
     * @param leading characters to ignore at the beginning of the block.
     */
    Result parse(Reader in, Writer out, boolean recursive, 
                 char[] separators, char[] leading)
	throws IOException, TemplateException {

	Result result = new Result();
	int lc = -1;

	while (true) {
	    int c = in.read();
            
            if (leading != null && isSeparator(c, leading))
                continue;
            else
                leading = null;

            boolean leftBracket = false;
            int index;

            for (index = 0; index < BR_LEFT.length; index++) {
                if (lc == BR_LEFT[index]) {
                    leftBracket = true;
                    break;
                }
            }

	    if (recursive && leftBracket && c == BR_IN) {
		
		lc = -1;
                int tb = termBracket;
                termBracket = BR_RIGHT[index];
		result.substitutions += parseFunction(in, out);
                termBracket = tb;
		
	    } else if (lc == BR_IN && c == termBracket) {
		
		lc = -1;
		result.terminator = Terminator.BRACKET;
		break;
		
	    } else {

		if (lc >= 0)
		    out.write(lc);
	    
		lc = c;
	    }

	    if (c < 0) {
		result.terminator = Terminator.EOF;
		break; 
	    } else if (separators != null && isSeparator(c, separators)) {
                result.terminator = Terminator.SEPARATOR;
                break;
            }

            result.empty = false;
	}

	return result;
    }

    int parseVariable(String name, Writer out) 
	throws IOException, TemplateException {

        Object ovalue = getValue(name);
        if (ovalue == null)
            return 0;

	String svalue = ovalue.toString();

	if (svalue == null || svalue.length() == 0)
	    return 0;
       
	out.write(svalue);
	return 1;
    }

    boolean isSeparator(int c, char[] separators) {
        for (char p : separators) {
            if (c == p) return true;
        }

        return false;
    }

    int parseFunction(Reader in, Writer out)
	throws IOException, TemplateException {
        
	StringBuffer sb = new StringBuffer();
        
	while (true) {
	    int c = in.read();
            
            if (isSeparator(c, LIST_SEPARATORS)) {
		return invokeFunction(sb.toString(), 
				      FunctionData.SUBFUNCTION,
				      in, out);
            } else if (isSeparator(c, REC_DATA_SEPARATORS)) {
		return invokeFunction(sb.toString(), 
				      FunctionData.RECURSIVE,
				      in, out);
            } else if (isSeparator(c, NONREC_DATA_SEPARATORS)) {
		return invokeFunction(sb.toString(), 
				      FunctionData.NONRECURSIVE, 
				      in, out);
            } else if (c < 0) {
		System.err.println("Error: unexpected end of file.");
		return 0;
            } else {
		sb.append((char)c);
	    }
	}
    }

    private int invokeFunction(String name, FunctionData fd,
			       Reader in, Writer out)
	throws IOException, TemplateException {

        FunctionDataParser fdp = new FunctionDataParser(this, fd, in);

	if ("".equals(name)) {
            return fdp.parseData(out);
	}
        
        Function f = getFunction(name);

        if (f == null) {
	    throw new TemplateException("Unknown function: " + name);
        }

        int r = f.invoke(fdp, out);

	if (fdp.hasMoreData()) {
	    /* Skip remaining function data. */
	    fdp.parseData(new Writer() {
		    public void close() {}
		    public void flush() {}
		    public void write(char[] cbuf, int off, int len) {}
		});
	}
	
	return r;
    }

    Function getFunction(String name) throws TemplateException {
	Function function = functions.get(name);
	if (function != null) return function;   

	if (superParser != null) {
	    if (name.startsWith(SUPER))
		return superParser.getFunction(name.substring(SUPER.length()));
	    else
		return superParser.getFunction(name);
	}

	return null;
    }

    Object getValue(String name) throws TemplateException {
	Object value = variables.get(name);
	if (value != null) return value;   

	if (superParser != null) {
	    if (name.startsWith(SUPER))
		return superParser.getValue(name.substring(SUPER.length()));
	    else
		return superParser.getValue(name);
	}

	return null;
    }

    void setValue(String name, Object value) {
	variables.put(name, value);
    }

    void clearValues() {
	variables.clear();
    }

    // TEST
    public static void main(String[] args) 
	throws IOException, TemplateException {
	
	TemplateParser p = new TemplateParser();
	
	FileReader in = new FileReader("parser.in");
	FileWriter out = new FileWriter("parser.out");

	p.parse(in, out);

	in.close();
	out.close();
    }
}