// $Id: Input.java,v 1.4 2004/03/05 17:05:44 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" 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.

package com.meyling.principia.io;


/**
 * This class provides convenient methods for parsing input.
 *
 * TODO offer input stream constructor
 *
 * @version $Revision: 1.4 $
 * @author  Michael Meyling
 */
public class Input {

    /** char marking end of data */
    private final static char EOF = Character.MAX_VALUE;

    /** char marking end of input line */
    private final static char CR = '\015';

    /** string for marking current reading position */
    private final static String MARKER = "<<<<";

    /** holds the data */
    private final StringBuffer source;

    /** current line */
    private StringBuffer line = new StringBuffer();

    /** current line number */
    private int lineNumber = 0;

    /** current reading position */
    private int position = 0;


// TODO
/*
    private StringBuffer savedLine = new StringBuffer();
    private int savedLineNumber = 0;
    private int savedPosition = 0;
*/


    /**
     * Constructor using <code>StringBuffer</code> source.
     *
     * @param   source  data source
     * @throws  IllegalArgumentException if argument was
     *          an nullpointer
     */
    public Input(StringBuffer source) {
        if (source == null) {
            throw new IllegalArgumentException(
                IoConstants.NULLPOINTER_PARAMETER);
        }
        this.source = source;
    }


    /**
     * Constructor using <code>String</code> source.
     *
     * @param   source  data source
     * @throws  IllegalArgumentException if argument was
     *          an nullpointer
     */
    public Input(String source) {
        if (source == null) {
            throw new IllegalArgumentException(
                IoConstants.NULLPOINTER_PARAMETER);
        }
        this.source = new StringBuffer(source);
    }


    /**
     * Reads a single character and increments the reading position
     * by one.
     *
     * @return  character read, if there are no more chars
     *          <code>Character.MAX_VALUE</code> is returned
     *
     */
    public final char readChar() {
        if (position >= source.length()) {
            return EOF;
        } else {
            char c = source.charAt(position++);
            if (c == CR) {
                lineNumber++;
                line.setLength(0);
            } else {
                line.append(c);
            }
            return c;
        }
    }


    /**
     * Reads a single character and does not change the reading
     * position.
     *
     * @return  character read, if there are no more chars
     *          <code>Character.MAX_VALUE</code> is returned
     *
     */
    public final char getChar() {
        if (position >= source.length()) {
            return EOF;
        } else {
            return source.charAt(position);
        }
    }


    /**
     * Reads a single character and does not change the reading
     * position.
     *
     * @param   i   offset from current reading position.
     * @return  character read, if there are no more chars
     *          <code>Character.MAX_VALUE</code> is returned
     *
     */
    public final char getChar(int i) {
        if (i < 0 || position + i >= source.length()) {
            return EOF;
        } else {
            return source.charAt(position + i);
        }
    }


    /**
     * Returns the current line number.
     *
     * @return  current line number
     */
    public final int getLineNumber() {
        return lineNumber;
    }


    /**
     * Skips white space, beginning from reading position.
     * Changes reading position to next non white space
     * character.
     */
    public final void skipWhiteSpace() {
        while (!isEmpty() && Character.isWhitespace(getChar())) {
            readChar();
        }
    }


    /**
     * Is there no data left for reading?
     *
     * @return  is all data read?
     */
    public final boolean isEmpty() {
        return position >= source.length();
    }


    /**
     * Reads the next string containing only letters or digits,
     * leading whitespace is skipped.
     * Changes reading position.
     *
     * @return  read string
     * @throws  IllegalArgumentException if no such characters could
     *          be found
     */
    public final String readLetterDigitString() {
        skipWhiteSpace();
        if (isEmpty() || !Character.isLetterOrDigit(getChar())) {
            readChar();     // for showing correct position
            throw new IllegalArgumentException(
                IoConstants.NO_LETTER_DIGIT_STRING_FOUND);
        }
        StringBuffer buffer = new StringBuffer();
        while (!isEmpty() && Character.isLetterOrDigit(getChar())) {
            buffer.append(readChar());
        }
        return buffer.toString();
    }


    /**
     * Reads the next positive integer made completly out of digits,
     * leading whitespace is skipped.
     * Changes reading position.
     *
     * @return  read integer
     * @throws  IllegalArgumentException if no digits where found or
     *          the number was to big for an <code>int</code>
     */
    public final int readCounter() {
        skipWhiteSpace();
        if (isEmpty()) {
            throw new IllegalArgumentException(
                IoConstants.NO_POSITIVE_INTEGER_FOUND);
        }
        if (!Character.isDigit(getChar())) {
            readChar();     // for showing correct position
            throw new IllegalArgumentException(
                IoConstants.NO_POSITIVE_INTEGER_FOUND);
        }
        StringBuffer buffer = new StringBuffer();
        while (!isEmpty() && Character.isDigit(getChar())) {
            buffer.append(readChar());
        }
        if (buffer.length() >= 2 && '0' == buffer.charAt(0)) {
            throw new IllegalArgumentException(
                IoConstants.NO_LEADING_ZERO_ALLOWED);
        }
        int i = 0;
        try {   // TODO better parse themself?
            i = Integer.parseInt(buffer.toString());
        } catch (NumberFormatException nfe) {
            throw new IllegalArgumentException(IoConstants.INTEGER_TOO_BIG);
        }
        return i;
    }


    /**
     * Reads the next quoted string, leading whitespace is skipped.
     * A correctly quoted string could be created by adding a leading and
     * a trailing quote character and doubling each other quote character.
     * The resulting string is dequoted.
     * Changes reading position.
     *
     * @return  deqoted read string
     * @throws  IllegalArgumentException if no correctly quoted
     *          string was found
     */
    public final String readQuoted() throws IllegalArgumentException {
        skipWhiteSpace();
        if (isEmpty() || readChar() != '\"') {
            throw new IllegalArgumentException(
                IoConstants.NO_QUOTED_STRING);
        }
        StringBuffer unquoted = new StringBuffer();
        char c;
        do {
            if (isEmpty()) {
                throw new IllegalArgumentException(
                    IoConstants.NO_QUOTE_TERMINATION);
            }
            c = readChar();
            if (c != '\"') {
                unquoted.append(c);
            } else {        // c == '\"'
                if (isEmpty() || getChar() != '\"') {
                    break;  // success
                } else {
                    unquoted.append(readChar());
                }
            }
        } while (true);
        return unquoted.toString();
    }


    /**
     * Show reading position.
     *
     * @return  current line with mark at current reading position
     */
    public final String showLinePosition() {
        StringBuffer buffer = new StringBuffer(line.toString())
            .append(MARKER);
        int i = 0;
        while (getChar(i) != EOF && i < 200) {
            buffer.append(getChar(i));
            i++;
        }
        i = buffer.length() - 1;
        while (i >= 0 && Character.isWhitespace(buffer.charAt(i))) {
            i--;
        }
        buffer.setLength(i + 1);
        return buffer.toString();
    }

/*
    public final void mark() {
        savedPosition = position;
        savedLineNumber = lineNumber;

        savedLine.setLength(0);
// TODO inefficent:
        savedLine.append(line.toString());
    }


    public final void reset() {
        position = savedPosition;
        lineNumber = savedLineNumber;

        line.setLength(0);
// TODO inefficent:
        line.append(savedLine.toString());
    }
*/

}
