/*
 * Copyright (c) 2005-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.text.NumberFormat;

import java.awt.Color;
import java.awt.Font;
import java.awt.FlowLayout;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;

import javax.swing.ImageIcon;
import javax.swing.JToggleButton;
import javax.swing.JTextField;
import javax.swing.JPanel;

import com.xmlmind.util.Preferences;

import com.xmlmind.xml.doc.Document;

import com.xmlmind.xmledit.cmd.search.ElementCharSequence;

import com.xmlmind.guiutil.ThinBorder;
import com.xmlmind.guiutil.DialogUtil;

import com.xmlmind.xmleditapp.kit.AppTool;
import com.xmlmind.xmleditapp.kit.OpenedDocument;
import com.xmlmind.xmleditapp.kit.App;

public class CountWordsTool extends JPanel implements AppTool {
    private String helpId;
    private App app;
    private String id;

    private JToggleButton toggle;
    private JTextField field;
    private Color needUpdateColor;
    private Color upToDateColor;

    private boolean activated;
    private int minCharCount;

    private static final class WordCount {
        public int wordCount;
        public boolean needUpdate;

        public WordCount() {
            wordCount = 0;
            needUpdate = true;
        }
    }

    private static final String WORD_COUNT_PROPERTY = 
        "CountWordsTool.WordCount";

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

    public CountWordsTool() {
        setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0));

        toggle = new JToggleButton(
            new ImageIcon(CountWordsTool.class.getResource(
                              "icons/wordCountTool.gif")));
        DialogUtil.setIconic(toggle);
        toggle.setToolTipText("Turns word counting on and off");
        toggle.setFocusable(false);
        add(toggle);

        toggle.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                activated = toggle.isSelected();
                activationChanged();
            }
        });

        field = new JTextField(10);
        field.setEditable(false);
        field.setFont(new Font("SansSerif", Font.PLAIN, 10));
        Color bg = field.getBackground();
        field.setBorder(new ThinBorder(bg, /*raised*/ false));
        field.setToolTipText("Word count (click on it to update it)");
        field.setFocusable(false);
        add(field);

        needUpdateColor = bg.darker();
        upToDateColor = field.getForeground();

        field.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent event) {
                if (!activated)
                    return;

                OpenedDocument openedDoc = app.getActiveOpenedDocument();
                if (openedDoc != null)
                    updateWordCount(openedDoc);
            }
        });
    }

    private void activationChanged() {
        WordCount wc = null;

        OpenedDocument[] openedDocs = app.getOpenedDocuments();
        for (int i = 0; i < openedDocs.length; ++i) {
            if (!activated) {
                openedDocs[i].removeProperty(WORD_COUNT_PROPERTY);
            } else {
                wc = new WordCount();
                openedDocs[i].putProperty(WORD_COUNT_PROPERTY, wc);
            }
        }

        updateField(wc);
    }

    private void updateField(WordCount wc) {
        if (!activated || wc == null) {
            field.setText("");
        } else {
            field.setText(
                NumberFormat.getIntegerInstance().format(wc.wordCount));
            field.setForeground(wc.needUpdate? 
                                needUpdateColor : upToDateColor);
        }
    }

    private void updateWordCount(OpenedDocument openedDoc) {
        WordCount wc = (WordCount) openedDoc.getProperty(WORD_COUNT_PROPERTY);
        wc.wordCount = countWords(openedDoc.getDocument());
        wc.needUpdate = false;

        updateField(wc);
    }

    private int countWords(Document doc) {
        ElementCharSequence chars =
            new ElementCharSequence(doc.getRootElement());

        /*
        // MARK_LAST does not work great with content models such as the
        // the content model of XHTML div, td, li, etc (that is, ``flows'').

        ElementCharSequence chars = new ElementCharSequence(
            doc.getRootElement(), null, null,
            ElementCharSequence.DEFAULT_EMPTY_TEXT_CONTENT, 
            ElementCharSequence.Mark.ALL, 
            ElementCharSequence.DEFAULT_PARAGRAPH_MARK);
        */

        int wordCount = 0;

        int wordStart = -1;
        int count = chars.length();
        for (int i = 0; i < count; ++i) {
            char c = chars.charAt(i);

            boolean whitespace;
            switch (c) {
            case ' ':
            case '\t': // HORIZONTAL TABULATION.
            case '\n': // LINE FEED.
            case '\r': // CARRIAGE RETURN.
            case '\f': // FORM FEED.
            case '\u000B': // VERTICAL TABULATION.
            case '\u001C': // FILE SEPARATOR.
            case '\u001D': // GROUP SEPARATOR.
            case '\u001E': // RECORD SEPARATOR.
            case '\u001F': // UNIT SEPARATOR.
                whitespace = true;
                break;
            default:
                // This includes non-breaking spaces.
                whitespace = Character.isSpaceChar(c);
            }

            if (whitespace) {
                if (wordStart >= 0) {
                    if (i - wordStart >= minCharCount)
                        ++wordCount;

                    wordStart = -1;
                }
            } else {
                if (wordStart < 0) {
                    wordStart = i;
                }
            }
        }

        if (wordStart >= 0) {
            if (count - wordStart >= minCharCount)
                ++wordCount;
        }

        return wordCount;
    }

    // -----------------------------------------------------------------------
    // AppTool
    // -----------------------------------------------------------------------

    public void setHelpId(String helpId) {
        this.helpId = helpId;
    }

    public String getHelpId() {
        return helpId;
    }

    // -------------------------------------
    // AppPart
    // -------------------------------------

    public void initApp(App app, String id) {
        this.app = app;
        this.id = id;
    }

    public App getApp() {
        return app;
    }

    public String getId() {
        return id;
    }

    public void applyPreferences() {
        Preferences prefs = app.getPreferences();
        activated = prefs.getBoolean("countWords", false);
        minCharCount = prefs.getInt("countedWordMinChars", 1, 1000, 1);

        toggle.setSelected(activated);
        activationChanged();
    }

    public void flushPreferences() {
        Preferences prefs = app.getPreferences();
        prefs.putBoolean("countWords", toggle.isSelected());
        prefs.putInt("countedWordMinChars", minCharCount);
    }

    public void activeEditorChanged() {
        if (!activated)
            return;

        WordCount wc = null;

        OpenedDocument openedDoc = app.getActiveOpenedDocument();
        if (openedDoc != null) {
            wc = (WordCount) openedDoc.getProperty(WORD_COUNT_PROPERTY);
            if (wc == null) {
                wc = new WordCount();
                openedDoc.putProperty(WORD_COUNT_PROPERTY, wc);
            }
        }

        updateField(wc);
    }

    public void saveStateChanged() {
        if (!activated)
            return;

        OpenedDocument openedDoc = app.getActiveOpenedDocument();
        if (openedDoc.isSaveNeeded()) {
            WordCount wc = 
                (WordCount) openedDoc.getProperty(WORD_COUNT_PROPERTY);
            wc.needUpdate = true;
            updateField(wc);
        } else {
            updateWordCount(openedDoc);
        }
    }

    public void namespacePrefixesChanged() {}
    public void validityStateChanged() {}
    public void undoStateChanged() {}
    public void editingContextChanged() {}
    public boolean isEditingContextSensitive() { return false; }
}

