/*
 * @(#)ByteBufferTokenizer.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */

package com.jscape.util;

import java.util.Enumeration;

/**
 * Class for parsing arrays of bytes.
 */
public class ByteBufferTokenizer
        implements Enumeration {

    private ByteBuffer buffer = null;
    private byte[] data = null;

    private static final byte SPACE = 32;
    private static final byte TAB = 11;
    private static final byte LF = 10;
    private static final byte CR = 13;
    private static final byte FF = 12;
    private int pos = 0;
    private byte[] delimiter = { SPACE, TAB, LF, CR, FF };
    private boolean matchAll = false;
    private boolean skip = true;

    /**
     * Creates a new ByteBufferTokenizer instance.
     *
     * @param buffer a <code>ByteBuffer</code>
     */
    public ByteBufferTokenizer(ByteBuffer buffer) {
        this.buffer = buffer;
        this.data = buffer.toByteArray();
    }

    /**
     * Creates a new ByteBufferTokenizer instance.
     *
     * @param buffer    a <code>ByteBuffer</code>
     * @param delimiter an array of byte values to use as delimiters
     */
    public ByteBufferTokenizer(ByteBuffer buffer, byte[] delimiter) {
        this(buffer);
        this.delimiter = delimiter;
    }

    /**
     * Creates a new ByteBufferTokenizer instance.
     *
     * @param buffer    a <code>ByteBuffer</code>
     * @param delimiter an array of byte values to use as delimiters
     * @param matchAll  specified if all bytes in delimiter must match in sequence for tokenization
     */
    public ByteBufferTokenizer(ByteBuffer buffer,
                               byte[] delimiter,
                               boolean matchAll) {
        this(buffer, delimiter);
        this.matchAll = matchAll;
    }

    /**
     * Creates a new ByteBufferTokenizer instance.
     *
     * @param buffer    a <code>ByteBuffer</code>
     * @param delimiter an array of byte values to use as delimiters
     * @param matchAll  specified if all bytes in delimiter must match in sequence for tokenization
     */
    public ByteBufferTokenizer(ByteBuffer buffer,
                               byte[] delimiter,
                               boolean matchAll,
                               boolean skip) {
        this(buffer, delimiter, matchAll);
        this.skip = skip;
    }

    public int getPosition() {
        return pos;
    }

    public int getLength() {
        return data.length;
    }

    /**
     * Test if this has more elements
     *
     * @return true if more elements exist, false otherwise.
     */
    public boolean hasMoreElements() {
        return pos < data.length;
    }

    /**
     * Gets next element from this Enumeration
     *
     * @return the next element
     */
    public Object nextElement() {
        return nextElement(true);
    }

    /**
     * Gets next element from this Enumeration
     *
     * @param increment if offset should be incremented
     *
     * @return the next element as a <code>ByteBuffer</code>
     */
    private ByteBuffer nextElement(boolean increment) {
        ByteBuffer next = new ByteBuffer();
        if (!matchAll) {
            // match 1 byte at a time
            for (int i = pos;
                 i < data.length;
                 ++i) {
                // get data
                byte b = data[i];

                // increment offset
                if (increment) {
                    pos++;
                }

                // check if byte is a delimiter
                if (isDelimiter(b)) {
                    break;
                } else {
                    next.append(b);
                }
            }
        } else {

            // calculate the number of data chunks

            int index = buffer.indexOf(delimiter, pos);
            if (index != -1) {
                next = buffer.getBuffer(pos, index);
                if (increment) {
                    pos = index + delimiter.length;
                }
            } else {
                next = buffer.getBuffer(pos);
                pos = data.length;
            }
        }

        if (next.length() == 0) {
            if (skip) {
                if (pos >= data.length) {
                    return next;
                } else {
                    return nextElement(increment);
                }
            } else {
                return next;
            }
        } else {
            return next;
        }
    }

    /**
     * Checks if byte matches any of those specified in delimiter
     *
     * @param data the byte to check against
     *
     * @return <code>true</code> if data is a valid delimiter, <code>false</code> otherwise
     */
    private boolean isDelimiter(byte data) {
        boolean isDelimiter = false;
        for (int i = 0;
             i < delimiter.length;
             ++i) {
            byte b = delimiter[i];
            if (b == data) {
                isDelimiter = true;
                break;
            }
        }
        return isDelimiter;
    }

    /**
     * Gets the next token from this tokenizer.
     *
     * @return a <code>ByteBuffer</code> containing token data
     */
    public ByteBuffer nextToken() {
        return nextElement(true);
    }

    /**
     * Calculates the number of times that this tokenizer's nextToken method can be called before it generates an exception.
     * The current offset is not advanced.
     *
     * @return the number of tokens remaining in the string using the current delimiter set.
     */
    public int countTokens() {
        int count = 0;
        ByteBuffer next = null;

        // keep track of current offset
        int currentPosition = pos;

        while (true) {
            // to prevent never ending loop
            next = nextElement(true);
            byte[] data = next.toByteArray();
            if (data.length == 0) {
                break;
            } else {
                count++;
            }
        }

        // reset offset to currentPosition
        pos = currentPosition;
        return count;
    }

}
