/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2009 - Javolution (http://javolution.org/)
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.text;

import java.lang.CharSequence;
import java.text.ParsePosition;
import javolution.context.ObjectFactory;
import javolution.lang.Reusable;

/**
 * <p> This class represents a parsing cursor over characters. Cursor
 *     allows for token iterations over any {@link CharSequence}.
 *     [code]
 *     CharSequence csq = "this is a test";
 *     Cursor cursor = Cursor.newInstance();
 *     try {
 *        for (CharSequence token; (token=cursor.nextToken(csq, ' '))!= null;)
 *            System.out.println(token); 
 *     } finally {
 *         Cursor.recycle(cursor);
 *     }
 *     [/code]
 *     Prints the following output:<pre>
 *        this
 *        is
 *        a
 *        test</pre>
 *     Cursors are typically used with {@link TextFormat} instances.
 *     [code]
 *     // Parses decimal number (e.g. "xxx.xxxxxExx" or "NaN")
 *     public Decimal parse(CharSequence csq, Cursor cursor) {
 *         if (cursor.skip("NaN", csq))
 *             return Decimal.NaN;
 *         LargeInteger significand = LargeInteger.TEXT_FORMAT.parse(csq, cursor);
 *         LargeInteger fraction = cursor.skip('.', csq) ? LargeInteger.TEXT_FORMAT.parse(csq, cursor) : LargeInteger.ZERO;
 *         int exponent = cursor.skip(CharSet.valueOf('E', 'e'), csq) ? TypeFormat.parseInt(csq, 10, cursor) : 0;
 *         int fractionDigits = fraction.digitLength();
 *         return Decimal.valueOf(significand.E(fractionDigits).plus(fraction), exponent - fractionDigits);
 *     }
 *     [/code]
 * </p>
 *
 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 5.4, November 19, 2009
 */
public class Cursor extends ParsePosition implements Reusable {

    /**
     * Default constructor.
     */
    public Cursor() {
        super(0);
    }

    /**
     * Returns a factory produced instance which can be {@link #recycle recycled}
     * after usage.
     *
     * @return a recyclable instance.
     */
    public static Cursor newInstance() {
        return (Cursor) FACTORY.object();
    }
    private static final ObjectFactory FACTORY = new ObjectFactory() {

        protected Object create() {
            return new Cursor();
        }
    };

    /**
     * Recycles the specified factory {@link #newInstance() produced} cursor.
     *
     * @param cursor the cursor to recycle.
     */
    public static void recycle(Cursor cursor) {
        FACTORY.recycle(cursor);
    }

    /**
     * Returns this cursor index.
     *
     * @return the index of the next character to parse.
     */
    public final int getIndex() {
        return super.getIndex();
    }

    /**
     * Sets the cursor current index.
     *
     * @param i the index of the next character to parse.
     */
    public void setIndex(int i) {
        super.setIndex(i);
    }

    /**
     * Indicates if this cursor points to the end of the specified
     * character sequence.
     *
     * @param csq the character sequence iterated by this cursor.
     * @return <code>getIndex() &gt;= csq.length()</code>
     */
    public final boolean atEnd(CharSequence csq) {
        return getIndex() >= csq.length();
    }

    /**
     * Indicates if this cursor points to the specified character in the
     * specified character sequence.
     *
     * @param c the character to test.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>csq.charAt(this.getIndex()) == c</code>
     */
    public final boolean at(char c, CharSequence csq) {
        int i = getIndex();
        return i < csq.length() ? csq.charAt(i) == c : false;
    }

    /**
     * Indicates if this cursor points to any of the specified character in the
     * specified character sequence.
     *
     * @param charSet any of the character to test.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>csq.charAt(this.getIndex()) == c</code>
     */
    public final boolean at(CharSet charSet, CharSequence csq) {
        int i = getIndex();
        return i < csq.length() ? charSet.contains(csq.charAt(i)) : false;
    }

    /**
     * Indicates if this cursor points to the specified characters in the
     * specified sequence.
     *
     * @param str the characters to test.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>true</code> if this cursor points to the specified
     *         characters; <code>false</code> otherwise.
     */
    public final boolean at(String str, CharSequence csq) {
        int i = getIndex();
        int length = csq.length();
        for (int j = 0; j < str.length();) {
            if ((i >= length) || (str.charAt(j++) != csq.charAt(i++)))
                return false;
        }
        return true;
    }

    /**
     * Returns the next character at this cursor position.The cursor
     * position is incremented by one.
     *
     * @param csq the character sequence iterated by this cursor.
     * @return the next character this cursor points to.
     * @throws IndexOutOfBoundsException if {@link #atEnd this.atEnd(csq)}
     */
    public final char nextChar(CharSequence csq) {
        int i = getIndex();
        setIndex(i + 1);
        return csq.charAt(i);
    }

    /**
     * Moves this cursor forward until it points to a character
     * different from the specified character.
     *
     * @param c the character to skip.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>true</code> if this cursor has skipped at least one 
     *         character;<code>false</code> otherwise (e.g. end of sequence
     *         reached).
     */
    public final boolean skipAny(char c, CharSequence csq) {
        int i = getIndex();
        int n = csq.length();
        for (; (i < n) && (csq.charAt(i) == c); i++) {
        }
        if (i == getIndex())
            return false; // Cursor did not moved.
        setIndex(i);
        return true;
    }

    /**
     * Moves this cursor forward until it points to a character
     * different from any of the character in the specified set.
     * For example: [code]
     *  // Reads numbers separated by tabulations or spaces.
     *  FastTable<Integer> numbers = new FastTable<Integer>();
     *  while (cursor.skipAny(CharSet.SPACE_OR_TAB, csq)) {
     *      numbers.add(TypeFormat.parseInt(csq, cursor));
     *  }[/code]
     *
     * @param charSet the character to skip.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>true</code> if this cursor has skipped at least one
     *         character;<code>false</code> otherwise (e.g. end of sequence
     *         reached).
     */
    public final boolean skipAny(CharSet charSet, CharSequence csq) {
        int i = getIndex();
        int n = csq.length();
        for (; (i < n) && charSet.contains(csq.charAt(i)); i++) {
        }
        if (i == getIndex())
            return false; // Cursor did not moved.
        setIndex(i);
        return true;
    }

    /**
     * Moves this cursor forward only if at the specified character.
     * This method is equivalent to:
     * [code]
     *     if (at(c, csq))
     *          increment();
     * [/code]
     *
     * @param c the character to skip.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>true</code> if this cursor has skipped the specified
     *         character;<code>false</code> otherwise.
     */
    public final boolean skip(char c, CharSequence csq) {
        if (this.at(c, csq)) {
            this.increment();
            return true;
        } else {
            return false;
        }
    }

    /**
     * Moves this cursor forward only if at any of the specified character.
     * This method is equivalent to:
     * [code]
     *     if (at(charSet, csq))
     *          increment();
     * [/code]
     *
     * @param charSet holding the characters to skip.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>true</code> if this cursor has skipped any the specified
     *         character;<code>false</code> otherwise.
     */
    public final boolean skip(CharSet charSet, CharSequence csq) {
        if (this.at(charSet, csq)) {
            this.increment();
            return true;
        } else {
            return false;
        }
    }

    /**
     * Moves this cursor forward only if at the specified string.
     * This method is equivalent to:
     * [code]
     *     if (at(str, csq))
     *          increment(str.length());
     * [/code]
     *
     * @param str the string to skip.
     * @param csq the character sequence iterated by this cursor.
     * @return <code>true</code> if this cursor has skipped the specified
     *        string;<code>false</code> otherwise (e.g. end of sequence
     *         reached).
     */
    public final boolean skip(String str, CharSequence csq) {
        if (this.at(str, csq)) {
            this.increment(str.length());
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns the subsequence from the specified cursor position not holding
     * the specified character. For example:[code]
     *    CharSequence csq = "This is a test";
     *    for (CharSequence token; (token=cursor.nextToken(csq, ' '))!= null;) {
     *        System.out.println(token); // Prints one word at a time.
     *    }[/code]
     *
     * @param csq the character sequence iterated by this cursor.
     * @param c the character being skipped.
     * @return the subsequence not holding the specified character or
     *         <code>null</code> if none.
     */
    public final CharSequence nextToken(CharSequence csq, char c) {
        int n = csq.length();
        for (int i = getIndex(); i < n; i++) {
            if (csq.charAt(i) != c) {
                int j = i;
                for (; (++j < n) && (csq.charAt(j) != c);) {
                    // Loop until j at the end of sequence or at specified character.
                }
                setIndex(j);
                return csq.subSequence(i, j);
            }
        }
        setIndex(n);
        return null;
    }

    /**
     * Returns the subsequence from the specified cursor position not holding
     * any of the characters specified. For example:[code]
     *    CharSequence csq = "This is a test";
     *    for (CharSequence token; (token=cursor.nextToken(csq, CharSet.WHITESPACE))!= null;) {
     *        System.out.println(token); // Prints one word at a time.
     *    }[/code]
     *
     * @param csq the character sequence iterated by this cursor.
     * @param charSet the characters being skipped.
     * @return the subsequence not holding the specified character or
     *         <code>null</code> if none.
     */
    public final CharSequence nextToken(CharSequence csq, CharSet charSet) {
        int n = csq.length();
        for (int i = getIndex(); i < n; i++) {
            if (!charSet.contains(csq.charAt(i))) {
                int j = i;
                for (; (++j < n) && !charSet.contains(csq.charAt(j));) {
                    // Loop until j at the end of sequence or at specified characters.
                }
                setIndex(j);
                return csq.subSequence(i, j);
            }
        }
        setIndex(n);
        return null;
    }

    /**
     * Increments the cursor index by one.
     *
     * @return <code>this</code>
     */
    public final Cursor increment() {
        return increment(1);
    }

    /**
     * Increments the cursor index by the specified value.
     *
     * @param i the increment value.
     * @return <code>this</code>
     */
    public final Cursor increment(int i) {
        setIndex(getIndex() + i);
        return this;
    }

    /**
     * Returns the string representation of this cursor.
     *
     * @return the index value as a string.
     */
    public String toString() {
        return "Index: " + getIndex();
    }

    /**
     * Indicates if this cursor is equals to the specified object.
     *
     * @return <code>true</code> if the specified object is a cursor
     *         at the same index; <code>false</code> otherwise.
     */
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (!(obj instanceof Cursor))
            return false;
        return getIndex() == ((Cursor) obj).getIndex();
    }

    /**
     * Returns the hash code for this cursor.
     *
     * @return the hash code value for this object
     */
    public int hashCode() {
        return getIndex();
    }

    /**
     * Resets this cursor instance.
     *
     * @see Reusable
     */
    public void reset() {
        super.setIndex(0);
        super.setErrorIndex(-1);
    }
}
