package net.sourceforge.rtf.parser;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

/**
 * Core RTF parser which is able to parse RTF Stream (Reader) character by character.
 * It launch events on special characters <b>{</b>, <b>}</b> and <b>\</b> :
 * <ul>
 * 	<li>
 * 		<b>startGroup</b> when current character parsed <b>{</b> is found. This event
 *      is start group. 
 * 	</li>
 * 	<li>
 * 		<b>endGroup</b> when current character parsed <b>}</b> is found. This event
 *      is end group. 
 * 	</li>
 * 	<li>
 * 		<b>handleKeyword</b> when current String parsed end by <b>\</b> character. This event
 *      is launching when RTF keyword is founded. 
 * 	</li>
 * </ul>
 * To use this abstract class, you must implement events 
 * <b>startGroup</b>, <b>endGroup</b> and <b>handleKeyword</b>.
 * 
 * @see net.sourceforge.rtf.handler.RTFIndentHandler
 * @see net.sourceforge.rtf.parser.AbstractDefaultRTFParser
 * @version 1.0.0
 * @author <a href="mailto:angelo.zerr@gmail.com">Angelo ZERR</a>
 */
public abstract class AbstractCoreRTFParser {

	private StringBuffer currentCharacters; // current character parsed start with \

	private int level; // current level of group

	/**
	 * Constructor.
	 * 
	 */
	public AbstractCoreRTFParser() {
		currentCharacters = new StringBuffer();
		level = 0;
	}

	/**
	 * Parse RTF Reader stream.
	 * @param reader RTF source reader to parse.
	 * @throws IOException the exception.
	 */
	public void parse(Reader reader) throws IOException {
		BufferedReader br = new BufferedReader(reader);
		int c;
		while ((c = br.read()) != -1) {
			parse((char) c);
		}
		br.close();
	}

    /**
     * Parse RTF Input stream.
     * @param inputStream RTF source reader to parse.
     * @throws IOException the exception.
     */
    public void parse(InputStream inputStream) throws IOException {
        BufferedInputStream bs = new BufferedInputStream(inputStream);
        int c;
        while ((c = bs.read()) != -1) {
            parse((char) c);
        }
        bs.close();
    }
    
	/**
	 * Launch events <b>startGroup</b>, <b>endGroup</b> and
	 * <b>handleKeyword</b> among to charcater c.
	 * @param c current character parsed.
	 * @throws IOException the exception.
	 */
	private void parse(char c) throws IOException {
		if (c == '{') {
			// Start group
			if (currentCharacters.length() > 0) {
				handleKeyword(currentCharacters.toString());
				currentCharacters = new StringBuffer();
			}
			level++;
			startGroup(c, level);
			return;
		}
		if (c == '}') {
			// End group
			if (currentCharacters.length() > 0) {
				handleKeyword(currentCharacters.toString());
				currentCharacters = new StringBuffer();
			}
			if (level == 0)
				// Start/End Group are not synchronized
				throw new IOException("Too many close-groups in RTF text");
			endGroup(c, level);
			level--;
			return;
		}
		if (c == '\\') {
			// Start RTF keyword (like \trowd, \row, ...)
			if (currentCharacters.length() > 0) {
				handleKeyword(currentCharacters.toString());
				currentCharacters = new StringBuffer();
			}
			currentCharacters.append(c);
		} else {
			currentCharacters.append(c);
		}
	}

	/**
	 * Return level of current group.
	 * @return level of current group.
	 */
	protected int getLevel() {
		return level;
	}

	// EVENTS TO IMPLEMENT
	
	// GROUP EVENT TO IMPLEMENT
	/**
	 * Event start group. This event is launched when 
	 * character <b>{</b> is found.
	 * @param startGroupCharacter start group character <b>{</b>.
	 * @param level of current group.
	 * @throws IOException the exception.
	 */
	protected abstract void startGroup(char startGroupCharacter, int level)
			throws IOException;

	/**
	 * Event end group. This event is launched when 
	 * character <b>}</b> is found.
	 * @param endGroupCharacter end group character <b>}</b>.
	 * @param level of current group.
	 * @throws IOException the exception.
	 */
	protected abstract void endGroup(char endGroupCharacter, int level)
			throws IOException;

	// HANDLE KEYWORD TO IMPLEMENT
	/**
	 * Event RTF keyword. This event is launched when 
	 * RTF content (current string parsed) end by character <b>\</b>.
	 * @param content RTF keyword.
	 * @throws IOException the exception.
	 */
	protected abstract void handleKeyword(String content) throws IOException;

}
