/*
 * Copyright (c) 2012 Vadim Zaslawski, Ontos AG
 * Copyright (c) 2012-2017 Mozilla Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package nu.validator.servlet;

import java.io.IOException;
import java.util.Deque;

import nu.validator.servlet.OutlineBuildingXMLReaderWrapper.Section;
import nu.validator.xml.AttributesImpl;
import nu.validator.xml.XhtmlSaxEmitter;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class XhtmlOutlineEmitter {

    private static final char[] OUTLINE = //
            "Outline".toCharArray();

    private final Deque<Section> outline;

    private final XhtmlSaxEmitter emitter;

    private final AttributesImpl attrs = new AttributesImpl();

    public XhtmlOutlineEmitter(final ContentHandler contentHandler,
            final Deque<Section> outline) {
        this.emitter = new XhtmlSaxEmitter(contentHandler);
        this.outline = outline;
    }

    boolean hasH1 = false;

    boolean hasH2 = false;

    boolean hasH3 = false;

    boolean hasH4 = false;

    boolean hasH5 = false;

    boolean emittedDummyH1 = false;

    boolean emittedDummyH2 = false;

    boolean emittedDummyH3 = false;

    boolean emittedDummyH4 = false;

    boolean emittedDummyH5 = false;

    public void emit() throws SAXException {
        hasH1 = false;
        hasH2 = false;
        hasH3 = false;
        hasH4 = false;
        hasH5 = false;
        emittedDummyH1 = false;
        emittedDummyH2 = false;
        emittedDummyH3 = false;
        emittedDummyH4 = false;
        emittedDummyH5 = false;
        if (outline != null) {
            attrs.clear();
            attrs.addAttribute("id", "headingoutline");
            emitter.startElement("section", attrs);
            emitter.startElement("h2");
            emitter.characters(OUTLINE);
            emitter.endElement("h2");
            try {
                emitHeadingOutline(outline, 0);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            emitter.endElement("section");
        }
    }

    protected void emitOutline(Deque<Section> outline, int currentDepth)
            throws IOException, SAXException {
        emitter.startElement("ol");
        for (Section section : outline) {
            if (!section.getIsMasked()) {
                emitter.startElement("li");
                StringBuilder headingText = section.getHeadingTextBuilder();
                if (headingText.length() > 0) {
                    emitter.startElementWithClass("span", "heading");
                    emitter.characters(headingText.toString().toCharArray());
                    if (section.getSubheadSections() != null) {
                        for (Section subhead : section.getSubheadSections()) {
                            emitter.characters(": ".toCharArray());
                            StringBuilder subheadText = subhead.getHeadingTextBuilder();
                            emitter.characters(
                                    subheadText.toString().toCharArray());
                        }
                    }
                    emitter.endElement("span");
                } else if (section.hasEmptyHeading()) {
                    emitter.characters(("[" + section.getElementName()
                            + " element with empty heading]").toCharArray());
                } else if ("h1".equals(section.getElementName())
                        || "h2".equals(section.getElementName())
                        || "h3".equals(section.getElementName())
                        || "h4".equals(section.getElementName())
                        || "h5".equals(section.getElementName())
                        || "h6".equals(section.getElementName())) {
                    emitter.characters(("[section implied by empty "
                            + section.getElementName()
                            + " element]").toCharArray());
                } else {
                    emitter.characters(("[" + section.getElementName()
                            + " element with no heading]").toCharArray());
                }
            }
            Deque<Section> sections = section.sections;
            if (!sections.isEmpty()) {
                emitOutline(sections, currentDepth + 1);
            }
            if (!section.getIsMasked()) {
                emitter.endElement("li");
            }
        }
        emitter.endElement("ol");
    }

    protected void emitHeadingOutline(Deque<Section> outline,
            int currentDepth) throws IOException, SAXException {
        for (Section section : outline) {
            String headingName = section.getHeadingElementName();
            int headingRank = section.getHeadingRank();

            if (headingRank == 1) {
                hasH1 = true;
                hasH2 = false;
                hasH3 = false;
                hasH4 = false;
                hasH5 = false;
                emittedDummyH2 = false;
                emittedDummyH3 = false;
                emittedDummyH4 = false;
                emittedDummyH5 = false;
            } else if (headingRank == 2) {
                hasH2 = true;
                hasH3 = false;
                hasH4 = false;
                hasH5 = false;
                emittedDummyH3 = false;
                emittedDummyH4 = false;
                emittedDummyH5 = false;
            } else if (headingRank == 3) {
                hasH3 = true;
                hasH4 = false;
                hasH5 = false;
                emittedDummyH4 = false;
                emittedDummyH5 = false;
            } else if (headingRank == 4) {
                hasH4 = true;
                hasH5 = false;
                emittedDummyH5 = false;
            } else if (headingRank == 5) {
                hasH5 = true;
            }
            if (headingRank >= 1 && headingRank <= 9) {
                StringBuilder headingText = section.getHeadingTextBuilder();
                if (headingRank > 1 && !hasH1 && !emittedDummyH1) {
                    emitMissingHeading("h1", 1);
                    emittedDummyH1 = true;
                }
                if (headingRank > 2 && !hasH2 && !emittedDummyH2) {
                    emitMissingHeading("h2", 2);
                    emittedDummyH2 = true;
                }
                if (headingRank > 3 && !hasH3 && !emittedDummyH3) {
                    emitMissingHeading("h3", 3);
                    emittedDummyH3 = true;
                }
                if (headingRank > 4 && !hasH4 && !emittedDummyH4) {
                    emitMissingHeading("h4", 4);
                    emittedDummyH4 = true;
                }
                if (headingRank == 6 && !hasH5 && !emittedDummyH5) {
                    emitMissingHeading("h5", 5);
                    emittedDummyH5 = true;
                }
                String levelClass = headingRank <= 6 ? "h" + headingRank : "h6";
                emitter.startElementWithClass("p", levelClass);
                emitter.startElementWithClass("span", "headinglevel");
                emitter.characters(("<h" + headingRank + ">").toCharArray());
                emitter.endElement("span");
                if (headingText.length() > 0) {
                    emitter.characters(
                            (" " + headingText.toString()).toCharArray());
                } else {
                    emitter.startElementWithClass("span", "missingheading");
                    emitter.characters((" [empty]").toCharArray());
                    emitter.endElement("span");
                }
                emitter.endElement("p");
            }
            Deque<Section> sections = section.sections;
            if (!sections.isEmpty()) {
                emitHeadingOutline(sections, currentDepth + 1);
            }
        }
    }

    private void emitMissingHeading(String headingName, int level) throws SAXException {
        emitter.startElementWithClass("p", headingName);
        emitter.startElementWithClass("span", "missingheadinglevel");
        emitter.characters(("<h" + level + ">").toCharArray());
        emitter.endElement("span");
        emitter.startElementWithClass("span", "missingheading");
        emitter.characters((" [missing]").toCharArray());
        emitter.endElement("span");
        emitter.endElement("p");
    }
}
