/*
 * Copyright (c) 2003-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.awt.Color;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.MissingResourceException;

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

import com.xmlmind.xml.doc.Node;
import com.xmlmind.xml.doc.Element;
import com.xmlmind.xml.doc.DocumentEvent;
import com.xmlmind.xml.doc.TreeEvent;
import com.xmlmind.xml.doc.InclusionUpdatedEvent;
import com.xmlmind.xml.doc.AttributeEvent;

import com.xmlmind.xmledit.view.DocumentView;

import com.xmlmind.xmledit.stylesheet.StyleValue;
import com.xmlmind.xmledit.stylesheet.StyleSpec;
import com.xmlmind.xmledit.stylesheet.StyleSpecsBase;

import com.xmlmind.xmledit.styledview.StyledViewFactory;
import com.xmlmind.xmledit.styledview.CustomViewManager;

public class StyleSheetExtension extends StyleSpecsBase {
    private static final Namespace EMAIL_NAMESPACE = 
        Namespace.get("http://www.xmlmind.com/xmleditor/schema/email");

    private static final Name EMPHASIS = Name.get(EMAIL_NAMESPACE, "emphasis");
    private static final Name ROLE = Name.get("role");

    private static final Name ORDEREDLIST =
        Name.get(EMAIL_NAMESPACE, "orderedlist");
    private static final Name LISTITEM = Name.get(EMAIL_NAMESPACE, "listitem");
    private static final Name CONTINUATION = Name.get("continuation");

    private Color highlightBackground;
    private Color highlightForeground;

    // -----------------------------------------------------------------------

    public StyleSheetExtension(String[] args, StyledViewFactory viewFactory) {
        viewFactory.addDependency(EMAIL_NAMESPACE, "message", 
                                  Namespace.XML, "lang");

        // ---

        switch (args.length) {
        case 2:
            highlightForeground = StyleValue.parseColor(args[1]);
            /*FALLTHROUGH*/
        case 1:
            highlightBackground = StyleValue.parseColor(args[0]);
            break;
        }
        if (highlightBackground == null)
            highlightBackground = Color.yellow;
        if (highlightForeground == null)
            highlightForeground = Color.red;

        viewFactory.addIntrinsicStyleSpecs(this);

        viewFactory.addDependency(EMAIL_NAMESPACE, "emphasis", 
                                  Namespace.NONE, "role");

        // ---

        CustomViewManager.NamePattern[] observed = {
            new CustomViewManager.NamePattern(EMAIL_NAMESPACE, "body"),
            new CustomViewManager.NamePattern(EMAIL_NAMESPACE, "listitem"),
            new CustomViewManager.NamePattern(EMAIL_NAMESPACE, "orderedlist")
        };
        viewFactory.getCustomViewManager().add(
            new OrderedListObserver(viewFactory), observed);

        // Needed for PassiveSmiley ---

        viewFactory.addDependency(EMAIL_NAMESPACE, "smiley", 
                                  Namespace.NONE, "emotion");
    }

    // -----------------------------------------------------------------------

    public StyleValue localize(StyleValue[] args, Node contextNode,
                               StyledViewFactory viewFactory) {
        String text;
        if (args.length != 1 || (text = args[0].stringValue()) == null) {
            System.err.println("usage: content: invoke('localize', text);");
            return null;
        }

        Element root = contextNode.getDocument().getRootElement();
        String lang = root.getTokenAttribute(Name.XML_LANG, "en");

        ResourceBundle messages = getMessages(lang);
        if (messages == null && !"en".equals(lang)) 
            messages = getMessages("en");

        String localizedText = null;
        if (messages != null) {
            try {
                localizedText = messages.getString(text);
            } catch (Exception ignored) {}
        }

        if (localizedText == null)
            return args[0];
        else
            return StyleValue.createString(localizedText);
    }

    private static HashMap<String, ResourceBundle> langToMessages = 
        new HashMap<String, ResourceBundle>(5);

    private static ResourceBundle getMessages(String lang) {
        ResourceBundle messages = langToMessages.get(lang);
        if (messages == null) {
            try {
                messages = ResourceBundle.getBundle("messages/Messages", 
                                                    new Locale(lang));
            } catch (MissingResourceException ignored) {}

            if (messages != null)
                langToMessages.put(lang, messages);
        }
        return messages;
    }

    // -----------------------------------------------------------------------

    public int findStyleSpec(Element element, StyleSpec[] specs) {
        if (element.getName() == EMPHASIS) {
            styleEmphasis(element, specs[StyleSpecsBase.ELEMENT]);
            return StyleSpecsBase.ELEMENT_MASK;
        }

        return 0x0;
    }

    private void styleEmphasis(Element element, StyleSpec styleSpec) {
        int role = getRole(element);

        int nesting = 0;
        Element ancestor = element.getParentElement();
        while (ancestor != null) {
            if (ancestor.getName() != EMPHASIS ||
                getRole(ancestor) != role)
                break;

            ++nesting;
            ancestor = ancestor.getParentElement();
        }

        switch (role) {
        case BOLD:
            if ((nesting % 2) == 0) {
                setFontWeight(styleSpec, StyleValue.Keyword.BOLD);
            } else {
                setFontWeight(styleSpec, StyleValue.Keyword.NORMAL);
            }
            break;
        case HIGHLIGHT:
            if ((nesting % 2) == 0) {
                setBackgroundColor(styleSpec, highlightBackground);
                setColor(styleSpec, highlightForeground);
            } else {
                setBackgroundColor(styleSpec, Color.white);
                setColor(styleSpec, Color.black);
            }
            break;
        default:
            if ((nesting % 2) == 0) {
                setFontStyle(styleSpec, StyleValue.Keyword.ITALIC);
            } else {
                setFontStyle(styleSpec, StyleValue.Keyword.NORMAL);
            }
        }
    }

    private static final int ITALIC = 0;
    private static final int BOLD = 1;
    private static final int HIGHLIGHT = 2;

    private static int getRole(Element element) {
        String role = element.getTokenAttribute(ROLE, null);
        if ("bold".equals(role))
            return BOLD;
        else if ("highlight".equals(role))
            return HIGHLIGHT;
        else
            return ITALIC;
    }

    private StyleValue fontStyleValue = 
    	StyleValue.createIdentifier(StyleValue.Keyword.NORMAL);
    private StyleValue fontWeightValue = 
    	StyleValue.createIdentifier(StyleValue.Keyword.NORMAL);
    private StyleValue backgroundColorValue = 
    	StyleValue.createColor(Color.white);
    private StyleValue colorValue = 
    	StyleValue.createColor(Color.black);

    private void setFontStyle(StyleSpec styleSpec, 
                              StyleValue.Keyword fontStyle) {
        fontStyleValue.initIdentifier(fontStyle);
        styleSpec.fontStyle = fontStyleValue;
    }

    private void setFontWeight(StyleSpec styleSpec, 
                               StyleValue.Keyword fontWeight) {
        fontWeightValue.initIdentifier(fontWeight);
        styleSpec.fontWeight = fontWeightValue;
    }

    private void setBackgroundColor(StyleSpec styleSpec, Color color) {
        backgroundColorValue.color = color;
        styleSpec.backgroundColor = backgroundColorValue;
    }

    private void setColor(StyleSpec styleSpec, Color color) {
        colorValue.color = color;
        styleSpec.color = colorValue;
    }

    // -----------------------------------------------------------------------

    public StyleValue listItemCounter(StyleValue[] args, Node contextNode,
                                      StyledViewFactory viewFactory) {
        int index = indexOfListItem((Element) contextNode);
        return StyleValue.createString(Integer.toString(1 + index) + '.');
    }

    private static int indexOfListItem(Element listItem) {
        Element orderedList = listItem.getParentElement();
        if (orderedList == null || orderedList.getName() != ORDEREDLIST)
            return -1;

        int index = orderedList.indexOfChildElement(listItem);
        int offset = 0;

        String continuation = orderedList.getNmtokenAttribute(CONTINUATION, 
                                                              "restarts");
        if ("continues".equals(continuation)) {
            Element prevOrderedList = null;

            if (orderedList.getParentElement() != null) {
                Node node = orderedList.getPreviousSibling();
                while (node != null) {
                    if ((node instanceof Element) &&
                        ((Element) node).getName() == ORDEREDLIST) {
                        prevOrderedList = (Element) node;
                        break;
                    }

                    node = node.getPreviousSibling();
                }
            } // Otherwise, orderedList is the root element.

            if (prevOrderedList != null) {
                Element last = prevOrderedList.getLastChildElement();
                if (last != null) {
                    offset = indexOfListItem(last) + 1;
                } // Otherwise, prevOrderedList is invalid.
            }
        }

        return (offset + index);
    }

    private static class OrderedListObserver 
                   implements CustomViewManager.BasicElementObserver {
        private DocumentView docView;
        private ArrayList<Element> orderedLists = new ArrayList<Element>();

        public OrderedListObserver(StyledViewFactory viewFactory) {
            docView = viewFactory.getDocumentView();
        }

        public void customViewAdded() {}
        public void customViewRemoved() {}

        public void elementChanged(DocumentEvent[] events) {
            orderedLists.clear();

            for (int i = 0; i < events.length; ++i) {
                DocumentEvent event = events[i];

                switch (event.getType()) {
                case CHILD_ADDED:
                case CHILD_REPLACED:
                case CHILD_REMOVED:
                    {
                        TreeEvent e = (TreeEvent) event;

                        Element element = e.getElementSource();
                        if (element == null)
                            break;

                        if (element.getName() == ORDEREDLIST) {
                            add(orderedLists, element);
                        } else {
                            if (isOrderedList(e.getOldChild()) ||
                                isOrderedList(e.getNewChild())) {
                                // Add first child orderedlist (if any).

                                Node node = element.getFirstChild();
                                while (node != null) {
                                    if ((node instanceof Element) &&
                                        ((Element) node).getName() == 
                                        ORDEREDLIST) {
                                        add(orderedLists, (Element) node);
                                        break;
                                    }

                                    node = node.getNextSibling();
                                }
                            }
                        }
                    }
                    break;

                // ------------------------------------------------------
                // Left as an exercise, treat INCLUSION_UPDATED similarly 
                // to CHILD_REPLACED
                // ------------------------------------------------------

                case ATTRIBUTE_ADDED:
                case ATTRIBUTE_CHANGED:
                case ATTRIBUTE_REMOVED:
                    {
                        Element element = 
                            ((AttributeEvent) event).getElementSource();
                        if (element.getName() == ORDEREDLIST) {
                            add(orderedLists, element);
                        }
                    }
                    break;
                }
            }

            int count = orderedLists.size();
            if (count > 0) {
                for (int i = 0; i < count; ++i) {
                    Element orderedList = (Element) orderedLists.get(i);

                    if (orderedList.getParentElement() != null) {
                        // Add all following orderedlist siblings (if any).

                        Node node = orderedList.getNextSibling();
                        while (node != null) {
                            if ((node instanceof Element) &&
                                ((Element)node).getName() == ORDEREDLIST) {
                                add(orderedLists, (Element) node);
                            }

                            node = node.getNextSibling();
                        }
                    } // Otherwise, orderedList is the root element.
                }

                count = orderedLists.size();
                for (int i = 0; i < count; ++i) {
                    docView.rebuildView((Element) orderedLists.get(i));
                }

                orderedLists.clear();
            }
        }

        private static final void add(ArrayList<Element> orderedLists,
                                      Element orderedList) {
            if (!orderedLists.contains(orderedList))
                orderedLists.add(orderedList);
        }

        private static final boolean isOrderedList(Node node) {
            if (node == null || !(node instanceof Element))
                return false;
            else
                return (((Element) node).getName() == ORDEREDLIST);
        }
    }
}
