package net.sourceforge.rtf.handler;

import java.io.IOException;
import java.util.Stack;

import net.sourceforge.rtf.IRTFDocumentParser;
import net.sourceforge.rtf.document.RTFAnnotation;
import net.sourceforge.rtf.document.RTFDocument;
import net.sourceforge.rtf.document.RTFElement;
import net.sourceforge.rtf.document.RTFEndBookmark;
import net.sourceforge.rtf.document.RTFField;
import net.sourceforge.rtf.document.RTFPage;
import net.sourceforge.rtf.document.RTFRow;
import net.sourceforge.rtf.document.RTFStartBookmark;
import net.sourceforge.rtf.document.RTFUserProperty;
import net.sourceforge.rtf.parser.AbstractDefaultRTFParser;

/**
 * RTF handler which implement AbstractDefaultRTFParser to create RTFDocument
 * element RTF of RTF stream source.
 * 
 * @see net.sourceforge.rtf.document.RTFDocument
 * @version 1.0.0
 * @author <a href="mailto:angelo.zerr@gmail.com">Angelo ZERR</a>
 */
public class RTFDocumentHandler extends AbstractDefaultRTFParser implements
        IRTFDocumentParser {

    private RTFDocument document = null; // Document RTF

    private RTFElement currentRTFElement = null; // current RTF element

    // parsed

    private int currentLevel; // level of current group

    // private RTFRow lastRTFRow = null; // last RTF row parsed
    // private int levelOfLastField = -1; // level of last RTF field

    // private RTFBookmark lastRTFBookmark = null; // last RTF bookmark parsed
    // private RTFBookmark lastRTFUserProperty = null; // last RTF user property
    // parsed

    private RTFElement lastRTFElement = null;

    // private RTFField lastRTFField = null;
    private Stack lastRTFFields = new Stack();

    private Stack levelOfLastFields = new Stack();

    // private RTFBookmark lastRTFBookmark = null;

    private String startGroup = null; // start group

    private int levelOfAnnotation = -1;

    /**
     * Implement startGroup event to create RTF root element RTFDocument on
     * first level and store start group character.
     * 
     * @param startGroupCharacter
     *            start group character <b>{</b>.
     * @param level
     *            of current group.
     * @throws IOException
     */
    protected void startGroup(char startGroupCharacter, int level)
            throws IOException {
        this.currentLevel = level; // store level of current group
        if (level == 1) {
            // start parsing of RTF source
            // Create RTFDocument object which is root of RTF source.
            document = new RTFDocument();
            currentRTFElement = document;
            getCurrentRTFElement().addRTFString(
                    String.valueOf(startGroupCharacter));
        } else {
            // store start group character '{'.
            if (startGroup == null)
                startGroup = String.valueOf(startGroupCharacter);
            else {
                // This case sometimes (according to MS Word version
                // comes from when there is several start group. Ex :
                /*
                 * {{\field{\*\fldinst SYMBOL 165 \\f "Wingdings" \\s
                 * 12}{\fldrslt\f14\fs24}}}
                 */
                startGroup += String.valueOf(startGroupCharacter);
            }
        }
    }

    /**
     * Implement endGroup event to add end group character to last row, last
     * bookmark... parsed otherwise into current RTF element.
     * 
     * @param endGroupCharacter
     *            end group character <b>}</b>.
     * @param level
     *            of current group.
     * @throws IOException
     */
    protected void endGroup(char endGroupCharacter, int level)
            throws IOException {
        this.currentLevel = level; // store level of current group
        if (startGroup != null) {
            // the last start group character has been not added
            // into current RTF element. Add it.
            getCurrentRTFElement().addRTFString(startGroup);
            startGroup = null;
        }
        if (lastRTFElement != null) {
            // the last RTF element (row, bookmarok, userProperty...) parsed is
            // not end by } character
            // add character into the RTF element
            lastRTFElement.addRTFString(String.valueOf(endGroupCharacter));
            lastRTFElement = null;
            return;
        }
        // Add } character into current RTF element
        getCurrentRTFElement().addRTFString(String.valueOf(endGroupCharacter));

        int size = levelOfLastFields.size();
        if (size > 0) {
            Integer levelOfLastField = (Integer) levelOfLastFields
                    .get(size - 1);
            if (levelOfLastField.intValue() == level) {
                // The current level equal to
                // level of the last RTF field parsed.
                // RTF field element must be ended
                endField();
                return;
            }
        }

        if (levelOfAnnotation == this.currentLevel) {
            endAnnotation();
        }
    }

    /**
     * Implement startRow to start and add RTF row element into current RTF
     * element parsed.
     * 
     * @param content
     *            RTF start row keyword <b>\trowd</b>.
     * @throws IOException
     */
    protected void startRow(String content) throws IOException {
        RTFRow row = null;
        boolean isNewRow = true;
        if (currentRTFElement instanceof RTFRow) {
            // current RTF element parsed id ROW
            // (Sometimes there is Row into Row. Why???)
            // new RTFRow object must not instanciate
            row = (RTFRow) currentRTFElement;
            isNewRow = false;
        } else {
            // current RTF element parsed id ROW
            // instanciate new RTFRow and add it to current element
            row = new RTFRow();
        }

        if (startGroup != null) {
            // Last character parsed is start group '{'
            // add it to RTF Row element
            row.addRTFString(startGroup);
            startGroup = null;
        }
        // Add content of row to RTF row
        row.addRTFString(content);
        if (isNewRow)
            getCurrentRTFElement().addRTFElement(row);
        // RTF ROW built is the current element.
        currentRTFElement = row;
    }

    /**
     * Implement endRow to end RTF row element into current RTF element parsed.
     * 
     * @param content
     *            RTF end row keyword <b>\row</b>.
     * @throws IOException
     */
    protected void endRow(String content) throws IOException {
        if (startGroup != null) {
            // Last character parsed is start group '{'
            // add it to RTF Row element
            getCurrentRTFElement().addRTFString(startGroup);
            startGroup = null;
        }
        // store last Row parsed
        lastRTFElement = getCurrentRTFElement();

        // Add RTF content into RTF row
        // current RTF element begins the parent
        // of RTF row
        getCurrentRTFElement().addRTFString(content);
        currentRTFElement = currentRTFElement.getRtfElementParent();
    }

    /**
     * Implement inTable - if currentElement is not row, then put it and the
     * preceeding text from the last element to a new row, and set it as current
     * element.
     * 
     * @param content
     *            RTF in table keyword <b>\intbl</b>
     * @throws IOException
     */
    protected void inTable(String content) throws IOException {
        // if it is not already in a row, put it in one.
        if (currentRTFElement instanceof RTFDocument) {
            RTFRow row = new RTFRow();
            // not checking startgroup, cause it's not necessary
            row.addRTFString(getCurrentRTFElement().removeCurrentRTFString()
                    .toString());
            getCurrentRTFElement().addRTFElement(row);
            currentRTFElement = row;
        }
        currentRTFElement.addRTFString(content);
    }

    /**
     * Implement startField to start and add RTF field element into current RTF
     * element parsed.
     * 
     * @param content
     *            RTF start field keyword <b>\field</b>.
     * @throws IOException
     */
    protected void startField(String content) throws IOException {
        RTFField field = new RTFField();
        if (startGroup != null) {
            // Last character parsed is start group '{'
            // add it to RTF Row element
            field.addRTFString(startGroup);
            startGroup = null;
        }
        field.addRTFString(content);
        RTFElement element = getCurrentRTFElement();
        if (element instanceof RTFRow) {
            // Current element is Row
            // Add Field to Row
            RTFRow rtfRow = (RTFRow) element;
            rtfRow.addRTFField(field);
        } else {
            // Add RTF field to current RTF element
            getCurrentRTFElement().addRTFElement(field);
        }
        // RTF FIELD built is the current element.
        currentRTFElement = field;
        // Store last RTF field into stack
        lastRTFFields.add(field);
        // Store level of last field into stack
        levelOfLastFields.add(new Integer(currentLevel));
    }

    /**
     * End field, compute name of field and update current RTF element to parent
     * of field/
     * 
     * @throws IOException
     */
    protected void endField() throws IOException {
        // Get last RTF field stored into stack
        RTFField field = (RTFField) lastRTFFields.pop();
        // Remove level of last fields
        levelOfLastFields.pop();
        field.getName();
        // lastRTFField = null;
        if (currentRTFElement instanceof RTFField) {
            // Current RTF element is RTF Field, get parent RTFElement
            currentRTFElement = currentRTFElement.getRtfElementParent();
        } else {
            // Current RTF element is not RTFField, but RTFBookmark
            // So the currentRTFElement must not change. This case
            // sometimes come when you have Bookmark inserted into Field
            // It's manage case like :
            /*
             * {\field\flddirty => START RTF field .... {\*\bkmkstart Texte2 =>
             * BOOKMARK } FORMTEXT } => END RTF Field
             * 
             * {\b\fs20\insrsid7366814 {\*\bkmkend Texte2 => BOOKMARK }\cell }
             */
        }
    }

    /**
     * Implement startBookmark to start and add RTF bookmark element into
     * current RTF element parsed.
     * 
     * @param content
     *            RTF start bookmark keyword <b>\bkmstart</b>.
     * @throws IOException
     */
    protected void startBookmark(String content) throws IOException {
        RTFStartBookmark bookmark = new RTFStartBookmark();
        if (startGroup != null) {
            bookmark.addRTFString(startGroup);
            startGroup = null;
        }
        bookmark.addRTFString(content);
        getCurrentRTFElement().addRTFElement(bookmark);
        lastRTFElement = bookmark;
    }

    /**
     * Implement endBookmark to end RTF bookmark element
     * 
     * @param content
     *            RTF end bookmark keyword <b>\bkmend</b>.
     * @throws IOException
     */
    protected void endBookmark(String content) throws IOException {
        RTFEndBookmark bookmark = new RTFEndBookmark();
        if (startGroup != null) {
            bookmark.addRTFString(startGroup);
            startGroup = null;
        }
        bookmark.addRTFString(content);
        getCurrentRTFElement().addRTFElement(bookmark);
        lastRTFElement = bookmark;
    }

    /**
     * Implement startPage to start and add RTF page break element into current
     * RTF element parsed.
     * 
     * @param content
     *            RTF start page keyword <b>\page</b>.
     * @throws IOException
     */
    protected void startPage(String content) throws IOException {
        RTFPage page = new RTFPage();
        page.addRTFString(content);
        currentRTFElement.addRTFElement(page);
    }

    protected void startUserProperty(String content) throws IOException {
        RTFUserProperty userProperty = new RTFUserProperty();
        if (startGroup != null) {
            userProperty.addRTFString(startGroup);
            startGroup = null;
        }
        userProperty.addRTFString(content);
        getCurrentRTFElement().addRTFElement(userProperty);
        currentRTFElement = userProperty;
    }

    protected void endUserProperty(String content) throws IOException {
        RTFUserProperty userProperty = (RTFUserProperty) currentRTFElement;
        if (startGroup != null) {
            userProperty.addRTFString(startGroup);
            startGroup = null;
        }
        userProperty.addRTFString(content);
        userProperty.getName();
        lastRTFElement = userProperty;
        currentRTFElement = currentRTFElement.getRtfElementParent();
    }

    protected void startAnnotation(String content) throws IOException {
        RTFAnnotation annotation = new RTFAnnotation();
        if (startGroup != null) {
            annotation.addRTFString(startGroup);
            startGroup = null;
        }
        annotation.addRTFString(content);
        getCurrentRTFElement().addRTFElement(annotation);
        currentRTFElement = annotation;
        levelOfAnnotation = this.currentLevel;
    }

    protected void endAnnotation() {
        levelOfAnnotation = -1;
        if (currentRTFElement instanceof RTFAnnotation)
            currentRTFElement = currentRTFElement.getRtfElementParent();
    }

    /**
     * Implement handleText to add RTF content (different of RTF keyword) into
     * current RTF element.
     * 
     * @param content
     *            RTF code.
     * @throws IOException
     */
    protected void handleText(String content) throws IOException {
        if (startGroup != null) {
            // Last character parsed is start group '{'
            // add it to RTF Row element
            getCurrentRTFElement().addRTFString(startGroup);
            startGroup = null;
        }
        getCurrentRTFElement().addRTFString(content);
    }

    /**
     * Return current RTF element parsed.
     * 
     * @return current RTF element parsed.
     * @throws IOException
     *             when current RTF element is null.
     */
    private RTFElement getCurrentRTFElement() throws IOException {
        if (currentRTFElement == null)
            throw new IOException(
                    "Current RTF element is null. RTF stream is not valid.");
        return currentRTFElement;
    }

    /**
     * Return RTFDocument populated by RTF handler.
     * 
     * @return
     */
    public RTFDocument getRTFDocument() {
        return document;
    }

}
