// Copyright 2001, FreeHEP.
package org.freehep.util.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * The ConditionalInputStream reads a stream and filters certain parts depending
 * of properties and statements in the input.
 * <P>
 * The following statements, all start with the
 * 
 * &at;-sign, are allowed:
 * 
 * <UL>
 * <LI><B>@ifdef property</B>, reads everything up to the next
 * @statement if the property is defined.
 *            <LI><B>@ifndef property</B>, reads everything up to the next
 * @statement if the property is not defined.
 *            <LI><B>@else</B>, corresponding else statement
 *            <LI><B>@endif</B>, corresponging endif statement
 *            </UL>
 * 
 * The
 * &at;-sign itself must be escaped by a backslash, if used in the text followed by
 *        any of the keywords described above and no action should be taken.
 * 
 * <P>
 * IMPORTANT: inherits from InputStream rather than FilterInputStream so that
 * the correct read(byte[], int, int) method is used.
 * 
 * @author Mark Donszelmann
 * @version $Id: ConditionalInputStream.java 8584 2006-08-10 23:06:37Z duns $
 */
public class ConditionalInputStream extends InputStream {

    private int[] buffer = new int[4096];

    private int index;

    private int len;

    private InputStream in;

    private Properties defines;

    private int nesting;

    private boolean[] ok = new boolean[50];

    private boolean escape;

    /**
     * Creates a Conditional Input Stream from given stream.
     * 
     * @param input stream to read from
     * @param defines set of properties to be used in ifdefs
     */
    public ConditionalInputStream(InputStream input, Properties defines) {
        super();
        in = input;
        this.defines = defines;
        nesting = 0;
        escape = false;
        index = 0;
        len = 0;
    }

    public int read() throws IOException {

        int b;
        int n;

        // read from buffer if possible
        if (index < len) {
            b = buffer[index];
            index++;
        } else {
            b = in.read();
        }

        // return if End Of Stream
        if (b < 0)
            return -1;

        // escape \@-signs
        if (b == '\\') {
            n = in.read();
            if (n == '@') {
                b = ' ';
                escape = true;
            }
            buffer[0] = n;
            index = 0;
            len = 1;
        }

        // check on @ sign
        if (b == '@') {
            if (escape) {
                escape = false;
            } else {
                // read keyword (ifdef, ifndef, else, endif
                index = 0;
                StringBuffer s = new StringBuffer();
                n = in.read();
                while ((n >= 0) && !Character.isWhitespace((char) n)) {
                    s.append((char) n);
                    buffer[index] = n;
                    n = in.read();
                    index++;
                }
                buffer[index] = n;
                index++;
                b = ' ';

                // check on keyword
                String keyword = s.toString();
                if (keyword.equals("ifdef") || keyword.equals("ifndef")) {

                    // skip whitespace and read property
                    s = new StringBuffer();
                    n = in.read();
                    while ((n >= 0) && Character.isWhitespace((char) n)) {
                        buffer[index] = n;
                        n = in.read();
                        index++;
                    }
                    while ((n >= 0) && !Character.isWhitespace((char) n)) {
                        s.append((char) n);
                        buffer[index] = n;
                        n = in.read();
                        index++;
                    }
                    buffer[index] = n;
                    index++;

                    // check on property
                    String property = s.toString();
                    if (defines.getProperty(property) != null) {
                        ok[nesting] = (nesting > 0 ? ok[nesting - 1] : true)
                                && keyword.equals("ifdef");
                    } else {
                        ok[nesting] = (nesting > 0 ? ok[nesting - 1] : true)
                                && keyword.equals("ifndef");
                    }
                    nesting++;
                    replaceBufferWithWhitespace(index);
                } else if (keyword.equals("else")) {
                    // FIXME one could have multiple elses without endifs...
                    // calculate inclusion based on ifdef nesting
                    if (nesting <= 0)
                        throw new RuntimeException(
                                "@else without corresponding @ifdef");
                    ok[nesting - 1] = (nesting > 1 ? ok[nesting - 2] : true)
                            && !ok[nesting - 1];
                    replaceBufferWithWhitespace(index);
                } else if (keyword.equals("endif")) {
                    // calculate inclusion based on ifdef nesting
                    if (nesting <= 0)
                        throw new RuntimeException(
                                "@endif without corresponding @ifdef");
                    nesting--;
                    replaceBufferWithWhitespace(index);
                } else {
                    // not an known @
                    b = '@';
                }
                len = index;
                index = 0;
            }
        }

        if ((nesting > 0) && !ok[nesting - 1]) {
            if (!Character.isWhitespace((char) b))
                b = ' ';
        }
        return b & 0x00FF;
    }

    private void replaceBufferWithWhitespace(int size) {
        for (int i = 0; i < size; i++) {
            if (!Character.isWhitespace((char) buffer[i])) {
                buffer[i] = ' ';
            }
        }
    }
}
