/* ------------------------------
 * CSVTokenizer.java
 * ------------------------------
 * (C)opyright 2003, abupon (Manabu Hashimoto)
 * This class is based on the CSV tokenizer found at
 * http://sourceforge.net/projects/csvtokenizer/
 */

package jp.riken.brain.ni.samuraigraph.base;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 * The csv tokenizer class allows an application to break a Comma Separated
 * Value format into tokens. The tokenization method is much simpler than the
 * one used by the <code>StringTokenizer</code> class. The
 * <code>CSVTokenizer</code> methods do not distinguish among identifiers,
 * numbers, and quoted strings, nor do they recognize and skip comments.
 * <p>
 * The set of separator (the characters that separate tokens) may be specified
 * either at creation time or on a per-token basis.
 * <p>
 * A <tt>CSVTokenizer</tt> object internally maintains a current position
 * within the string to be tokenized. Some operations advance this current
 * position past the characters processed.
 * <p>
 * A token is returned by taking a substring of the string that was used to
 * create the <tt>CSVTokenizer</tt> object.
 * <p>
 * The following is one example of the use of the tokenizer. The code:
 * <blockquote>
 * 
 * <pre>
 * CSVTokenizer csvt = new CSVTokenizer(&quot;this,is,a,test&quot;);
 * while (csvt.hasMoreTokens()) {
 * 	println(csvt.nextToken());
 * }
 * </pre>
 * 
 * </blockquote>
 * <p>
 * prints the following output: <blockquote>
 * 
 * <pre>
 *  
 *       this
 *       is
 *       a
 *       test
 *   
 * </pre>
 * 
 * </blockquote>
 * 
 * @author abupon
 * @version
 * @see
 * @since
 */
public class SGCSVTokenizer implements Enumeration {

	private String record;

	private int currentIndex;

	private static final String DOUBLE_QUATE = "\""; //$NON-NLS-1$

	private static final String WHITE_SPACE = " \t\n\r\f,"; //$NON-NLS-1$

	private static final String SEPARATOR_COMMA = ","; //$NON-NLS-1$

	private static final int SEPARATOR_LEN = 1;

	private static final int DOUBLE_QUATE_LEN = 1;

	private ArrayList mWhiteSpaceList = new ArrayList();

	private boolean is_csv_mode = false;

	private boolean is_comment_line = false;

	/**
	 * Constructs a csv tokenizer for the specified string.
	 * <code>theSeparator</code> argument is the separator for separating
	 * tokens.
	 * 
	 * @param aString
	 *            a string to be parsed.
	 * @param isDataFile
	 *            a data file reading flag
	 */
	public SGCSVTokenizer(final String aString, final boolean isDataFile) {
		this.record = aString.trim();
		this.currentIndex = 0;
		for (int ii = 0; ii < WHITE_SPACE.length(); ii++)
			this.mWhiteSpaceList.add(new Character(WHITE_SPACE.charAt(ii)));
		// check comment line
		if (isDataFile && this.record.startsWith("#")) //$NON-NLS-1$
			this.is_comment_line = true;
		// check camma separated mode
		char c;
		boolean in_quote = false;
		for (int ii = 0; ii < this.record.length(); ii++) {
			c = this.record.charAt(ii);
			if (in_quote) {
				if (c == '"')
					in_quote = false;
			} else {
				if (c == '"') {
					in_quote = true;
				} else if (c == ',') {
					this.is_csv_mode = true;
				}
			}
		}
	}

	/**
	 * Tests if there are more tokens available from this tokenizer's string. If
	 * this method returns <tt>true</tt>, then a subsequent call to
	 * <tt>nextToken</tt> with no argument will successfully return a token.
	 * 
	 * @return <code>true</code> if and only if there is at least one token in
	 *         the string after the current position; <code>false</code>
	 *         otherwise.
	 */
	public boolean hasMoreTokens() {
		if (this.is_comment_line)
			return false;
		return (this.currentIndex >= 0);
	}

	/**
	 * Returns the next token from this string tokenizer.
	 * 
	 * @return the next token from this string tokenizer.
	 * @exception NoSuchElementException
	 *                if there are no more tokens in this tokenizer's string.
	 * @exception IllegalArgumentException
	 *                if given parameter string format was wrong
	 */
	public String nextToken() throws NoSuchElementException,
	IllegalArgumentException {
		String token = null;
		int start;
		int end;
		if (!this.hasMoreTokens()) {
			throw new NoSuchElementException();
		}
		if (this.record.startsWith(SGCSVTokenizer.DOUBLE_QUATE,
				this.currentIndex)) {
			String rec = this.record.substring(this.currentIndex
					+ SGCSVTokenizer.DOUBLE_QUATE_LEN);
			token = ""; //$NON-NLS-1$
			for (;;) {
				end = rec.indexOf(SGCSVTokenizer.DOUBLE_QUATE);
				if (end < 0) {
					throw new IllegalArgumentException("Illegal format"); //$NON-NLS-1$
				}
				if (!rec.startsWith(SGCSVTokenizer.DOUBLE_QUATE, end + 1)) {
					token = token + rec.substring(0, end);
					break;
				}
				token = token + rec.substring(0, end + 1);
				rec = rec.substring(end + SGCSVTokenizer.DOUBLE_QUATE_LEN * 2);
				this.currentIndex++;
			}
			// don't trim string
			this.currentIndex += (token.length()
					+ SGCSVTokenizer.DOUBLE_QUATE_LEN * 2 + SGCSVTokenizer.SEPARATOR_LEN);
			if (!this.is_csv_mode) {
				this.currentIndex = nextTokenIndexOf(this.currentIndex);
			}
			if (this.currentIndex >= this.record.length())
				this.currentIndex = -1;
		} else {
			start = this.currentIndex;
			if (this.is_csv_mode)
				end = this.record.indexOf(SEPARATOR_COMMA, this.currentIndex);
			else
				end = nextSeparatorIndexOf(this.currentIndex);
			if (end >= 0) {
				token = this.record.substring(start, end);
				if (this.is_csv_mode) {
					this.currentIndex = end + SEPARATOR_LEN;
				} else {
					this.currentIndex = nextTokenIndexOf(end);
					if (this.currentIndex == this.record.length())
						this.currentIndex = -1;
				}
			} else {
				// end of line reached
				if (this.currentIndex == this.record.length())
					token = ""; //$NON-NLS-1$
				else
					token = this.record.substring(start);
				this.currentIndex = -1;
			}
			token = token.trim();
		}
		return token;
	}

	private int nextSeparatorIndexOf(int fromIndex) {
		char c;
		int cnt = 0;
		int ii;
		int len = this.record.length();
		if (len == fromIndex)
			return -1;
		for (ii = fromIndex; ii < len; ii++) {
			c = this.record.charAt(ii);
			if (this.mWhiteSpaceList.contains(new Character(c)))
				break;
			cnt++;
		}
		if (ii == len)
			return -1;
		return cnt + fromIndex;
	}

	private int nextTokenIndexOf(int fromIndex) {
		char c;
		int cnt = 0;
		int len = this.record.length();
		for (int ii = fromIndex; ii < len; ii++) {
			c = this.record.charAt(ii);
			if (!this.mWhiteSpaceList.contains(new Character(c))) {
				break;
			}
			cnt++;
		}
		return cnt + fromIndex;
	}

	/**
	 * Returns the same value as the <code>hasMoreTokens</code> method. It
	 * exists so that this class can implement the <code>Enumeration</code>
	 * interface.
	 * 
	 * @return <code>true</code> if there are more tokens; <code>false</code>
	 *         otherwise.
	 * @see java.util.Enumeration
	 * @see java.util.SGCSVTokenizer#hasMoreTokens()
	 */
	public boolean hasMoreElements() {
		return hasMoreTokens();
	}

	/**
	 * Returns the same value as the <code>nextToken</code> method, except
	 * that its declared return value is <code>Object</code> rather than
	 * <code>String</code>. It exists so that this class can implement the
	 * <code>Enumeration</code> interface.
	 * 
	 * @return the next token in the string.
	 * @exception NoSuchElementException
	 *                if there are no more tokens in this tokenizer's string.
	 * @see java.util.Enumeration
	 * @see java.util.SGCSVTokenizer#nextToken()
	 */
	public Object nextElement() {
		return nextToken();
	}

	// public static void main(String[] args) {
	// int i = 1;
	// String str;
	// String expect;
	// String result;

	// str = "1, \t 2, \"\" 3, 4, \"a, \"\"\\hoge\"";
	// str = "1, \t 2, \"\" 3, b 4, a hoge";
	// System.out.println("String : [" + str + "]");
	// SGCSVTokenizer csvt = new SGCSVTokenizer(str, true);
	// i = 1;
	// while (csvt.hasMoreTokens()) {
	// try {
	// expect = String.valueOf(i++);
	// result = csvt.nextToken();
	// System.out.print(expect + ": [");
	// System.out.println(result + "]");
	// } catch (NoSuchElementException e) {
	// e.printStackTrace();
	//	System.exit(-1);
	//	}
	//	}

	//	}

}
