package net.sourceforge.pmd.swingui;


import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleSet;

import java.io.OutputStream;
import java.io.PrintStream;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;


/**
 * Writes an XML file containing information about rule set and each rule within the rule set.
 *
 * @author Donald A. Leckie
 * @since August 30, 2002
 * @version $Revision$, $Date$
 */
public class RuleSetWriter {
    private PrintStream m_outputStream;
    private StringBuffer m_line = new StringBuffer(500);
    private int m_indent;

    /**
     *******************************************************************************
     *
     * @param outputStream
     */
    public RuleSetWriter(OutputStream outputStream) {
        m_outputStream = new PrintStream(outputStream);

        m_line.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
        outputLine();
    }

    /**
     *******************************************************************************
     *
     * @param ruleSet
     */
    public void write(RuleSet ruleSet) {
        // <ruleset name="xxxxxx"
        indent();
        setupNewLine();
        m_line.append("<ruleset name=\"");
        m_line.append(convertChars(ruleSet.getName()));
        m_line.append('"');

        //
        // include="true">
        //
        m_line.append(" include=\"");
        m_line.append(ruleSet.include() ? "true" : "false");
        m_line.append("\">");
        outputLine();

        // <description>
        // xxxxxxxxx
        // </description>
        indent();
        writeDescription(ruleSet.getDescription());
        outdent();

        //
        // Write each rule.
        //
        Iterator rules = ruleSet.getRules().iterator();

        while (rules.hasNext()) {
            write((Rule) rules.next());
        }

        // </ruleset>
        setupNewLine();
        m_line.append("</ruleset>");
        outputLine();
        outdent();
    }

    /**
     *******************************************************************************
     *
     * @param rule
     */
    private void write(Rule rule) {
        // Write a blank line to separate rules for easier reading.
        m_outputStream.println("");

        // <rule name="xxxxxx"
        indent();
        setupNewLine();
        m_line.append("<rule name=\"");
        m_line.append(convertChars(rule.getName()));
        m_line.append('"');
        outputLine();

        // message="xxxxxx"
        m_indent += 6;
        setupNewLine();
        m_line.append("message=\"");
        m_line.append(convertChars(rule.getMessage()));
        m_line.append('"');
        outputLine();

        // class="xxxx"
        setupNewLine();
        m_line.append("class=\"");
        m_line.append(rule.getClass().getName());
        m_line.append('"');
        outputLine();

        // include="true">
        setupNewLine();
        m_line.append("include=\"");
        m_line.append(rule.include() ? "true" : "false");
        m_line.append("\">");
        outputLine();
        m_indent -= 6;

        // <description>
        // xxxxxxxxx
        // </description>
        indent();
        writeDescription(rule.getDescription());
        outdent();

        // <example>
        // xxxxxxxxxxx
        // </example>
        indent();
        writeExample(rule.getExample());
        outdent();

        // <priority>
        // xxx
        // </priority>
        indent();
        writePriority(rule.getPriority());
        outdent();

        // <properties>
        // <property name="xxxx" value="yyyyy" />
        // </properties>
        indent();
        writeProperties(rule);
        outdent();

        // </ruleset>
        setupNewLine();
        m_line.append("</rule>");
        outputLine();
        outdent();
    }

    /**
     *******************************************************************************
     *
     * @param description
     */
    private void writeDescription(String description) {
        // <description>
        setupNewLine();
        m_line.append("<description>");
        outputLine();

        // xxxxxxxx
        indent();
        setupNewLine();
        m_line.append(convertChars(description));
        outputLine();
        outdent();

        // </description>
        setupNewLine();
        m_line.append("</description>");
        outputLine();
    }

    /**
     *******************************************************************************
     *
     * @param example
     */
    private void writeExample(String example) {
        // <example>
        setupNewLine();
        m_line.append("<example>");
        outputLine();

        // xxxxxxxx
        indent();
        setupNewLineWithoutIndent();
        m_line.append("<![CDATA[");
        outputLine();
        setupNewLineWithoutIndent();
        m_line.append(example);
        outputLine();
        setupNewLineWithoutIndent();
        m_line.append("]]>");
        outputLine();
        outdent();

        // </description>
        setupNewLine();
        m_line.append("</example>");
        outputLine();
    }

    /**
     *******************************************************************************
     *
     * @param priority
     */
    private void writePriority(int priority) {
        // <priority>
        setupNewLine();
        m_line.append("<priority>");
        outputLine();

        // xx
        indent();
        setupNewLine();
        m_line.append(String.valueOf(priority));
        outputLine();
        outdent();

        // </priority>
        setupNewLine();
        m_line.append("</priority>");
        outputLine();
    }

    /**
     *******************************************************************************
     *
     * @param rule
     */
    private void writeProperties(Rule rule) {
        // <properties>
        setupNewLine();
        m_line.append("<properties>");
        outputLine();
        indent();

        Properties props = rule.getProperties();
        RuleProperties properties = new RuleProperties(props);
        Enumeration keys = properties.keys();

        while (keys.hasMoreElements()) {
            String name = (String) keys.nextElement();
            String value = properties.getValue(name);
            String valueType = properties.getValueType(name);

            // <property name="xxxxx" value="yyyyy" />
            setupNewLine();
            m_line.append("<property name=\"");
            m_line.append(convertChars(name));
            m_line.append("\" value=\"");
            m_line.append(convertChars(value));
            m_line.append("\" type=\"");
            m_line.append(convertChars(valueType));
            m_line.append("\"/>");
            outputLine();
        }

        outdent();
        setupNewLine();
        m_line.append("</properties>");
        outputLine();
    }

    /**
     *******************************************************************************
     *
     */
    private void indent() {
        m_indent += 3;
    }

    /**
     *******************************************************************************
     *
     */
    private void outdent() {
        m_indent -= 3;
    }

    /**
     *******************************************************************************
     *
     */
    private void setupNewLine() {
        m_line.setLength(0);

        for (int n = 0; n < m_indent; n++) {
            m_line.append(' ');
        }
    }

    /**
     *******************************************************************************
     *
     */
    private void setupNewLineWithoutIndent() {
        m_line.setLength(0);
    }

    /**
     *******************************************************************************
     *
     */
    private void outputLine() {
        m_outputStream.println(m_line.toString());
    }

    /**
     *******************************************************************************
     *
     * @param text
     * @return
     */
    private String convertChars(String text) {
        if (text == null) {
            text = "";
        }

        StringBuffer buffer = new StringBuffer(text);

        for (int n = 0; n < buffer.length(); n++) {
            switch (buffer.charAt(n)) {
            case '<':
                buffer.replace(n, n + 1, "&lt;");
                n += 3;

                break;

            case '>':
                buffer.replace(n, n + 1, "&gt;");
                n += 3;

                break;

            case '&':
                buffer.replace(n, n + 1, "&amp;");
                n += 4;

                break;

            case '"':
                buffer.replace(n, n + 1, "&quot;");
                n += 4;

                break;
            }
        }

        return buffer.toString();
    }
}
