/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */
package net.sourceforge.pmd.lang.cpp;

import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;

/**
 * A custom {@link Reader} which completely omits C/C++ continuation character
 * sequences from an underlying reader. Specifically the sequences {@code \ \n}
 * (backslash, carriage return), or {@code \ \r \n} (backslash, line feed,
 * carriage return).
 * <p>
 * This reader exists because to modify a JavaCC lexer to understand arbitrary
 * continuations inside of any token is cumbersome, and just removing them from
 * the input entirely is easier to implement. See this discussion on the JavaCC
 * mailing list on <a href=
 * "http://java.net/projects/javacc/lists/users/archive/2005-06/message/16">line
 * continuation character</a>.
 */
public class ContinuationReader extends Reader {
    private static final int EOF = -1;
    private static final char BACKSLASH = '\\';
    private static final char CARRIAGE_RETURN = '\n';
    private static final char LINE_FEED = '\r';

    /** the original stream is wrapped in this pushback reader. */
    protected final PushbackReader in;

    /**
     * Creates a new {@link ContinuationReader} which filters the given reader.
     * @param in the given reader
     */
    public ContinuationReader(Reader in) {
        this.in = new PushbackReader(in, 2);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        int count = 0;
        while (count < len) {
            int c1 = in.read();
            if (c1 == EOF) {
                break;
            } else if (c1 == BACKSLASH) {
                int c2 = in.read();
                if (c2 == EOF) {
                    // No match
                } else if (c2 == CARRIAGE_RETURN) {
                    // Match: backslash, carriage return
                    continue;
                } else if (c2 == LINE_FEED) {
                    int c3 = in.read();
                    if (c3 == EOF) {
                        // No match
                        in.unread(c2);
                    } else if (c3 == CARRIAGE_RETURN) {
                        // Match: backslash, line feed, carriage return
                        continue;
                    } else {
                        // No match
                        in.unread(c3);
                        in.unread(c2);
                    }
                } else {
                    // No match
                    in.unread(c2);
                }
            }
            cbuf[off + count] = (char) c1;
            count++;
        }

        return count > 0 ? count : -1;
    }

    @Override
    public void close() throws IOException {
        in.close();
    }
}
