/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil -*-
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is the Grendel mail/news client.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are Copyright (C) 1997
 * Netscape Communications Corporation.  All Rights Reserved.
 */

package calypso.util;

/**
 * This class is stores data in a private ByteBuf appending new
 * data as needed, and returning line separated data.
 */

public class ByteLineBuffer {

    private static final boolean DEBUG = false;

    private boolean buffer_full;
    private ByteBuf buffer;
    private ByteBuf outputeol;
    private ByteBuf inputeol;

  /**
   * Constructs an empty ByteLineBuffer.
   */

    public ByteLineBuffer() {
        buffer = new ByteBuf();
        buffer_full = false;

        if (DEBUG) {
            System.err.println("BLB " + hashCode() + ": created");
        }
    }

  /**
   * Appends data to the end of the internal ByteBuf.
   * @param buf  data to append to internal buffer
   */
    public void pushBytes(ByteBuf buf) {
        if (DEBUG) {
            System.err.print("BLB " + hashCode() + ": pushBytes(\"");
            byte b[] = buf.toBytes();
            for (int i = 0; i < buf.length(); i++)
                System.err.print((b[i] == '\r' ? "\\r" :
                                  b[i] == '\n' ? "\\n" :
                                  b[i] == '\t' ? "\\t" :
                                  b[i] == '"'  ? "\\\"" :
                                  b[i] == '\\' ? "\\\\" :
                                  new String(b, i, 1)));
            System.err.print("\")\n");
        }
        buffer.append(buf);
    }

  /**
   * indicates that the last piece of data is now present.
   */

    public void pushEOF() {
        if (DEBUG) {
            System.err.println("BLB " + hashCode() + ": pushEOF()");
        }
        buffer_full = true;
    }

  /** Sets the EOL characters to look for in the incoming stream.  Setting
   * this to be null will cause it to look for any of <CR>, <LF>, or <CRLF>.
   * Note that null (the default) could cause up to one extra to be held in
   * the buffer (in the case where the last byte read from the underlying
   * stream was <CR>, and no following byte (or EOF) has yet been read.)
   */
  public void setInputEOL(ByteBuf buf) {
      Assert.Assertion(buf == null || buf.length() > 0);
      inputeol = buf;
  }

  /**
   * End of line characters <CR> & <LF> or any combination
   * will be replaced by this string if it is present. Setting
   * this to a zero length string will cause them to be stripped.
   * Setting this to null will cause the EOL characters to be passed
   * through unchanged (the default).
   * @param buf  ByteBuf representing EOL replacement string
   */

    public void setOutputEOL(ByteBuf buf) {
        outputeol = buf;
    }

  /**
   * Returns a ByteBuf with a line of data which was separated by
   * <CR><LF> or any combination.  Holds the last line in the ByteBuf
   * until pushEOF() is called.  If a ByteBuf containing data is sent in,
   * the data is removed.
   * @param buf  single line of data in a ByteBuf
   * @return true if a line was retrieved, false if not
   */

    public boolean pullLine(ByteBuf buf) {
        int position;
        int buffer_length;
        byte value;
        // Remove any data that already exists in the buffer sent in
        buf.setLength(0);

        if (buffer_full && buffer == null)
          return false;

        buffer_length = buffer.length();

        if (inputeol != null) {
            int last = buffer_length - inputeol.length() + 1;
            byte first = inputeol.byteAt(0);
            byte[] peekbuf = buffer.toBytes();
            for (position = 0 ; position < last ; position++) {
                if (peekbuf[position] == first) {
                    boolean match = true;
                    for (int j=inputeol.length() - 1 ; j>=1 ; j--) {
                        if (peekbuf[position + j] != inputeol.byteAt(j)) {
                            match = false;
                            break;
                        }
                    }
                    if (match) {
                        buf.setLength(0);
                        if (outputeol != null) {
                            buf.append(peekbuf, 0, position);
                            buf.append(outputeol);
                        } else {
                            buf.append(peekbuf, 0,
                                       position + inputeol.length());
                        }
                        buffer.remove(0, position + inputeol.length());

                        if (DEBUG) {
                            System.err.print("BLB " + hashCode() +
                                             ": EOL matched; returning: \"");
                            byte b[] = buf.toBytes();
                            for (int i = 0; i < buf.length(); i++)
                                System.err.print((b[i] == '\r' ? "\\r" :
                                                  b[i] == '\n' ? "\\n" :
                                                  b[i] == '\t' ? "\\t" :
                                                  b[i] == '"'  ? "\\\"" :
                                                  b[i] == '\\' ? "\\\\" :
                                                  new String(b, i, 1)));
                            System.err.print("\"\n");
                        }

                        return true;
                    }
                }
            }
        } else {
            // process the entire buffer, or until a newline character is hit
            for (position = 0; position < buffer_length; position++) {
                value = buffer.byteAt(position);
                if (value == '\n' || value == '\r') {

                    boolean ambiguous = true;

                    position++;
                    // got a newline, pass it
                    if (value == '\r' && position < buffer_length) {

                        if (DEBUG) {
                            System.err.println("BLB " + hashCode() +
                                             ": got \\r not at end of buffer");
                        }

                        // if we have seen \r, and we know what character
                        // lies after it, then there is no ambiguity:
                        // the linebreak is "\r" or "\r\n", and we can
                        // tell which it is.
                        //
                        ambiguous = false;

                        // if we are not replacing the EOL chars, return the
                        // existing ones
                        if (outputeol == null)
                            buf.append(value);
                        value = buffer.byteAt(position);
                        if (value == '\n') {
                            position++;
                            if (outputeol == null)
                                buf.append(value);
                        }
                    } else {

                        if (DEBUG) {
                            if (value == '\r')
                                System.err.println("BLB " + hashCode() +
                                               ": got \\r at end of buffer");
                            else
                                System.err.println("BLB " + hashCode() +
                                                   ": got \\n without \\r");
                        }

                        // If we have seen \n, then the linebreak is
                        // unambiguously "\n".  If we have seen \r, but we
                        // don't know what character follows it (because
                        // it happened to be at the end of the buffer) then
                        // the linebreak is ambiguous ("\r" or "\r\n").
                        //
                        ambiguous = (value == '\r');

                        if (outputeol == null) {
                            buf.append(value);
                        }
                    }

                    // if this is the last line in the buffer, and pushEOF()
                    // hasn't been called, and the linebreak was ambiguous,
                    // then wait for more data or a call to pushEOF() before
                    // returning this line.
                    //
                    if (position == buffer_length &&
                        !buffer_full &&
                        ambiguous) {

                        if (DEBUG) {
                            System.err.println("BLB " + hashCode() +
                                               ": waiting for next line");
                        }

                        return false;
                    }
                    // remove the string from the internal ByteBuf
                    buffer.remove(0,position);
                    // if we have a replacement EOL string, use it
                    if (outputeol != null)
                        buf.append(outputeol);

                    if (DEBUG) {
                        System.err.print("BLB " + hashCode() +
                                         ": returning line: \"");
                        byte b[] = buf.toBytes();
                        for (int i = 0; i < buf.length(); i++)
                            System.err.print((b[i] == '\r' ? "\\r" :
                                              b[i] == '\n' ? "\\n" :
                                              b[i] == '\t' ? "\\t" :
                                              b[i] == '"'  ? "\\\"" :
                                              b[i] == '\\' ? "\\\\" :
                                              new String(b, i, 1)));
                        System.err.print("\"\n");
                    }

                    return true;
                }
                else
                    // append bytes to output ByteBuf
                    buf.append(value);
            }
        }
        // process full buffer
        if (buffer_full && buffer_length > 0)
        {
            if (outputeol != null)
                buf.append(outputeol);
            buffer.remove(0,position);
            buffer.Recycle(buffer);
            buffer = null;

            if (DEBUG) {
                System.err.print("BLB " + hashCode() +
                                 ": at EOF; returning line: \"");
                byte b[] = buf.toBytes();
                for (int i = 0; i < buf.length(); i++)
                    System.err.print((b[i] == '\r' ? "\\r" :
                                      b[i] == '\n' ? "\\n" :
                                      b[i] == '\t' ? "\\t" :
                                      b[i] == '"'  ? "\\\"" :
                                      b[i] == '\\' ? "\\\\" :
                                      new String(b, i, 1)));
                System.err.print("\"\n");
            }

            return true;
        }

        // didn't get a string this time around.
        buf.setLength(0);

        if (DEBUG) {
            System.err.println("BLB " + hashCode() +
                               ": don't have a line yet");
        }

        return false;
    }

//  public static void main(String args[]) {
//
//    ByteLineBuffer blb = new ByteLineBuffer();
//    ByteBuf bb = new ByteBuf();
//    ByteBuf line_bytes = new ByteBuf(100);
//
//    bb.setLength(0);
//    bb.append("from: jwz");
//    System.err.println("\npushed \"" + bb + "\""); blb.pushBytes(bb);
//    while(blb.pullLine(line_bytes)) {
//      System.err.println(" pulled \"" + line_bytes + "\"");
//    }
//
//    bb.setLength(0);
//    bb.append("\r\n");
//    System.err.println("\npushed \"" + bb + "\""); blb.pushBytes(bb);
//    while(blb.pullLine(line_bytes)) {
//      System.err.println(" pulled \"" + line_bytes + "\"");
//    }
//
//    bb.setLength(0);
//    bb.append("content-type: text/plain");
//    System.err.println("\npushed \"" + bb + "\""); blb.pushBytes(bb);
//    while(blb.pullLine(line_bytes)) {
//      System.err.println(" pulled \"" + line_bytes + "\"");
//    }
//
//    bb.setLength(0);
//    bb.append("\r\n");
//    System.err.println("\npushed \"" + bb + "\""); blb.pushBytes(bb);
//    while(blb.pullLine(line_bytes)) {
//      System.err.println(" pulled \"" + line_bytes + "\"");
//    }
//
//    bb.setLength(0);
//    System.err.println("\npushed EOF"); blb.pushEOF();
//    while(blb.pullLine(line_bytes)) {
//      System.err.println(" pulled \"" + line_bytes + "\"");
//    }
//  }

}
