/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: RtfElement.java 1805173 2017-08-16 10:50:04Z ssteiner $ */

package org.apache.fop.render.rtf.rtflib.rtfdoc;

/*
 * This file is part of the RTF library of the FOP project, which was originally
 * created by Bertrand Delacretaz bdelacretaz@codeconsult.ch and by other
 * contributors to the jfor project (www.jfor.org), who agreed to donate jfor to
 * the FOP project.
 */

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;

/**
 * <p>Base class for all elements of an RTF file.</p>
 *
 * <p>This work was authored by Bertrand Delacretaz (bdelacretaz@codeconsult.ch)
 * and Andreas Putz (a.putz@skynamics.com).</p>
 */
public abstract class RtfElement {
    /** Writer to be used */
    protected final Writer writer;
    /** parent element */
    protected final RtfContainer parent;
    /** attributes of the element */
    protected final RtfAttributes attrib;
    private boolean written;
    private boolean closed;
    private final int id;
    private static int idCounter;

    /** Create an RTF element as a child of given container */
    RtfElement(RtfContainer parent, Writer w) throws IOException {
        this(parent, w, null);
    }

    /** Create an RTF element as a child of given container with given attributes */
    RtfElement(RtfContainer parent, Writer w, RtfAttributes attr) throws IOException {
        // @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
        id = idCounter++;
        this.parent = parent;
        attrib = (attr != null ? attr : new RtfAttributes());
        if (this.parent != null) {
            this.parent.addChild(this);
        }
        writer = w;
        written = false;
    }

    /**
     * Does nothing, meant to allow elements to write themselves without waiting
     * for write(), but not implemented yet
     * @throws IOException for I/O problems
     */
    public final void close() throws IOException {
        closed = true;
    }

    /**
     * Write the RTF code of this element to our Writer
     * @throws IOException for I/O problems
     */
    public final void writeRtf() throws IOException {
        if (!written) {
            written = true;
            if (okToWriteRtf()) {
                writeRtfPrefix();
                writeRtfContent();
                writeRtfSuffix();
            }
        }
    }

    /**
     * Starts a new line in the RTF file being written. This is only to format
     * the RTF file itself (for easier debugging), not its content.
     * @throws IOException in case of an I/O problem
     */
    public void newLine() throws IOException {
        writer.write("\n");
    }

    /**
     * Write an RTF control word to our Writer
     * @param word RTF control word to write
     * @throws IOException for I/O problems
     */
    protected final void writeControlWord(String word)
    throws IOException {
        writer.write('\\');
        writer.write(word);
        writer.write(' ');
    }

    /**
     * Write an RTF control word to our Writer, preceeded by a star '*'
     * meaning "ignore this if you don't know what it means"
     * @param word RTF control word to write
     * @throws IOException for I/O problems
     */
    protected final void writeStarControlWord(String word)
    throws IOException {
        writer.write("\\*\\");
        writer.write(word);
        writer.write(' ');
    }

    /**
     * Same as writeStarControlWord(String word), except with no space behind it
     * @param word RTF control word to write
     * @throws IOException for I/O problems
     */
    protected final void writeStarControlWordNS(String word)
    throws IOException {
        writer.write("\\*\\");
        writer.write(word);
    }

    /**
     * Write rtf control word without the space behind it
     * @param word RTF control word to write
     * @throws IOException for I/O problems
     */
    protected final void writeControlWordNS(String word)
    throws IOException {
        writer.write('\\');
        writer.write(word);
    }

    /**
     * Called before writeRtfContent()
     * @throws IOException for I/O problems
     */
    protected void writeRtfPrefix() throws IOException {
    }

    /**
     * Must be implemented to write RTF content to m_writer
     * @throws IOException for I/O problems
     */
    protected abstract void writeRtfContent() throws IOException;

    /**
     * Called after writeRtfContent()
     * @throws IOException for I/O problems
     */
    protected void writeRtfSuffix() throws IOException {
    }

    /**
     * Write a start or end group mark
     * @param isStart set to true if this is a start mark
     * @throws IOException for I/O problems
     */
    protected final void writeGroupMark(boolean isStart)
    throws IOException {
        writer.write(isStart ? "{" : "}");
    }

    /**
     * Write given attribute values to our Writer
     * @param attr RtfAttributes to be written
     * @param nameList if given, only attribute names from this list are considered
     * @throws IOException for I/O problems
     */
    protected void writeAttributes(RtfAttributes attr, String [] nameList)
    throws IOException {
        if (attr == null) {
            return;
        }

        if (nameList != null) {
            // process only given attribute names
            for (final String name : nameList) {
                if (attr.isSet(name)) {
                    writeOneAttribute(name, attr.getValue(name));
                }
            }
        } else {
            // process all defined attributes
            for (Iterator it = attr.nameIterator(); it.hasNext();) {
                final String name = (String)it.next();
                if (attr.isSet(name)) {
                    writeOneAttribute(name, attr.getValue(name));
                }
            }
        }
    }

    /**
     * Write one attribute to our Writer
     * @param name name of attribute to write
     * @param value value of attribute to be written
     * @throws IOException for I/O problems
     */
    protected void writeOneAttribute(String name, Object value)
    throws IOException {
        String cw = name;
        if (value instanceof Integer) {
            // attribute has integer value, must write control word + value
            cw += value;
        } else if (value instanceof String) {
            cw += value;
        } else if (value instanceof RtfAttributes) {
            writeControlWord(cw);
            writeAttributes((RtfAttributes) value, null);
            return;
        }
        writeControlWord(cw);
    }

    /**
     * Write one attribute to our Writer without a space
     * @param name name of attribute to write
     * @param value value of attribute to be written
     * @throws IOException for I/O problems
     */
    protected void writeOneAttributeNS(String name, Object value)
    throws IOException {
        String cw = name;
        if (value instanceof Integer) {
            // attribute has integer value, must write control word + value
            cw += value;
        } else if (value instanceof String) {
            cw += value;
        } else if (value instanceof RtfAttributes) {
            writeControlWord(cw);
            writeAttributes((RtfAttributes) value, null);
            return;
        }
        writeControlWordNS(cw);
    }

    /**
     * can be overridden to suppress all RTF output
     * @return true if this object can be written into the RTF
     */
    protected boolean okToWriteRtf() {
        return true;
    }

    /** debugging to given PrintWriter */
    void dump(Writer w, int indent)
    throws IOException {
        for (int i = 0; i < indent; i++) {
            w.write(' ');
        }
        w.write(this.toString());
        w.write('\n');
        w.flush();
    }

    /**
     * minimal debugging display
     * @return String representation of object
     */
    public String toString() {
        return (this == null) ? "null" : (this.getClass().getName() + " #" + id);
    }

    /** true if close() has been called */
    boolean isClosed() {
        return closed;
    }

    /** access our RtfFile, which is always the topmost parent */
    RtfFile getRtfFile() {
        // go up the chain of parents until we find the topmost one
        RtfElement result = this;
        while (result.parent != null) {
            result = result.parent;
        }

        // topmost parent must be an RtfFile
        // a ClassCastException here would mean that the parent-child structure is not as expected
        return (RtfFile)result;
    }

    /** find the first parent where c.isAssignableFrom(parent.getClass()) is true
     *  @return null if not found
     */
    public RtfElement getParentOfClass(Class c) {
        RtfElement result = null;
        RtfElement current = this;
        while (current.parent != null) {
            current = current.parent;
            if (c.isAssignableFrom(current.getClass())) {
                result = current;
                break;
            }
        }
        return result;
    }

    /**
     * @return true if this element would generate no "useful" RTF content
     */
    public abstract boolean isEmpty();

    /**
     * Make a visible entry in the RTF for an exception
     * @param ie Exception to flag
     * @throws IOException for I/O problems
     */
    protected void writeExceptionInRtf(Exception ie)
    throws IOException {
        writeGroupMark(true);
        writeControlWord("par");

        // make the exception message stand out so that the problem is visible
        writeControlWord("fs48");
//        RtfStringConverter.getInstance().writeRtfString(m_writer,
//                JForVersionInfo.getShortVersionInfo() + ": ");
        RtfStringConverter.getInstance().writeRtfString(writer, ie.getClass().getName());

        writeControlWord("fs20");
        RtfStringConverter.getInstance().writeRtfString(writer, " " + ie.toString());

        writeControlWord("par");
        writeGroupMark(false);
    }

    /**
     * Added by Normand Masse
     * Used for attribute inheritance
     * @return RtfAttributes
     */
    public RtfAttributes getRtfAttributes() {
        return attrib;
    }
}
