/*
 *  Copyright (C) 2005, 2006 Mikhail A. Kryshen
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  $Id: TemplateParser.java,v 1.11 2006/12/14 19:44:31 mikhail Exp $
 */

package kryshen.tema;

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

/**
 * Parser for TEMA templates.
 *
 * @author Mikhail A. Kryshen
 */
public class TemplateParser {
    // TODO: report non-critical errors and warnings.

    static final String SUPER_PREFIX = "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'};

    /**
     * Methods of parsing function arguments and data.
     */
    static enum FunctionData {
	SUBFUNCTION, RECURSIVE, NONRECURSIVE;
    };

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

    static class Result {
	Terminator terminator;
	int retCode;

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

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

        Result() {
            this(null, -1, true);
        }       
    };    

    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(String name, Function f) {
        variables.put(name, f);
    }

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

    /**
     *  Parse template.
     *
     * @param in TemplateReader 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(TemplateReader 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) {
		
                if (result.retCode < 0)
                    result.retCode = 0;

		lc = -1;
                int tb = termBracket;
                termBracket = BR_RIGHT[index];
		result.retCode += 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;
    }

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

        return false;
    }

    int parseFunction(TemplateReader 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) {
		throw new TemplateException("Unexpected end of file.", in);
            } else {
		sb.append((char)c);
	    }
	}
    }

    int parseValue(Object value, Writer out) throws IOException {
        if (value == null)
            return 0;

        String svalue = value.toString();
        
        if (svalue == null || svalue.length() == 0)
            return 0;

        out.write(svalue);
        return 1;
    }

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

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

	if ("".equals(name)) {
            return fdp.parseData(out);
	}
        
        Object value = getValue(name);
        int r;

        if (value instanceof Function) {            
            r = ((Function) value).invoke(fdp, out);           
        } else { 
            r = parseValue(value, 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;
    }

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

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

	return null;
    }

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

    public void clearValues() {
	variables.clear();
        Functions.registerAllFunctions(this);
    }

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

	FileWriter out = new FileWriter("parser.out");

	p.parse(in, out);

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