/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package [PACKAGE];

import java.io.IOException;
import [PACKAGE].[LEXER_NAME]Exception;

public class [LEXER_NAME] {

    public static final int
        TOKEN_EOF = 0, TOKEN_AUX_NOT_FOUND = 1 [TOKENS_CONSTANTS];

    // Human representation of tokens. Useful for debug.
    // Is possible to convert a TOKEN_CONSTANT in its image through
    // [LEXER_NAME].tokenKindToString(TOKEN_CONSTANT);
    private static final String[] tokenImage = {
            "<EOF>", "<AUX_NOT_FOUND>" [TOKENS_IMAGES]
          };

    private static final char EOF_CHAR = 4;
    protected java.io.Reader inputStream;
    protected int column;
    protected int line;
    protected boolean prevCharIsCR;
    protected boolean prevCharIsLF;
    protected boolean containsEscapes;
    protected char[] buffer;
    protected int bufsize;
    protected int bufpos;
    protected int tokenBegin;
    protected int endOf_USED_Buffer;
    protected int endOf_UNUSED_Buffer;
    protected int maxUnusedBufferSize;

// ================================================================================
//  Auxiliary functions. Can parse the tokens used in the grammar as partial/auxiliary
// ================================================================================

    [LEXER_AUXFUNCTIONS]

// ================================================================================
//  Main method. Return a TOKEN_CONSTANT
// ================================================================================

    public int next() throws [LEXER_NAME]Exception, IOException {
        char currentChar = buffer[bufpos];
        while (currentChar == ' ' || currentChar=='\t' || currentChar == '\n' || currentChar=='\r')
            currentChar = readNextChar();
        tokenBegin = bufpos;
        containsEscapes = false;
        if (currentChar==EOF_CHAR) return TOKEN_EOF;

        [LEXER_LOGIC]
    }

// ================================================================================
//  Public interface
// ================================================================================

    public [LEXER_NAME](java.io.Reader stream) throws IOException {
        reInit(stream);
    }

    public [LEXER_NAME]() {
        reInit();
    }

    public void setBuffer(char[] buffer) {
        this.buffer = buffer;
        tokenBegin = bufpos = 0;
        containsEscapes = false;
        tokenBegin = -1;
    }

    public void reInit() {
        bufsize        = Integer.MAX_VALUE;
        endOf_UNUSED_Buffer = bufsize;
        endOf_USED_Buffer = bufsize;
        line           = 0;
        prevCharIsCR   = false;
        prevCharIsLF   = false;
        tokenBegin     = -1;
        maxUnusedBufferSize = bufsize;
    }

    public void reInit(java.io.Reader stream) throws IOException {
        done();
        inputStream    = stream;
        bufsize        = 4096;
        line           = 1;
        column         = 0;
        bufpos         = -1;
        endOf_UNUSED_Buffer = bufsize;
        endOf_USED_Buffer = 0;
        prevCharIsCR   = false;
        prevCharIsLF   = false;
        buffer         = new char[bufsize];
        tokenBegin     = -1;
        maxUnusedBufferSize = 4096/2;
        readNextChar();
    }

    public String getLastTokenImage() {
        if (bufpos >= tokenBegin) {
            return new String(buffer, tokenBegin, bufpos - tokenBegin);
        } else {
            return new String(buffer, tokenBegin, bufsize - tokenBegin) + new String(buffer, 0, bufpos);
        }
    }

    public void getLastTokenImage(TokenImage image) {
        if (bufpos >= tokenBegin) {
            image.reset(buffer, tokenBegin, bufpos - tokenBegin);
        } else {
            image.reset(buffer, tokenBegin, bufsize - tokenBegin, buffer, 0, bufpos);
        }
    }

    public static class TokenImage{
        private char[] buffer;
        private int begin;
        private int length;

        // used to hold circular tokens
        private char[] tmpBuffer;

        private static final double TMP_BUFFER_GROWTH = 1.5;

        public void reset(char[] buffer, int begin, int length) {
            this.buffer = buffer;
            this.begin = begin;
            this.length = length;
        }

        public void reset(char[] buffer, int begin, int length, char[] extraBuffer, int extraBegin, int extraLength) {
            ensureTmpBufferSize(length + extraLength);
            System.arraycopy(buffer, begin, tmpBuffer, 0, length);
            System.arraycopy(extraBuffer, extraBegin, tmpBuffer, length, extraLength);
            this.buffer = tmpBuffer;
            this.begin = 0;
            this.length = length + extraLength;
        }

        public char[] getBuffer() {
            return buffer;
        }

        public int getBegin() {
            return begin;
        }

        public int getLength() {
            return length;
        }


        private void ensureTmpBufferSize(int size) {
            int oldSize = tmpBuffer!=null?tmpBuffer.length:0;
            if(oldSize < size) {
                int newSize = Math.max((int)(oldSize * TMP_BUFFER_GROWTH), size);
                tmpBuffer = new char[newSize];
            }
        }

    }

    public int getColumn() {
        return column;
    }

    public int getLine() {
        return line;
    }

    public boolean containsEscapes() {
        return containsEscapes;
    }

    public static String tokenKindToString(int token) {
        return tokenImage[token];
    }

    public void done(){
        buffer = null;
    }

// ================================================================================
//  Parse error management
// ================================================================================

    protected int parseError(int ... tokens) throws [LEXER_NAME]Exception {
        StringBuilder message = new StringBuilder();
        message.append("Parse error at (").append(line).append(", ").append(column).append(")");
        if (tokens.length > 0) {
            message.append(" expecting:");
            for (int tokenId : tokens){
                message.append(" ").append([LEXER_NAME].tokenKindToString(tokenId));
            }
        }
        throw new [LEXER_NAME]Exception(message.toString());
    }

    protected void updateLineColumn(char c){
        column++;

        if (prevCharIsLF) {
            prevCharIsLF = false;
            line += (column = 1);
        } else if (prevCharIsCR) {
            prevCharIsCR = false;
            if (c == '\n') {
                prevCharIsLF = true;
            } else {
                line += (column = 1);
            }
        }

        if (c=='\r') {
            prevCharIsCR = true;
        } else if (c == '\n') {
            prevCharIsLF = true;
        }
    }

// ================================================================================
//  Read data, buffer management. It uses a circular (and expandable) buffer
// ================================================================================

    protected char readNextChar() throws IOException {
        if (++bufpos >= endOf_USED_Buffer) {
            fillBuff();
        }
        char c = buffer[bufpos];
        updateLineColumn(c);
        return c;
    }

    protected boolean fillBuff() throws IOException {
        if (endOf_UNUSED_Buffer == endOf_USED_Buffer) {
            // If no more unused buffer space
            if (endOf_UNUSED_Buffer == bufsize) {
                // If the previous unused space was at the end of the buffer
                if (tokenBegin > maxUnusedBufferSize) {
                    // If the first N bytes before the current token are enough
                    // setup buffer to use that fragment
                    bufpos = endOf_USED_Buffer = 0;
                    endOf_UNUSED_Buffer = tokenBegin;
                } else if (tokenBegin < 0){
                    // If no token yet
                    // reuse the whole buffer
                    bufpos = endOf_USED_Buffer = 0;
                } else {
                    // Otherwise expand buffer after its end
                    ExpandBuff(false);
                }
            } else if (endOf_UNUSED_Buffer > tokenBegin){
                // If the endOf_UNUSED_Buffer is after the token
                // set endOf_UNUSED_Buffer to the end of the buffer
                endOf_UNUSED_Buffer = bufsize;
            } else if ((tokenBegin - endOf_UNUSED_Buffer) < maxUnusedBufferSize) {
                // If between endOf_UNUSED_Buffer and the token there is NOT enough space expand the buffer
                // reorganizing it
                ExpandBuff(true);
            } else {
                // Otherwise there is enough space at the start
                // so we set the buffer to use that fragment
                endOf_UNUSED_Buffer = tokenBegin;
            }
        }
        int i;
        if ((i = inputStream.read(buffer, endOf_USED_Buffer, endOf_UNUSED_Buffer - endOf_USED_Buffer)) == -1) {
            inputStream.close();
            buffer[endOf_USED_Buffer] = (char)EOF_CHAR;
            endOf_USED_Buffer++;
            return false;
        } else {
            endOf_USED_Buffer += i;
        }
        return true;
    }


    protected void ExpandBuff(boolean wrapAround) {
        char[] newbuffer = new char[bufsize + maxUnusedBufferSize];

        try {
            if (wrapAround) {
                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
                System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
                buffer = newbuffer;
                endOf_USED_Buffer = (bufpos += (bufsize - tokenBegin));
            } else {
                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
                buffer = newbuffer;
                endOf_USED_Buffer = (bufpos -= tokenBegin);
            }
        } catch (Throwable t) {
            throw new Error(t.getMessage());
        }

        bufsize += maxUnusedBufferSize;
        endOf_UNUSED_Buffer = bufsize;
        tokenBegin = 0;
    }
}
