/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

package netscape.test.plugin.composer;

import java.io.*;
import java.util.*;
import java.awt.*;
import java.net.*;
import netscape.plugin.composer.*;
import netscape.plugin.composer.io.*;

/** Calculate some simple statistics about the document.
 * Shows how to call awt (and by extension any user interface
 * toolkit) from a plugin.
 */

public class DocInfo extends Plugin {
    /** Test the plugin. Not required for normal operation of the plugin.
     * You can use this to run the plugin from the command line:
     * java -classpath <your-class-path> <your-plugin-name> args...
     * where args... are passed on to the Test class.
     * You can remove this code before shipping your plugin.
     */
    static public void main(String[] args) {
        Test.perform(args, new DocInfo());
    }
    /** Execute the command.
     * @param document the current document state.
     */
    public boolean perform(Document document) throws IOException{
        DocInfoDialog dialog = new DocInfoDialog(getResource("title"));
        dialog.setText(getStats(document));
        dialog.reshape(50,50,500,300);
        dialog.show(); // make the window visible.
        dialog.requestFocus(); // Make sure the window is on top and gets focus.
        dialog.waitForExit(); //Wait for the user to exit the dialog.
        dialog.dispose(); // Cleans up the native OS window associated with the dialog.
        return true;
    }
    /** Get the human readable name of the plugin. Defaults to the name of the plugin class.
     * @return the human readable name of the plugin.
     */
    public String getName()
    {
        return getResource("name");
    }

    /** Get the human readable category of the plugin. Defaults to the name of the plugin class.
     * @return the human readable category of the plugin.
     */
    public String getCategory()
    {
        return getResource("category");
    }

    /** Get the human readable hint for the plugin. This is a one-sentence description of
     * what the plugin does. Defaults to the name of the plugin class.
     * @return the human readable hint for the plugin.
     */
    public String getHint()
    {
        return getResource("hint");
    }

     /** Get the document stats.
     */
    String getStats(Document document) throws IOException{
        StringBuffer b = new StringBuffer();
        try {
            DocStats stats = new DocStats(document);
            b.append(formatProp("chars", stats.textBytes));
            b.append(formatProp("words", stats.wordCount));
            b.append("\n");
            b.append(formatProp("images", stats.imageCount));
            b.append(formatProp("totalImageBytes", stats.imageBytes));
            int totalBytes = stats.textBytes + stats.imageBytes;
            b.append(formatProp("totalBytes", totalBytes));
            b.append(formatProp("min14400", (totalBytes / 1440 + 1)));
            b.append("\n\n\n");
            b.append(formatProp("base", document.getBase().toString()));
            b.append(formatProp("workDir", document.getWorkDirectory().toString()));
            b.append(formatProp("workDirURL", document.getWorkDirectoryURL().toString()));
        } catch (Throwable t) {
        }
        // If this is an "edit" event, we'll fail when we try to actually get
        // the document statistics. But the following should work anyway.
        try {
            String eventName = document.getEventName();
            b.append(formatProp("eventName", eventName != null ? eventName : "null"));
            URL docURL = document.getDocumentURL();
            b.append(formatProp("documentURL", docURL != null ? docURL.toString() : "null"));
        } catch (Throwable t) {
        }
        return b.toString();
    }
    /** Internationalizable format function.
     */
    String formatProp(String key, int num){
        return formatProp(key, Integer.toString(num));
    }
    String formatProp(String key, String string){
        return format(getResource(key), string);
    }

    static String getResource(String key){
        if ( resources == null ) {
            resources = ResourceBundle.getBundle(
                "netscape.test.plugin.composer.DocInfoResources");
        }
        String result = resources.getString(key);
        if ( result == null ) {
            result = key;
        }

        return result;
    }
    String format(String formatString, String arg){
        String args[] = new String[1];
        args[0] = arg;
        return format(formatString, args);
    }

    /** Poor man's internationalizable formatting.
     * @param formatString The format string. Occurences of ^0 are replaced by
     * args[0], ^1 by args[1], and so on. Use ^^ to signify a singe ^.
     * @param args The argument array.
     * @return the formatted string.
     */
    String format(String formatString, String[] args){
        int len = formatString.length();
        StringBuffer b = new StringBuffer(len);
        for(int index = 0; index < len; index++ ){
            char c = formatString.charAt(index);
            if ( c == '^') {
                if ( index + 1 < len ) {
                    c = formatString.charAt(++index);
                    if ( c >= '0' && c <= '9') {
                        int argIndex = c - '0';
                        if ( argIndex < args.length) {
                            b.append(args[argIndex]);
                            continue;
                        }
                        else {
                            b.append('^');
                            // And fall thru to append the character.
                        }
                    }
                }
            }
            b.append(c);
        }
        return b.toString();
    }

    private static ResourceBundle resources;
}

/** Calculate some simple statistics for a document
 */

class DocStats{
    public DocStats(Document document) throws IOException{
        LexicalStream s = new LexicalStream(document.getInput());
        boolean inBody = false;
        String BODY = "BODY";
        String IMG = "IMG";
        String SRC = "SRC";
        for(;;){
            // Get the next token of the document.
            Token token = s.next();
            if ( token == null ) break; //  Null means we've finished the document.
            // Add to the total byte count.
            // This is not an accurate measurement
            // because many unicode characters
            // will ultimately be encoded as more than
            // one byte.
            textBytes += token.toString().length();
            if (token instanceof Tag ) {
                Tag tag = (Tag) token;
                boolean isOpen = tag.isOpen();
                String tagName = tag.getName();
                if ( tagName.equals(BODY) ) {
                    inBody = isOpen;
                }
                else if ( isOpen ) {
                    // Check for images
                    if ( tagName.equals(IMG) ){
                        imageCount++;
                        String src = tag.lookupAttribute(SRC);
                        if ( src != null ) {
                            try {
                                URL base = document.getBase();
                                URL srcURL = new URL(base, src);
                                imageBytes += getSize(srcURL);
                            } catch(Throwable t){
                                System.err.println("Couldn't open " + src);
                                t.printStackTrace();
                            }
                        }
                    }
                }
            }
            else if ( inBody ) {
                if ( token instanceof Text ) {
                    Text text = (Text) token;
                    if ( ! text.isNewline() ){
                        countWords(text.getText());
                    }
                }
            }
        }
    }

    protected int getSize(URL srcURL){
        System.err.println("URL: " + srcURL);
        int size = 0;
        try {
            URLConnection connection = srcURL.openConnection();
            size = connection.getContentLength();
            if ( size < 0 ) {
                size = 0;
                byte[] dummy = new byte[1000];
                InputStream in = connection.getInputStream();
                int skipped;
                while((skipped = (int) in.read(dummy)) > 0) {
                    size += skipped;
                }
                in.close();
            }
        } catch(Throwable t){
            System.err.println("Couldn't open URL " + srcURL);
            t.printStackTrace();
        }
        return size;
    }
    protected void countWords(String text){
        // This is an approximate measure because
        // some tags don't cause word breaks.
        char c;
        int len = text.length();
        boolean nonSpaceFound = false;
        for(int i = 0; i < len; i++ ){
            c = text.charAt(i);
            if ( Character.isWhitespace(c)){
                wordCount++;
            }
            else {
                if ( ! nonSpaceFound ) {
                    nonSpaceFound = true;
                    wordCount++;
                }
            }
        }
    }

    public int textBytes;
    public int wordCount;
    public int imageCount;
    public int imageBytes;
}

/** An awt dialog for interacting with the user. This is like
 * the java.awt.Dialog class, except that it doesn't require a
 * parent Frame.
 */
class DocInfoDialog extends Frame {
    public DocInfoDialog(String title) {
        super(title);
        add("Center", text = new TextArea());
     }
    /** Handle window close event.
    */
    public boolean handleEvent(Event event) {
        if (event.id == Event.WINDOW_DESTROY) {
            hide();
            signalExit();
            return true;
        } else {
            return super.handleEvent(event);
        }
    }
    public void setText(String text){
        this.text.setText(text);
    }
    synchronized public void waitForExit() {
        while ( ! bExited ) {
            try {
                wait();
            } catch ( InterruptedException e){
            }
        }
    }
    synchronized public void signalExit() {
        bExited = true;
        notifyAll();
    }
    private Button quit;
    private TextArea text;
    private boolean bExited = false;
}
