import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.Properties;
import java.util.Vector;

import javax.mail.*;
import javax.activation.*;
import javax.mail.internet.*;
import javax.mail.internet.MimeMultipart;
import com.sun.mail.pop3.POP3Folder;

/**
 * This class implements a Thread that provides the mail fetching services,
 * and the message list and display windows.
 *
 * @author      Simon Pollard
 */
public class PopFetcher extends Thread implements ActionListener, ListSelectionListener {

  private boolean threadRunning;
  private Vector myListeners;
  /** The fetch wait wasn't interrupted - perform a timed check */
  public static final int TIMEDCHECK = 1;
  /** The fetch wait was interrupted by a check mail request */
  public static final int FORCECHECK = 2;
  /** The fetch wait was interrupted by a list messages request */
  public static final int SHOWMESSAGES = 3;
  private int checkFlag;
  private boolean busyFlag = false;
  private Properties myProperties;
  private Store popStore = null;

  private JFrame mailFrame = null;
  private JLabel messagesLabel;
  private JButton okButton, showButton;
  private JButton msgOkButton, msgRefreshButton, msgViewButton, msgDeleteButton;
  private JButton viewOkButton;
  private JFrame messageListFrame = null;
  private JFrame msgFrame = null;

  private MailModel mailData;
  private JTable mailTable;

  /**
   * The constructor sets up an initial set of properties, and creates an
   * empty set of PopFetcherEvent listeners
   *
   * @param     initProperties the initial set of configuration properties
   */
  public PopFetcher(Properties initProperties) {
    myProperties = new Properties();
    myListeners = new Vector();
    myProperties.putAll(initProperties);
  }

  /**
   * Called to start the thread.  It sleeps the configured number of seconds
   * before checking for mail, or until interrupted externally.
   */

  public void run() {
    threadRunning = true;

    while(threadRunning) {
      try {
        checkFlag = TIMEDCHECK;

        firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEWAITING, 0));

        Thread.sleep(1000 * Integer.decode(myProperties.getProperty(PopProperties.FETCHTIME)).intValue());
      } catch(InterruptedException e) {
        // interrupted - eg by a GUI click
      }

      if(checkFlag == SHOWMESSAGES)
        showMessages();
      else
        checkMail(checkFlag);
    }
  }

  /**
   * Called to shut down the PopFetcher thread cleanly.
   */
  public void endThread() { threadRunning = false; interrupt(); }

  /**
   * Called by a class to add itself to the list of PopFetcherEvent listeners
   *
   * @param     p the class adding itself
   */
  public void addPopFetcherListener(PopFetcherListener p)
    { myListeners.addElement(p); }

  /**
   * Called by a class to remove itself from the list of PopFetcherEvent listeners
   *
   * @param     p the class removing itself
   */
  public void removePopFetcherListener(PopFetcherListener p)
    { myListeners.removeElement(p); }

  /**
   * Called when the configuration properties have been changed externally,
   * so the set contained in this class must be updated.
   *
   * @param     newProperties the new set of properties
   */
  public void updateProperties(Properties newProperties) {
    myProperties.clear();
    myProperties.putAll(newProperties);
  }

  /**
   * Returns whether the mailbox is currently in use (by this class)
   *
   * @return    true if the mailbox is in use
   */
  public boolean getBusyFlag() { return busyFlag; }

  /**
   * Sets an indicator to represent why the fetch wait was interrupted
   *
   * @param     newVal the new value of the indicator
   */
  public void setCheckFlag(int newVal) { checkFlag = newVal; }

  /**
   * Called to notify listeners of a PopFetcherEvent
   *
   * @param     e the PopFetcherEvent that occurred
   */
  protected void firePopFetcherEvent(PopFetcherEvent e) {
    PopFetcherListener p;

    for(int i = 0; i < myListeners.size(); i++) {
      p = (PopFetcherListener) myListeners.elementAt(i);
      p.popFetcherEvent(e);
    }
  }

  private Folder logOn() {
    Session popSession = Session.getDefaultInstance(new Properties(), null);
    Folder mailFolder = null;

    try {
       popStore = popSession.getStore("pop3");
    } catch(Exception e) {
      System.out.println("You haven't installed the pop3 provider for javax.mail.  Exiting.");
      System.exit(0);
    }

    try {
      popStore.connect(myProperties.getProperty(PopProperties.SERVER),
                       myProperties.getProperty(PopProperties.USERNAME),
                       myProperties.getProperty(PopProperties.PASSWORD));
    } catch(Exception e) {
      firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEERRORLOGON, 0));
      e.printStackTrace();
      return null;
    }

    try {
      mailFolder = popStore.getDefaultFolder();
      mailFolder = mailFolder.getFolder("INBOX");
      mailFolder.open(Folder.READ_ONLY);
    } catch(Exception e) {
      firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEERRORFOLDER, 0));
      e.printStackTrace();
      return null;
    }

    return mailFolder;
  }

  private void logOff(Folder mailFolder) {

    firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEDISCONNECTING, 0));

    try {
      mailFolder.close(true);
      popStore.close();
    } catch(Exception e) {
      firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEERRORCLOSE, 0));
      e.printStackTrace();
      return;
    }
  }

  private void checkMail(int checkType) {

    Folder mailFolder = null;
    int totMessages = 0;

    if(busyFlag) return;

    busyFlag = true;

    firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFECHECKING, 0));

    mailFolder = logOn();
    if(mailFolder == null) { busyFlag = false; return; }

    try {
      totMessages = mailFolder.getMessageCount();
    } catch(Exception e) {
      firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEERRORFOLDER, 0));
      e.printStackTrace();
      busyFlag = false;
      return;
    }

    firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEMESSAGES, totMessages));

    if((checkType == TIMEDCHECK) && (totMessages > 0)) {
      if(mailFrame == null) {
        mailFrame = new JFrame("Mail waiting");

        okButton = new JButton("OK");
        okButton.setMnemonic('O');
        okButton.addActionListener(this);
        showButton = new JButton("Show Messages");
        showButton.setMnemonic('S');
        showButton.addActionListener(this);

        JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 5, 5));
        buttonPanel.add(okButton);
        buttonPanel.add(showButton);

        messagesLabel = new JLabel("You have " + totMessages + " messages");
        mailFrame.getContentPane().setLayout(new BorderLayout());
        mailFrame.getContentPane().add(BorderLayout.CENTER, messagesLabel);
        mailFrame.getContentPane().add(BorderLayout.SOUTH, buttonPanel);

        mailFrame.pack(); mailFrame.setVisible(true);
      } else messagesLabel.setText("You have " + totMessages + " messages");
    }
    logOff(mailFolder);

    busyFlag = false;
  }

  private void showMessages() {
    Folder mailFolder = null;
    JButton thisButton;
    JLabel thisLabel;
    int numMessages;

    if(busyFlag) return;

    busyFlag = true;

    // already open?
    if(messageListFrame != null) { busyFlag = false; return; }

    firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEFETCHING, 0));

    mailFolder = logOn();
    if(mailFolder == null) { busyFlag = false; return; }

    POP3Folder popFolder = (POP3Folder)mailFolder;

    try {

      Message messageList[] = mailFolder.getMessages();

      numMessages = messageList.length;
      firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEMESSAGES, numMessages));
      
      if(numMessages > 0) {
        mailData = new MailModel(numMessages);

        for(int i = 0; i < numMessages; i++) {
          double tmp = i; tmp = (tmp / numMessages) * 100;

          firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEFETCHPERCENT,
             (int) tmp));

          mailData.setValueAt(InternetAddress.toString(messageList[i].getFrom()), i, 0);
          mailData.setValueAt(messageList[i].getSubject(), i, 1);
          mailData.setUID(popFolder.getUID(messageList[i]), i);
        }
        mailTable = new JTable(mailData);
        mailTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        mailTable.getSelectionModel().addListSelectionListener(this);
        JScrollPane mailPane = new JScrollPane(mailTable);

        msgViewButton = new JButton("View");
        msgViewButton.setMnemonic('V');
        msgViewButton.addActionListener(this);
        msgViewButton.setEnabled(false);
        msgDeleteButton = new JButton("Delete");
        msgDeleteButton.setMnemonic('D');
        msgDeleteButton.addActionListener(this);
        msgDeleteButton.setEnabled(false);
        msgOkButton = new JButton("OK");
        msgOkButton.setMnemonic('O');
        msgOkButton.addActionListener(this);
        msgRefreshButton = new JButton("Refresh");
        msgRefreshButton.setMnemonic('R');
        msgRefreshButton.addActionListener(this);

        JPanel buttonPanel = new JPanel(new GridLayout(2, 2, 5, 5));
        buttonPanel.add(msgViewButton);
        buttonPanel.add(msgDeleteButton);
        buttonPanel.add(msgRefreshButton);
        buttonPanel.add(msgOkButton);

        messageListFrame = new JFrame("Messages on server");
        messageListFrame.getContentPane().setLayout(new BorderLayout());
        messageListFrame.getContentPane().add(BorderLayout.CENTER, mailPane);
        messageListFrame.getContentPane().add(BorderLayout.SOUTH, buttonPanel);
        messageListFrame.pack(); messageListFrame.setVisible(true);
      }      
    } catch(Exception e) {
      firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEERRORFOLDER, 0));
      e.printStackTrace();
      busyFlag = false;
      return;
    }

    logOff(mailFolder);

    firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEFETCHDONE, 0));

    busyFlag = false;
  }

  /**
   * Called when a window button has been clicked.  Appropriate action
   * is taken.
   *
   * @param     e the ActionEvent that occurred
   */
  public void actionPerformed(ActionEvent e) {

    if(e.getSource() == okButton) { // OK button on 'You have X messages'
      mailFrame.dispose(); mailFrame = null;
    } else

    if(e.getSource() == showButton || e.getSource() == msgRefreshButton) {
      // Show Messages button on same, refresh button on message list
      if(busyFlag)
        JOptionPane.showMessageDialog(null, "Please wait - system busy");
      else {
        if(e.getSource() == showButton) {
          mailFrame.dispose(); mailFrame = null;
        }

        if(messageListFrame != null)
          { messageListFrame.dispose(); messageListFrame = null; }

        setCheckFlag(SHOWMESSAGES);
        interrupt();
      }
    } else

    if(e.getSource() == msgOkButton) { // OK button on message list
      messageListFrame.dispose(); messageListFrame = null;
    } else

    if(e.getSource() == msgViewButton || e.getSource() == msgDeleteButton) { // View / Delete message
      if(mailTable.getSelectedRow() == -1)
        JOptionPane.showMessageDialog(null, "No message selected");
      else if(busyFlag)
        JOptionPane.showMessageDialog(null, "Please wait - system busy");
      else {
        String whichID;
        Folder mailFolder = null;
        Message[] searchResult;
        boolean deleting = (e.getSource() == msgDeleteButton);

        if(!deleting && (msgFrame != null)) return; // only one message at a time

        busyFlag = true;

        if(deleting) {
          int result = JOptionPane.showConfirmDialog(null, "Really delete this message?", "Delete Confirmation", JOptionPane.YES_NO_OPTION);
          if(result == JOptionPane.NO_OPTION) { busyFlag = false; return; }
        }

        whichID = mailData.getUID(mailTable.getSelectedRow());

        mailFolder = logOn();
        if(mailFolder == null) { busyFlag = false; return; }

        POP3Folder popFolder = (POP3Folder)mailFolder;

        try {
          Message messageList[] = mailFolder.getMessages();

          for(int i = 0; i < messageList.length; i++) {
            if(popFolder.getUID(messageList[i]).compareTo(whichID) == 0) {
              if(deleting)
                messageList[i].setFlag(Flags.Flag.DELETED, true);
              else { // viewing
                JFrame waitFra = new JFrame();

                waitFra.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
                waitFra.getContentPane().add(new JLabel("Fetching message - please wait...", SwingConstants.CENTER));
                waitFra.pack(); waitFra.setLocation(new Point(300,200));
                waitFra.setVisible(true);
                waitFra.update(waitFra.getGraphics()); // force redraw

                StringBuffer msgText = new StringBuffer("From: " + InternetAddress.toString(messageList[i].getFrom()) + "\n");
                msgText.append("Subject: " + messageList[i].getSubject() + "\n\n");

                if(messageList[i].getContent() instanceof MimeMultipart) {
                  MimeMultipart mPart = (MimeMultipart) messageList[i].getContent();

                  for(int j = 0; j < mPart.getCount(); j++) {
                    if(mPart.getBodyPart(j).isMimeType("text/plain"))
                      msgText.append((String) mPart.getBodyPart(j).getContent() + "\n\n");
                    else
                      msgText.append("Attached: " + mPart.getBodyPart(j).getContentType() + "\n\n");
                  }
                } else
                  msgText.append((String) messageList[i].getContent());

                JTextArea msgTextArea = new JTextArea(msgText.toString(), 20, 60);
                msgTextArea.setEditable(false);

                viewOkButton = new JButton("OK");
                viewOkButton.setMnemonic('O');
                viewOkButton.addActionListener(this);
                Box okBox = Box.createHorizontalBox();
                okBox.add(Box.createHorizontalGlue());
                okBox.add(viewOkButton);
                okBox.add(Box.createHorizontalGlue());

                JScrollPane msgWindow = new JScrollPane(msgTextArea);

                msgFrame = new JFrame("Message");
                msgFrame.getContentPane().setLayout(new BorderLayout());
                msgFrame.getContentPane().add(BorderLayout.CENTER, msgWindow);
                msgFrame.getContentPane().add(BorderLayout.SOUTH, okBox);
                msgFrame.pack(); msgFrame.setVisible(true);

                waitFra.dispose();
              }
            }
          }
        } catch(Exception e2) {
          e2.printStackTrace();
          firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEERRORFOLDER, 0));
        }

        logOff(mailFolder);
        firePopFetcherEvent(new PopFetcherEvent(this, PopFetcherEvent.PFEWAITING, 0));

        busyFlag = false;

        if(deleting) { // refresh the list
          messageListFrame.dispose(); messageListFrame = null;
          setCheckFlag(SHOWMESSAGES);
          interrupt();
        }
      }
    } else

    if(e.getSource() == viewOkButton) { // OK button on message detail
      msgFrame.dispose(); msgFrame = null;
    }
  }

  /**
   * Called when the selected table row has changed.  If there is a
   * currently selected row, it enables the 'View' and 'Delete' buttons,
   * otherwise it disables them.
   *
   * @param     e the ListSelectionEvent that occurred
   */
  public void valueChanged(ListSelectionEvent e) {
    if(mailTable.getSelectedRow() == -1)
    {
      msgViewButton.setEnabled(false);
      msgDeleteButton.setEnabled(false);
    } else {
      msgViewButton.setEnabled(true);
      msgDeleteButton.setEnabled(true);
    }
  }
}
