/*
 * Copyright (C) 2010 Teleal GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.lemma.processor.xhtml;

import com.sun.tools.javac.util.Pair;
import org.teleal.common.xhtml.Body;
import org.teleal.common.xhtml.XHTML;
import org.teleal.common.xhtml.XHTMLElement;
import org.teleal.lemma.Constants;
import org.teleal.lemma.pipeline.Context;
import org.teleal.lemma.anchor.CitationAnchor;
import org.teleal.lemma.processor.AbstractProcessor;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * Replaces <code>toc</code> anchors with a generated table-of-contents.
 * <p>
 * The table-of-contents is generated by discovering all containers in an XHTML
 * document which are classed as a {@link org.teleal.lemma.processor.xhtml.TocProcessor.SectionType}.
 * Depending on their nesting level in the document (and not their name - all are equal), they
 * are added to the table-of-contents.
 * </p>
 *
 * @author Christian Bauer
 */
public class TocProcessor extends AbstractProcessor<XHTML, XHTML> {

    private Logger log = Logger.getLogger(TocProcessor.class.getName());

    public static final String TYPE_TOC = "toc";
    public static final String TYPE_TOC_ITEM = "tocitem";
    public static final String TYPE_TOC_ITEM_PREFIX = "prefix";
    public static final String TYPE_TOC_ITEM_LEVEL = "level_";
    public static final String SECTION_ELEMENT = "div";

    public static enum SectionType {
        part, chapter, section, sect1, sect2, sect3
    }

    public XHTML process(XHTML input, Context context) {
        log.fine("Processing input...");
        return addTableOfContents(input, context);
    }

    protected XHTML addTableOfContents(XHTML input, Context context) {

        Body body = input.getRoot(getXPath()).getBody();
        if (body == null) {
            log.info("Body element not found, skipping TOC generation");
            return input;
        }

        // We might have more than one TOC to generate
        CitationAnchor[] tocAnchors = findCitationAnchors(input.getRoot(getXPath()).getBody(), TYPE_TOC);
        if (tocAnchors.length == 0) {
            log.info("No class='toc' anchors found, returning input unchanged");
            return input;
        }

        // Build the TOC tree by appending TocItems to the root item, starting from the XHTML body element
        TocItem rootItem = new TocItem();

        // Walk the body recursively and append TocItems
        addChildTocItems(rootItem, body);

        // Now produce the TOC from the TocItem graph
        XHTML toc = generateToc(rootItem);

        // For every anchor, replace with the (same) TOC
        for (CitationAnchor tocAnchor : tocAnchors) {
            tocAnchor.getParent().replaceChild(tocAnchor, toc.getRoot(getXPath()), true);
        }

        return input;
    }

    protected void addChildTocItems(TocItem currentTocItem, XHTMLElement currentElement) {

        // Find all children that are section elements (e.g. all chapter/section div's)
        List<Pair<SectionType, XHTMLElement>> sectionElements = new ArrayList();
        for (XHTMLElement element : currentElement.getChildren(SECTION_ELEMENT)) {

            boolean isSection = false;
            String[] types = element.getClasses();
            for (SectionType sectionType : SectionType.values()) {
                for (String t : types) {
                    if (t.trim().equals(sectionType.name())) {
                        isSection = true;
                        break;
                    }
                }
                if (isSection) {
                    sectionElements.add(new Pair<SectionType, XHTMLElement>(sectionType, element));
                    break;
                }
            }
        }

        log.fine("Found section elements as children of '" + currentElement.getElementName() + "': " + sectionElements.size());

        // Create TOC items as needed for each section
        if (sectionElements.size() > 0) {
            for (Pair<SectionType, XHTMLElement> pair : sectionElements) {

                SectionType sectionType = pair.fst;
                XHTMLElement sectionElement = pair.snd;

                log.finest("Analyzing section element of type: " + sectionType);

                // First, we need an identifier so we can link to the TOC item
                log.finest("Trying to find nearest identifier of: " + sectionElement.getElementName());
                String id = findNearestIdentifier(sectionElement);

                // A title would be nice, so we can modify it later with a TOC number prefix
                log.finest("Trying to find nearest title element of: " + sectionElement.getElementName());
                XHTMLElement titleElement = findNearestTitleElement(sectionElement);

                // If we don't have them...
                if (id == null || titleElement == null) {
                    log.info("Skipping section element, no id or title found: " + sectionType);
                    // Don't create a TOC item but continue down the tree within this section
                    addChildTocItems(currentTocItem, sectionElement);
                    continue;
                }

                log.finest("Found identifier: " + id);
                log.finest("Found title element content: " + titleElement.getContent());

                // If we have them, create the TOC item and link it into the TOC tree
                TocItem sectionTocItem = new TocItem(currentTocItem.level + 1, sectionType.name(), id, titleElement);
                sectionTocItem.parent = currentTocItem;
                currentTocItem.children.add(sectionTocItem);

                // Recursion
                addChildTocItems(sectionTocItem, sectionElement);
            }
        } else {
            // Recursion (only for child elements)
            for (XHTMLElement child : currentElement.getChildren()) {
                addChildTocItems(currentTocItem, child);
            }
        }
    }

    protected String findNearestIdentifier(XHTMLElement sectionElement) {
        String id = sectionElement.getId();

        // If no identifier was specified, we look for a child that was a citation, and take its identifier
        if (id == null) {
            log.finest("Section element has no identifier, looking for child citation elements with identifier");
            XHTMLElement[] childCitations =
                    sectionElement.findChildrenWithClass(Constants.WRAPPER_ELEMENT, Constants.TYPE_CITATION);

            if (childCitations.length > 0) {
                id = childCitations[0].getId();
                log.finest("Child citation elements found, using the identifier of the first: " + id);
            } else {
                log.finest("No child citation elements found");
            }
        }

        return id == null || id.length() == 0 ? null : id;
    }

    protected XHTMLElement findNearestTitleElement(XHTMLElement sectionElement) {
        // Take the first <div class="title"> child we can find
        XHTMLElement[] titleElements =
                sectionElement.findChildrenWithClass(Constants.WRAPPER_ELEMENT, Constants.TYPE_TITLE);
        return titleElements.length > 0 ? titleElements[0] : null;
    }

    protected XHTML generateToc(TocItem root) {
        XHTML tocDom = getParser().createDocument();
        XHTMLElement container = tocDom
                .createRoot(getXPath(), Constants.WRAPPER_ELEMENT)
                .setAttribute(XHTML.ATTR.CLASS, TYPE_TOC);
        for (int i = 0; i < root.children.size(); i++) {
            TocItem child = root.children.get(i);
            child.generate(container, "", i);
        }
        return tocDom;
    }

    protected class TocItem {
        int level;
        String levelName;
        String id;
        XHTMLElement titleElement;
        List<TocItem> children = new ArrayList();
        TocItem parent;

        public TocItem() {
        }

        public TocItem(int level, String levelName, String id, XHTMLElement titleElement) {
            this.level = level;
            this.levelName = levelName;
            this.id = id;
            this.titleElement = titleElement;
        }

        public void generate(XHTMLElement parentElement, String numberPrefix, int number) {

            log.finest("Generating TOC item output, item in parent is " + number + " and prefix is: " + numberPrefix);

            String itemClass = TYPE_TOC_ITEM + " " + TYPE_TOC_ITEM_LEVEL + level + " " + TYPE_TOC_ITEM_LEVEL + levelName;
            String prefixedNumber = numberPrefix + (number + 1) + ".";

            XHTMLElement itemElement = parentElement
                    .createChild(Constants.WRAPPER_ELEMENT)
                    .setAttribute(XHTML.ATTR.CLASS, itemClass);

            itemElement.createChild(XHTML.ELEMENT.span)
                    .setAttribute(XHTML.ATTR.CLASS, TYPE_TOC_ITEM_PREFIX)
                    .setContent(prefixedNumber);

            itemElement.createChild(XHTML.ELEMENT.a)
                    .setAttribute(XHTML.ATTR.href, "#" + id)
                    .setContent(titleElement.getContent());

            log.finest("Prefixing title '" + titleElement.getContent() + "' with numbers: " + prefixedNumber);
            titleElement.setContent(prefixedNumber + " " + titleElement.getContent());

            for (int i = 0; i < children.size(); i++) {
                TocItem child = children.get(i);
                child.generate(parentElement, prefixedNumber, i);
            }
        }

/*
        // TODO: This is debug code

        public void print() {
            for (int i = 0; i < level; i++) {
                System.out.print("-");
            }
            System.out.println(" " + toString());
            for (TocItem child : children) {
                child.print();
            }
        }

*/

        @Override
        public String toString() {
            return "LEVEL " + level + " TITLE: " + (titleElement != null ? titleElement.getContent() : "NULL");
        }
    }

}
