/*
 * COPYRIGHT (c) 1998-1999 Jan-Henrik Haukeland, http://www.tildeslash.com/
 */

package ts.biff;

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

import ts.ui.*;
import ts.config.*;
import ts.biff.*;
import ts.mail.*;
import ts.event.*;
import ts.Arch;

/** 
 * The main application window. The window displays a table with mail
 * messages, where each row represent a message.
 * <P>
 * Columns in the table represents fields in a message. Which field
 * to display is configurable. There are lots of options for configuring 
 * the apperance of the table. Check out the Configure utility that 
 * could be reached from the popup menu.
 * <P>
 * When the user right-clicks anywhere inside the window, a popup menu
 * appears with choices for:
 * <OL>
 * <LI>Getting new mail
 * <LI>Sending mail 
 * <LI>Configuring the application
 * <LI>Display an about box
 * <LI> Quit the application
 * </OL>
 *
 * It is possible to view a mail message (in detail) by double 
 * clicking on it in the table. A simple dialog will then popup
 * with all available headers and mailbody (i.e. headers supported
 * by this program)
 *
 * <P>
 * If timeout is set by the user (in Config), the window will be
 * hidden after a given amount of time has elapsed. The logic for
 * displaying and hiding the biff window is as follows: If new mail are
 * present in the mailbox this window will recive a mailevent that
 * triggers a method which in turn will make this window visible.  If
 * timeout is on, then this method will also activate a timer, to hide
 * the window after a certain time.
 *
 * <P>
 * If the Biff window becomes hidden a Wakeup object will be activated.
 * The purpose of this object is to let the user make the window visible 
 * again between new mailevents (if he wants to). The WakeUp object will
 * listen at a certain port number and if anything tries to connect to
 * that port, the Biff window will be visible.
 *
 * <P> 
 * If the user position and resizes the window  and then quits the 
 * application, the window will remember it's  geometry. That is, it 
 * will start with the same size and in the same position the next time.
 *
 * @author  Jan-Henrik Haukeland &lt;hauk@tildeslash.com&gt;
 * @version $Id: Biff.java,v 1.7 2001/08/13 12:48:28 hauk Exp $ 
 */
public final class Biff extends TsWindow
    implements View.Callback, ActionListener, MailListener, WakeUpListener
{
    private Arch         myArch;
    
    private Configure    configure;
    
    private View         myView;
    private Controller   myController;
    
    private TimeOut      myTimeOut;
    private WakeUpServer myWakeUp;
    
    private Dimension    myDimension;
    private JScrollPane  myViewPort;
    private JPopupMenu   myPopup;
    private Smtp         mySmtp;
    
    private Dimension    myScreenSize;


    /**
     * Instantiate the window with a View and a controller.
     * I really don't like to make the controller and arch visible here,
     * but its a trade off. 
     *
     * @param arch Architecture object
     * @param view A View
     * @param controller a Mail Table controller
     */
    public Biff(Arch arch, View view, Controller controller)
    {
	myArch= arch;
	myView= view;
	myController= controller;
	
	configure= Configure.getInstance();
	mySmtp= Smtp.getInstance();
	
	buildUI();
	
	installPopUp();
	installListeners();
	installTimeout();
	
	this.setVisible(true);
	this.toFront();
	
	// Since this is just a plain Window we have to use our own
	// Window Manager to manage window move and resize
	WindowManager.register(this);
    }
    

    
    /**
     * Hide and dispose the window
     */
    public void close()
    {
      //FIXME
	WindowManager.deregister(this);

	myController.removeMailListener(this);
	
	if ( myWakeUp != null ) 
	    myWakeUp.removeWakeUpListener(this);

	Object o[]= myPopup.getSubElements();
	for ( int i= 0; i<o.length; i++)
	    if ( o[i] instanceof JMenuItem)
		((JMenuItem)o[i]).removeActionListener(this);

	myArch=null;
        configure= null;
	myView=null;
	myController= null;
	myTimeOut=null;
	myWakeUp=null;
	myDimension=null;
	myViewPort=null;
	myPopup=null;
	mySmtp=null;
	myScreenSize= null;

	this.setVisible(false);
	this.dispose();
    }
    
    
    
    /**
     * Actionlistener for popup menu events
     * @param e An ActionEvent
     */
    public void actionPerformed(ActionEvent e)
    {
	String command= e.getActionCommand();
	
	if (command.equals("quit")) {
	    myController.terminate();
	    exitPersistent();
	}
	else if (command.equals("config")) {
	    setGeometry();
	    setColumnSize();
	    
	    configure.showConfigure();
	    if ( configure.isChanged() ) {
		newBiff();
	    }
	}
	else if (command.equals("get")) {
	    myController.wakeUp();
	}
	else if (command.equals("send")) {
	    mySmtp.showSmtp();
	}
	else if (command.equals("about")) {
	    Util.showAbout();
	}
	else if (command.equals("hide")) {
	    Biff.this.setVisible(false);
	    installWakeUp();
	}
	
    }
    
    
    
    /**
     * Called when a Mail event occurs. Popup this window on new mail
     * (only). If a TimeOut object exist, restart it.
     *
     * @param e The mail event 
     */
    public void mailEvent(MailEvent e)
    { 
	if ( (e.getDropSize() > 0) && (! this.isVisible()) ) 
	{
	    this.setVisible(true);
	    this.toFront();
	    
	    if ( myTimeOut != null ) { 
		myTimeOut.restart(); 
	    }
	}
    }
    

  
  /**
   * Called when a WakeUp event occurs. Makes this window
   * visible if it's hidden
   *
   * @param w A WakeUp event
   */
    public void wakeUp(WakeUpEvent w)
    {
	if ( ! this.isVisible() )  {
	    this.setVisible(true);
	    this.toFront();
	}
    }



  /**
   * Called on a single click on a row. 
   * @param aTable view
   * @param row    row selected
   * @param col    column selected
   */
    public void singleClick(Object aTable, int row, int col)
    {
      // Left empty
    }


  
  /**
   * Called upon double-clicking on a row. 
   *
   * @param aTable view
   * @param row    row selected
   * @param col    column selected
   */
    public void doubleClick(Object aTable, int row, int col)
    {
      // Popup the selected message in a dialog for viewing the
      // "complete" mail
      String id= (String)((View)aTable).getRow(row)[0];
      myView.clearSelection();
      
      showMailDialog(myController.getMailModel(id).toString(),id);
    }




    //----------------------- private methods -------------------------//


  /**
   * Display the content of a MailModel in a simple dialog
   * @param m a MailModel content to display in a dialog
   * @param t dialog title
   */
  private void showMailDialog(String m, String t)
  {
    MailContentDialog.show(m,"Message-id: "+t);
  }

  
  
  /** 
   * Create a new instance of the Biff window with friends if anything
   * in the Config properties was changed. 
   * <P> 
   * This is a design bug - changes should be dynamically. Besides
   * this will make the JVM allocate a little more memory since we 
   * load a lot of thingy again. All the objects making up the window
   * will be nulled out and should be collected by the gc. Thats the 
   * theory, at least. 
   */
  private void newBiff()
  {
    if ( myWakeUp != null )
	myWakeUp.removeWakeUpListener(this);
    myWakeUp= null;
    myTimeOut= null;
    
    WindowManager.deregister(this);
    
    // FIXME: Ugly Should probably be done with events.
    myArch.createNewBiff();  
  }



  /**
   * Create the user interface for this class
   */
  private void buildUI()
  {
    myDimension= new Dimension(Config.getint("window.width"),
			       Config.getint("window.height"));
    
    myViewPort= new JScrollPane(myView,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    
    myView.setPreferredScrollableViewportSize(myDimension);
    
    getContentPane().add(myViewPort);
    
    setSize(myDimension);
    
    setLocation(Config.getint("window.location.x"),
		Config.getint("window.location.y"));
    
    
    myScreenSize= Toolkit.getDefaultToolkit().getScreenSize();
    
    // Set prefered column widths in view table
    myView.setColumnWidths(Config.getIntValues("mail.table.column.widths"));

    myView.setBackground(Config.getColor("mail.table.bgcolor"));
    myViewPort.setBackground(Config.getColor("mail.table.bgcolor"));
    myViewPort.getViewport().setBackground(Config.getColor("mail.table.bgcolor"));
    this.setBackground(Config.getColor("mail.table.bgcolor"));

  }   




  /**
   * Shows the popup menu at the mouse position, that is,  if
   * the popup menu pops up outside the bottom of the screen, relocate
   * it, so its lower edge is at the exact screen bottom. -- It's
   * amazing that this hasn't been fixed by the swing teams pencil neck
   * developers.  
   * @param e mouse event from where to get position from 
   */
  private void showPopUp(MouseEvent e)
  {
    int x= e.getX(); 
    int y= e.getY();
    Component c= e.getComponent();
    
    myPopup.show(c, x, y);
    
    Dimension popSize= myPopup.getSize(); 
    Point p= myPopup.getLocationOnScreen();
    if ( (popSize.height + p.y) > myScreenSize.height ) {
      myPopup.setLocation(p.x, (myScreenSize.height - popSize.height));
    }
  }
  


  /**
   * Install the windows popup menu
   */
  private void installPopUp()
  {
    JPopupMenu p= new JPopupMenu();
    
    p.setDefaultLightWeightPopupEnabled(false);
    
    for(int i= 0, j=0; i<popLabels.length; i++, j++){
      JMenuItem menuItem = new JMenuItem(popLabels[i]);
      
      if ( popLabels[i].startsWith("-") ) {
	p.addSeparator();
	j--;
	continue;
      }
      
      menuItem.addActionListener(this);
      menuItem.setActionCommand(popCommands[j]);
      p.add(menuItem);
    }
    
    myPopup= p;
  }



  
  /**
   * Install listeners for the window; for triggering the popup menu
   * and for receiving mail events. When <I>Space</I> is pressed hide
   * this window 
   */
  private void installListeners()
  {
    
    // Make this Object receive mail events
    myController.addMailListener(this);
    
    // A popup menu trigger
    MouseAdapter pop= new MouseAdapter() {
	  public void mouseClicked(MouseEvent e) {
	    if ( e.getModifiers() == InputEvent.BUTTON2_MASK  ||
		 e.getModifiers() == InputEvent.BUTTON3_MASK  ||
		 e.isPopupTrigger() )
	    {
	      showPopUp(e);
	    }
	  }
	};
    
    
    myView.addMouseListener(pop);
    myView.getTableHeader().addMouseListener(pop);
    myViewPort.addMouseListener(pop);
  }





  /**
   * Install timeout. If the user has choosen to hide the window
   * after a certain amount of time, this method will install a 
   * timeout object to do that. If not, this method will do nothing.
   */
  private void installTimeout()
  {
    if ( myTimeOut == null && Config.getboolean("window.timeout") ) {
      myTimeOut= new TimeOut(Config.getint("window.timeout.seconds"));
    }
  }
  
  
  
  /**
   * Install a WakeUp server. See WakeUpServer class.
   */
  private void installWakeUp()
  {
    if ( myWakeUp == null ) {
      myWakeUp= WakeUpServer.getInstance();
      myWakeUp.addWakeUpListener(this);
    }
  }
    
  
  
  /**
   * Save the Biff window's current location and size on screen and
   * column size to the users control file, before terminating the
   * application.
   */
  private void  exitPersistent() 
  {
    // Set current window size and position in Config
    setGeometry();
    
    // Set current column widths in Config
    setColumnSize();
    
    // Save current values to the users control file
    try {
      Config.getInstance().save(Constant.USER_CONTROL_FILE);
    }catch (ConfigException ce) {
      String msg= "Warning: Could not save window size and position"+
	  "\nReason:"+ ce.getMessage();
      System.err.println(msg);
      Util.showError(msg);
    }
    
    // Close the window
    this.close();
    
    // Do a controlled exit
    TerminateController.exit();	
  }
  
  
  
  /**
   * Set current view table column size in Config
   */
  private void setColumnSize()
  {
    int[] i= myView.getColumnWidths();
    int l= i.length;
    String w= "";
    for ( int j= 0; j<l; j++ ) {
      w+= i[j] + " ";
    }
    Config.set("mail.table.column.widths", w);
  }
  
  
  
  
  /**
   * Set current geometry in Config
   */
  private void setGeometry()
  {
    // Get and save the window's size
    Dimension d= this.getSize();
    Config.set("window.width", d.width+"");
    Config.set("window.height", d.height+"");
    
    // Get and save the window's location on screen
    Point p= this.getLocationOnScreen();
    Config.set("window.location.x", p.x+"");
    Config.set("window.location.y", p.y+"");
  }
  
  
  
  
  // ---------------------------------------------------------------- //
  
  
  
  
  /**
   * Display the content of a mail message in a simple dialog
   */
  private static class MailContentDialog
  {
    JTextArea   c= new JTextArea();
    JDialog     d= new JDialog();
    
    public static void show(String c,String t) {
      MailContentDialog d= new MailContentDialog(c,t);
      d.show();
    }
    
    /**
     * @param text mail content
     * @param title dialog title (Mail id)
     */
    private MailContentDialog(String text, String title)
    {
      // Set the same colors as the biff window
      c.setForeground(Config.getColor("mail.table.fgcolor"));
      c.setBackground(Config.getColor("mail.table.bgcolor"));
      
      c.setText(text);
      c.setLineWrap(false);
      c.setFont(Font.decode("Monospaced-plain-12"));
      
      JScrollPane scroll= 
	  new JScrollPane(c, ScrollPaneConstants.
			  VERTICAL_SCROLLBAR_AS_NEEDED,
			  ScrollPaneConstants.
			  HORIZONTAL_SCROLLBAR_AS_NEEDED) {
		public Dimension getPreferredSize() {
		  return new Dimension(600,400);
		}
	      };
      
      scroll.getViewport().setViewSize(new Dimension(c.getWidth(),
						     c.getHeight()));
      d.setTitle(title);
      d.getContentPane().add(scroll);
      d.addWindowListener( new WindowAdapter() {
	    public void windowClosing(WindowEvent e) {
	      d.setVisible(false);
	      d.dispose();
	    }
	  });
    }
    
    public void show() {
      d.show();
      d.pack();
    }
    
  }


  
  /**
   * A simple timeout class that hides the Biff after
   * a given amount of time has elapsed.
   */
  private class TimeOut implements ActionListener 
  {
    private Timer myTimer;
    private int   milliSeconds;
    
    /**
     * Hide the biff window after n seconds
     * @param seconds until the biff window is hidden
     */
    public TimeOut(int seconds) {
      milliSeconds= seconds * 1000;
      myTimer= new Timer(milliSeconds, this);
      myTimer.setRepeats(false);
      myTimer.start();
    }
    
    public void actionPerformed(ActionEvent e) 
    {
      if ( Biff.this.isVisible() ) {
	Biff.this.setVisible(false);
	// Just to make absolutely sure that the timer
	// doesn't starts to eat cpu-time:
	this.stop(); 
	// Install a Wakeup object
	installWakeUp();
      }
    }
    
    public void stop()
    {
      myTimer.stop();
    }
    
    public void restart()
    {
      myTimer.restart();
    }
    
    public void setDelay(int seconds)
    {
      milliSeconds= seconds * 1000;
      myTimer.setDelay(milliSeconds);
    }
    
  }
  
  
  
  
  
  /** Names that appeare in the popup menu */
  private String[] popLabels= Config.getCValues("popup.menu.names");
  
  /** Action commands for each choice in the popup menu */
  private String[] popCommands= {"get",
				 "send",
				 "config",
				 "hide",
				 "about",
				 "quit"};
}
