/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Intalio, Inc.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Intalio, Inc. Exolab is a registered
 *    trademark of Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2000 (C) Intalio Inc. All Rights Reserved.
 *
 * $Id: InputCharStream.java 5962 2006-06-03 20:37:15Z rjoachim $
 */

package org.exolab.castor.xml.dtd.parser;

/**
 * An implementation of interface
 * {@link org.exolab.castor.xml.dtd.parser.CharStream CharStream}.
 * Implements input character stream
 * that maintains line and column number positions of the characters.
 * It also has the capability to backup the stream to some extent.<br>
 * The object of this class is constructed using
 * {@link java.io.Reader java.io.Reader} <tt>reader</tt> and it is left to
 * constructor of the <tt>reader</tt> to set up character encoding correctly.
 * This means that method <u><font color="blue">read</font></u> of
 * the <tt>reader</tt> is used to get next characters, assuming it returns
 * appropriate values. It is recommended to use class
 * {@link java.io.InputStreamReader java.io.InputStreamReader}
 * as a <tt>reader</tt>, which allows to set desired character encoding.
 * This class is an intermediate component between input
 * character reader and the parser.<br>
 * The code of this class is based on the class <b>ASCII_CharStream</b> - implementation
 * of interface
 * {@link org.exolab.castor.xml.dtd.parser.CharStream CharStream},
 * that JavaCC would have generated with
 * the following options set in a JavaCC grammar file: <pre>
 *    JAVA_UNICODE_ESCAPE = false;
 *    UNICODE_INPUT = false;
 *    USER_CHAR_STREAM = false; </pre>
 * Note that this class is not fully JavaCC generated.<br>
 * @author <b>JavaCC</b>, <a href="mailto:totok@intalio.com">Alexander Totok</a>
 * @version $Revision: 5962 $ $Date: 2003-03-03 00:05:44 -0700 (Mon, 03 Mar 2003) $
 */
public final class InputCharStream implements CharStream {

   public static final boolean staticFlag = false;
   int bufsize;
   int available;
   int tokenBegin;
   public int bufpos = -1;
   private int bufline[];
   private int bufcolumn[];

   private int column = 0;
   private int line = 1;

   private boolean prevCharIsCR = false;
   private boolean prevCharIsLF = false;

   private java.io.Reader inputStream;

   private char[] buffer;
   private int maxNextCharInd = 0;
   private int inBuf = 0;

   private final void ExpandBuff(boolean wrapAround) {

      char[] newbuffer = new char[bufsize + 2048];
      int newbufline[] = new int[bufsize + 2048];
      int newbufcolumn[] = new int[bufsize + 2048];

      try {
         if (wrapAround) {
            System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
            System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
            buffer = newbuffer;

            System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
            System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
            bufline = newbufline;

            System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
            System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
            bufcolumn = newbufcolumn;

            maxNextCharInd = (bufpos += (bufsize - tokenBegin));

         } else {

            System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
            buffer = newbuffer;

            System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
            bufline = newbufline;

            System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
            bufcolumn = newbufcolumn;

            maxNextCharInd = (bufpos -= tokenBegin);
         }
      }
      catch (Throwable t) {
         throw new Error(t.getMessage());
      }

      bufsize += 2048;
      available = bufsize;
      tokenBegin = 0;
   }

   private final void FillBuff() throws java.io.IOException {
      if (maxNextCharInd == available) {
         if (available == bufsize) {
            if (tokenBegin > 2048) {
               bufpos = maxNextCharInd = 0;
               available = tokenBegin;
            } else if (tokenBegin < 0) bufpos = maxNextCharInd = 0;
            else ExpandBuff(false);
         } else if (available > tokenBegin) available = bufsize;
         else if ((tokenBegin - available) < 2048) ExpandBuff(true);
         else available = tokenBegin;
      }

      int i;

      try {
         if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) {
            inputStream.close();
            throw new java.io.IOException();
         }
         maxNextCharInd += i;
         return;
      } catch(java.io.IOException e) {
         --bufpos;
         backup(0);
         if (tokenBegin == -1) tokenBegin = bufpos;
         throw e;
      }
   }

   public final char BeginToken() throws java.io.IOException {

      tokenBegin = -1;
      char c = readChar();
      tokenBegin = bufpos;
      return c;
   }

   private final 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);
      }

      switch (c) {
         case '\r' :
            prevCharIsCR = true;
            break;
         case '\n' :
            prevCharIsLF = true;
            break;
         case '\t' :
            column--;
            column += (8 - (column & 07));
            break;
         default :
            break;
      }

      bufline[bufpos] = line;
      bufcolumn[bufpos] = column;
   }

   /**
    * Returns the next character from the input stream. The only method whose
    * implementation is different from its original in the ASCII_CharStream class.
    */
   public final char readChar() throws java.io.IOException {

      if (inBuf > 0) {
         --inBuf;

         //----------------------------------------------------------------------
         // the next line was changed, the original line generated by JavaCC was:
         // return (char)((char)0xff & buffer[(bufpos == bufsize - 1) ? (bufpos = 0) : ++bufpos]);
         //----------------------------------------------------------------------
         return buffer[(bufpos == bufsize - 1) ? (bufpos = 0) : ++bufpos];
      }
      if (++bufpos >= maxNextCharInd) FillBuff();

      //----------------------------------------------------------------------
      // the next line was changed, the original line generated by JavaCC was:
      // char c = (char)((char)0xff & buffer[bufpos]);
      //----------------------------------------------------------------------
      char c = buffer[bufpos];

      UpdateLineColumn(c);
      return (c);
    }

   /**
    * @deprecated
    * @see #getEndColumn
    */
   public final int getColumn() {
      return bufcolumn[bufpos];
   }

   /**
    * @deprecated
    * @see #getEndLine
    */
   public final int getLine() {
      return bufline[bufpos];
   }

   public final int getEndColumn() {
      return bufcolumn[bufpos];
   }

   public final int getEndLine() {
      return bufline[bufpos];
   }

   public final int getBeginColumn() {
      return bufcolumn[tokenBegin];
   }

   public final int getBeginLine() {
      return bufline[tokenBegin];
   }

   public final void backup(int amount) {
      inBuf += amount;
      if ((bufpos -= amount) < 0) bufpos += bufsize;
   }

   /**
    * Constructor, allowing to specify start line and start column of the char
    * stream, and buffer size as well.
    */
   public InputCharStream(java.io.Reader dstream, int startline,
                           int startcolumn, int buffersize) {
      inputStream = dstream;
      line = startline;
      column = startcolumn - 1;
      available = bufsize = buffersize;
      buffer = new char[buffersize];
      bufline = new int[buffersize];
      bufcolumn = new int[buffersize];
   }

   /**
    * Constructor, allowing to specify start line and start column
    * of the char stream.
    */
   public InputCharStream(java.io.Reader dstream, int startline,
                                                   int startcolumn) {
      this(dstream, startline, startcolumn, 4096);
   }

   /**
    * Constructor, instantiating the char stream to begin at 1-st line and
    * 1-st column of <tt>dstream</tt>.
    */
   public InputCharStream(java.io.Reader dstream) {
      this(dstream, 1, 1);
   }

   /**
    * Reinitialization of the char stream, allowing to specify start line
    * and start column of the char stream, and buffer size as well.
    */
   public void ReInit(java.io.Reader dstream, int startline,
                             int startcolumn, int buffersize) {
      inputStream = dstream;
      line = startline;
      column = startcolumn - 1;

      if (buffer == null || buffersize != buffer.length) {
         available = bufsize = buffersize;
         buffer = new char[buffersize];
         bufline = new int[buffersize];
         bufcolumn = new int[buffersize];
      }
      prevCharIsLF = prevCharIsCR = false;
      tokenBegin = inBuf = maxNextCharInd = 0;
      bufpos = -1;
   }

   /**
    * Reinitialization of the char stream, allowing to specify start line
    * and start column of the char stream.
    */
   public void ReInit(java.io.Reader dstream, int startline,
                                              int startcolumn) {
      ReInit(dstream, startline, startcolumn, 4096);
   }

   /**
    * Reinitialization of the char stream, instantiating the char stream
    * to begin at 1-st line and 1-st column of <tt>dstream</tt>.
    */
   public void ReInit(java.io.Reader dstream) {
      ReInit(dstream, 1, 1);
   }

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

   public final char[] GetSuffix(int len) {
      char[] ret = new char[len];

      if ((bufpos + 1) >= len)
         System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
      else {
         System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
                                  len - bufpos - 1);
         System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
      }
      return ret;
   }

   public void Done() {
      buffer = null;
      bufline = null;
      bufcolumn = null;
   }

   /**
    * Method to adjust line and column numbers for the start of a token.
    */
   public void adjustBeginLineColumn(int newLine, int newCol) {
      int start = tokenBegin;
      int len;

      if (bufpos >= tokenBegin) {
         len = bufpos - tokenBegin + inBuf + 1;
      } else {
         len = bufsize - tokenBegin + bufpos + 1 + inBuf;
      }

      int i = 0, j = 0, k = 0;
      int nextColDiff = 0, columnDiff = 0;

      while (i < len &&
             bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) {
         bufline[j] = newLine;
         nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
         bufcolumn[j] = newCol + columnDiff;
         columnDiff = nextColDiff;
         i++;
      }

      if (i < len) {
         bufline[j] = newLine++;
         bufcolumn[j] = newCol + columnDiff;

         while (i++ < len) {
            if (bufline[j = start % bufsize] != bufline[++start % bufsize])
               bufline[j] = newLine++;
            else
               bufline[j] = newLine;
         }
      }

      line = bufline[j];
      column = bufcolumn[j];
   }
}
