/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you under the Apache License, Version 2.0 (the            *
 * "License"); you may not use this file except in compliance   *
 * with the License.  You may obtain a copy of the License at   *
 *                                                              *
 *   http://www.apache.org/licenses/LICENSE-2.0                 *
 *                                                              *
 * Unless required by applicable law or agreed to in writing,   *
 * software distributed under the License is distributed on an  *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
 * KIND, either express or implied.  See the License for the    *
 * specific language governing permissions and limitations      *
 * under the License.                                           *
 ****************************************************************/

package org.apache.james.mime4j.samples.tree;

import java.awt.Dimension;
import java.awt.GridLayout;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Date;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeSelectionModel;

import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.dom.BinaryBody;
import org.apache.james.mime4j.dom.Body;
import org.apache.james.mime4j.dom.Entity;
import org.apache.james.mime4j.dom.Header;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.dom.MessageBuilder;
import org.apache.james.mime4j.dom.Multipart;
import org.apache.james.mime4j.dom.TextBody;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.dom.field.AddressListField;
import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.dom.field.DateTimeField;
import org.apache.james.mime4j.dom.field.UnstructuredField;
import org.apache.james.mime4j.field.address.AddressFormatter;
import org.apache.james.mime4j.message.BodyPart;
import org.apache.james.mime4j.message.MessageImpl;
import org.apache.james.mime4j.message.DefaultMessageBuilder;
import org.apache.james.mime4j.stream.Field;

/**
 * Displays a parsed Message in a window. The window will be divided into
 * two panels. The left panel displays the Message tree. Clicking on a
 * node in the tree shows information on that node in the right panel.
 *
 * Some of this code have been copied from the Java tutorial's JTree section.
 */
public class MessageTree extends JPanel implements TreeSelectionListener {
    private static final long serialVersionUID = 1L;

    private final JTextArea textView;
    private final JTree tree;

    /**
     * Wraps an Object and associates it with a text. All message parts
     * (headers, bodies, multiparts, body parts) will be wrapped in
     * ObjectWrapper instances before they are added to the JTree instance.
     */
    public static class ObjectWrapper {
        private String text = "";
        private Object object = null;

        public ObjectWrapper(String text, Object object) {
            this.text = text;
            this.object = object;
        }

        @Override
        public String toString() {
            return text;
        }

        public Object getObject() {
            return object;
        }
    }

    /**
     * Creates a new <code>MessageTree</code> instance displaying the
     * specified <code>Message</code>.
     *
     * @param message the message to display.
     */
    public MessageTree(Message message) {
        super(new GridLayout(1,0));

        DefaultMutableTreeNode root = createNode(message);

        tree = new JTree(root);
        tree.getSelectionModel().setSelectionMode(
                TreeSelectionModel.SINGLE_TREE_SELECTION);

        tree.addTreeSelectionListener(this);

        JScrollPane treeView = new JScrollPane(tree);

        JPanel contentPane = new JPanel(new GridLayout(1, 0));
        JScrollPane contentView = new JScrollPane(contentPane);

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.setLeftComponent(treeView);
        splitPane.setRightComponent(contentView);

        Dimension minimumSize = new Dimension(100, 50);
        contentView.setMinimumSize(minimumSize);
        treeView.setMinimumSize(minimumSize);
        splitPane.setDividerLocation(250);
        splitPane.setPreferredSize(new Dimension(750, 500));

        add(splitPane);

        textView = new JTextArea();
        textView.setEditable(false);
        textView.setLineWrap(true);
        contentPane.add(textView);
    }

    /**
     * Create a node given a Multipart body.
     * Add the Preamble, all Body parts and the Epilogue to the node.
     *
     * @return the root node of the tree.
     */
    private DefaultMutableTreeNode createNode(Header header) {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(
                new ObjectWrapper("Header", header));

        for (Field field : header.getFields()) {
            String name = field.getName();

            node.add(new DefaultMutableTreeNode(new ObjectWrapper(name, field)));
        }

        return node;
    }

    /**
     * Create a node given a Multipart body.
     * Add the Preamble, all Body parts and the Epilogue to the node.
     *
     * @param multipart the Multipart.
     * @return the root node of the tree.
     */
    private DefaultMutableTreeNode createNode(Multipart multipart) {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(
                new ObjectWrapper("Multipart", multipart));

        node.add(new DefaultMutableTreeNode(
                       new ObjectWrapper("Preamble", multipart.getPreamble())));
        for (Entity part : multipart.getBodyParts()) {
            node.add(createNode(part));
        }
        node.add(new DefaultMutableTreeNode(
                       new ObjectWrapper("Epilogue", multipart.getEpilogue())));

        return node;
    }

    /**
     * Creates the tree nodes given a MIME entity (either a Message or
     * a BodyPart).
     *
     * @param entity the entity.
     * @return the root node of the tree displaying the specified entity and
     *         its children.
     */
    private DefaultMutableTreeNode createNode(Entity entity) {

        /*
         * Create the root node for the entity. It's either a
         * Message or a Body part.
         */
        String type = "Message";
        if (entity instanceof BodyPart) {
            type = "Body part";
        }
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(
                                            new ObjectWrapper(type, entity));

        /*
         * Add the node encapsulating the entity Header.
         */
        node.add(createNode(entity.getHeader()));

        Body body = entity.getBody();

        if (body instanceof Multipart) {
            /*
             * The body of the entity is a Multipart.
             */

            node.add(createNode((Multipart) body));
        } else if (body instanceof MessageImpl) {
            /*
             * The body is another Message.
             */

            node.add(createNode((MessageImpl) body));

        } else {
            /*
             * Discrete Body (either of type TextBody or BinaryBody).
             */
            type = "Text body";
            if (body instanceof BinaryBody) {
                type = "Binary body";
            }

            type += " (" + entity.getMimeType() + ")";
            node.add(new DefaultMutableTreeNode(new ObjectWrapper(type, body)));

        }

        return node;
    }

    /**
     * Called whenever the selection changes in the JTree instance showing
     * the Message nodes.
     *
     * @param e the event.
     */
    public void valueChanged(TreeSelectionEvent e) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                tree.getLastSelectedPathComponent();

        textView.setText("");

        if (node == null) {
            return;
        }

        Object o = ((ObjectWrapper) node.getUserObject()).getObject();

        if (node.isLeaf()) {

            if (o instanceof TextBody){
                /*
                 * A text body. Display its contents.
                 */
                TextBody body = (TextBody) o;
                StringBuilder sb = new StringBuilder();
                try {
                    Reader r = body.getReader();
                    int c;
                    while ((c = r.read()) != -1) {
                        sb.append((char) c);
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                textView.setText(sb.toString());

            } else if (o instanceof BinaryBody){
                /*
                 * A binary body. Display its MIME type and length in bytes.
                 */
                BinaryBody body = (BinaryBody) o;
                int size = 0;
                try {
                    InputStream is = body.getInputStream();
                    while ((is.read()) != -1) {
                        size++;
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                textView.setText("Binary body\n"
                               + "MIME type: "
                                   + body.getParent().getMimeType() + "\n"
                               + "Size of decoded data: " + size + " bytes");

            } else if (o instanceof ContentTypeField) {
                /*
                 * Content-Type field.
                 */
                ContentTypeField field = (ContentTypeField) o;
                StringBuilder sb = new StringBuilder();
                sb.append("MIME type: ").append(field.getMimeType()).append("\n");
                for (Map.Entry<String, String> entry : field.getParameters().entrySet()) {
                    sb.append(entry.getKey()).append(" = ").append(entry.getValue()).append("\n");
                }
                textView.setText(sb.toString());

            } else if (o instanceof AddressListField) {
                /*
                 * An address field (From, To, Cc, etc)
                 */
                AddressListField field = (AddressListField) o;
                MailboxList list = field.getAddressList().flatten();
                StringBuilder sb = new StringBuilder();
                for (Mailbox mailbox : list) {
                    sb.append(AddressFormatter.DEFAULT.format(mailbox, false)).append("\n");
                }
                textView.setText(sb.toString());

            } else if (o instanceof DateTimeField) {
                Date date = ((DateTimeField) o).getDate();
                textView.setText(date.toString());
            } else if (o instanceof UnstructuredField){
                textView.setText(((UnstructuredField) o).getValue());
            } else if (o instanceof Field){
                textView.setText(((Field) o).getBody());
            } else {
                /*
                 * The Object should be a Header or a String containing a
                 * Preamble or Epilogue.
                 */
                textView.setText(o.toString());
            }

        }
    }

    /**
     * Creates and displays the gui.
     *
     * @param message the Message to display in the tree.
     */
    private static void createAndShowGUI(Message message) {
        /*
         * Create and set up the window.
         */
        JFrame frame = new JFrame("MessageTree");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        /*
         * Create and set up the content pane.
         */
        MessageTree newContentPane = new MessageTree(message);
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);

        /*
         * Display the window.
         */
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        final String fileName = retrieveFileName(args);
        try {
            final MessageBuilder builder = new DefaultMessageBuilder();
            final Message message = builder.parseMessage(new FileInputStream(fileName));

            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAndShowGUI(message);
                }
            });

        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("Wrong number of arguments.");
            System.err.println("Usage: org.mime4j.samples.tree.MessageTree"
                             + " path/to/message");
        } catch (FileNotFoundException e) {
            System.err.println("The file '" + fileName + "' could not be found.");
        } catch (IOException e) {
            System.err.println("The file '" + fileName + "' could not be read.");
        } catch (MimeException e) {
            System.err.println("The file '" + fileName + "' is invalid.");
        }
    }

    private static String retrieveFileName(String[] args) {
        if (args.length < 1) {
            return ClassLoader.getSystemResource("mime-example.msg").getFile();
        }
        return args[0];
    }

}
