/*
 * Copyright (c) 2002-2008 Pixware. 
 *
 * Author: Hussein Shafie
 *
 * This file is part of the XMLmind XML Editor project.
 * For conditions of distribution and use, see the accompanying legal.txt file.
 */

import java.io.IOException;
import java.io.File;

import com.xmlmind.util.XMLText;

import com.xmlmind.xml.name.Namespace;
import com.xmlmind.xml.name.Name;

import com.xmlmind.xml.doc.Node;
import com.xmlmind.xml.doc.Text;
import com.xmlmind.xml.doc.Element;
import com.xmlmind.xml.doc.Document;
import com.xmlmind.xml.doc.Traversal;

import com.xmlmind.xml.load.LoadDocument;

import com.xmlmind.xml.save.SaveDocument;

public class AddTOC {
    private static final Name BODY = Name.get(Namespace.XHTML, "body");
    private static final Name DIV = Name.get(Namespace.XHTML, "div");
    private static final Name H1 = Name.get(Namespace.XHTML, "h1");
    private static final Name H2 = Name.get(Namespace.XHTML, "h2");
    private static final Name H3 = Name.get(Namespace.XHTML, "h3");
    private static final Name A = Name.get(Namespace.XHTML, "a");
    private static final Name BR = Name.get(Namespace.XHTML, "br");

    private static final Name CLASS = Name.get("class");
    private static final Name NAME = Name.get("name");
    private static final Name HREF = Name.get("href");

    private static final class Info {
        public int headingCount;
        public Element toc;
        public Element body;
    }

    public static void processDocument(Document doc) {
        final Info info = new Info();

        Element b = new Element(Name.get(Namespace.XHTML, "b"));
        b.putAttribute(CLASS, "toctitle");
        b.appendChild(new Text("Contents"));

        info.toc = new Element(DIV);
        info.toc.putAttribute(CLASS, "toc");
        info.toc.appendChild(b);

        Traversal.traverse(doc.getRootElement(), new Traversal.HandlerBase() {
            public Object enterElement(Element element) {
                Name name = element.getName();

                if (name == H1 || name == H2 || name == H3) {
                    processHeading(element, info);
                    return Traversal.LEAVE_ELEMENT;
                } else {
                    if (name == BODY)
                        info.body = element;
                    return null;
                }
            }
        });

        if (info.body != null) {
            info.toc.appendChild(new Element(BR));
            info.toc.appendChild(new Element(Name.get(Namespace.XHTML, "hr")));

            add(info.body, info.toc);
        }
    }

    private static void processHeading(Element heading, Info info) {
        String id = "tocentry" + info.headingCount++;

        Element target = new Element(A);
        target.putAttribute(CLASS, "tocentry");
        target.putAttribute(NAME, id);

        add(heading, target);

        Traversal.TextGrabber grabber = new Traversal.TextGrabber();
        Traversal.traverse(heading, grabber);
        String headingText = 
            XMLText.collapseWhiteSpace(grabber.grabbed.toString());

        Element link = new Element(A);
        link.putAttribute(HREF, "#" + id);
        link.appendChild(new Text(headingText));

        int indentation;
        Name headingName = heading.getName();
        if (headingName == H1)
            indentation = 4;
        else if (headingName == H2)
            indentation = 8;
        else
            indentation = 12;
        StringBuilder indent = new StringBuilder();
        while (indentation > 0) {
            indent.append('\u00A0'); // &nbsp;
            --indentation;
        }
        
        info.toc.appendChild(new Element(BR));
        info.toc.appendChild(new Text(indent.toString()));
        info.toc.appendChild(link);
    }

    private static void add(Element parent, Element added) {
        Name addedName = added.getName();
        String addedClass = added.getAttribute(CLASS);

        boolean replaced = false;
        loop: for (Node child = parent.getFirstChild();
                   child != null;
                   child = child.getNextSibling()) {
            switch (child.getType()) {
            case TEXT:
            case COMMENT:
            case PROCESSING_INSTRUCTION:
                break;
            case ELEMENT:
                {
                    Element element = (Element) child;

                    if (element.getName() == addedName &&
                        addedClass.equals(element.getAttribute(CLASS))) {
                        parent.replaceChild(element, added);
                        replaced = true;
                        break loop;
                    }
                }
                break;
            }
        }

        if (!replaced) 
            parent.insertChild(parent.getFirstChild(), added);
    }

    public static void main(String[] args) 
        throws IOException {
        if (args.length != 2) {
            System.err.println(
                "usage: java AddTOC in_xhtml_file out_xhtml_file");
            System.exit(1);
        }
        String inFileName = args[0];
        String outFileName = args[1];

        Document doc = LoadDocument.load(new File(inFileName));

        AddTOC.processDocument(doc);

        SaveDocument.save(doc, new File(outFileName));
    }
}
