package sale.multiwindow;

import javax.swing.*;
import javax.swing.event.*;

import java.awt.event.*;

import sale.*;
import sale.events.*;

import util.*;

import java.io.*;

/**
  * A handle returned by {@link MultiWindow#getNewHandle() getNewHandle}.
  *
  * <p>This class represents a subframe inside a {@link MultiWindow}.</p>
  *
  * @author Stephan Gambke
  * @version 2.0 03/06/1999
  * @since v2.0
  */
public class MultiWindowHandle extends Object implements FormSheetContainer,
                                                         Display,
                                                         Serializable {

  /**
    * Identifier uniquely identifying the handle in its MultiWindow.
    *
    * @serial
    */
  private Integer m_iID;

  /**
    * Internal frame used for rendering the display in multiframe mode.
    */
  private transient JInternalFrame m_jifFrame = null; // transient: will not be stored, but rather be restored after loading

  /**
    * The MultiWindow owning this handle.
    */
  private transient MultiWindow m_mwOwner = null;

  /**
    * The FormSheet currently being displayed.
    *
    * @serial
    */
  private FormSheet m_fsFormSheet = null;

  /**
    * The current MenuSheet.
    *
    * @serial
    */
  private MenuSheet m_msMenuSheet = null;

  /**
    * The display caption.
    *
    * @serial
    */
  private String m_sDisplayCaption = null;

  /**
    * The current component.
    */
  private transient JComponent m_jcmpComponent = null;

  /**
    * The current button panel.
    */
  private transient JPanel m_jpButtonPanel = null;

  /**
    * The complete control consisting of component and button bar.
    */
  private transient JComponent m_jcmpContentPane = null;

  /**
    * The listeners listening to this display.
    *
    * @serial
    */
  private ListenerHelper m_lhListeners = new ListenerHelper();

  /**
    * True, if this display's internal frame must be packed the next time, we switch to
    * the multiframe view.
    *
    * @serial
    */
  private boolean m_fIsNew;

  /**
    * The object on which calls to {@link #setFormSheet} block.
    */
  private transient Object m_oWaiter;
  /**
    * Return the object on which calls to {@link #setFormSheet} block.
    *
    * @override Never
    */
  private final Object getWaiter() {
    if (m_oWaiter == null) {
      m_oWaiter = new Object();
    }

    return m_oWaiter;
  }

  /**
    * Writes the default serializable fields as well as the display title and the bounds of the internal frame.
    */
  private void writeObject (ObjectOutputStream oos) throws IOException {
    util.Debug.print ("Writing MultiWindowHandle \"" + getDisplayCaption() + "\".", -1);

    oos.defaultWriteObject();

    oos.writeObject (getFrame().getTitle());
    oos.writeObject (getFrame().getBounds());

    util.Debug.print ("Finished writing MultiWindowHandle \"" + getDisplayCaption() + "\".", -1);
  }

  /**
    * Reads the default serializable fields and the display title and bounds of the associated internal frame.
    * Then {@link java.io.ObjectInputStream#registerValidation registers} a validation routine with a priority
    * of {@link sale.OIV#MULTIWINDOW_HANDLE_PRIO} which will restore the internal frame and its contents.
    */
  private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {

    ois.defaultReadObject();

    getFrame().setTitle ((String) ois.readObject());
    final java.awt.Rectangle rcBounds = (java.awt.Rectangle) ois.readObject();

    ois.registerValidation (new ObjectInputValidation() {
      public void validateObject() {
        getFrame().setBounds (rcBounds);

        if (m_fsFormSheet != null) {
          synchronized (m_fsFormSheet.getComponentLock()) {
            synchronized (m_fsFormSheet.getButtonsLock()) {
              onFormSheetComponentChanged (m_fsFormSheet, m_fsFormSheet.getComponent());

              m_jpButtonPanel = null;
              m_fsFormSheet.fillBtnPanel (getButtonPanel());

              m_jcmpContentPane.validate();
            }
          }
        }
      }
    },
                            OIV.MULTIWINDOW_HANDLE_PRIO);
  }

  /**
    * Creates a MultiWindowHandle with the given ID.
    *
    * @param nNewID the ID of this handle
    */
  MultiWindowHandle (Integer iNewID) {
    super();
    m_iID = iNewID;

    m_jifFrame = null;
  }

  //////////////////////////////////////////////////////////////////////////
  // Methods needed to dispatch events to the MultiWindow
  //////////////////////////////////////////////////////////////////////////

  /**
    * Registers the MultiWindow owning this MultiWindowHandle.
    *
    * @override Never
    *
    * @param mwOwner the new owner of this handle
    */
  final void setOwner (MultiWindow mwOwner) {
    m_mwOwner = mwOwner;

    if (m_mwOwner != null) {
      m_fIsNew = (m_mwOwner.getViewMode() == MultiWindow.TABBED);
    }
  }

  /**
    * Notifies the owner that the MenuSheet of the handle has changed.
    *
    * @override Never
    *
    * @param msNewMenuSheet the new MenuSheet
    */
  private final void fireMenuSheetChanged (MenuSheet msNewMenuSheet) {
    if (m_mwOwner != null) m_mwOwner.onMenuSheetChanged (this, msNewMenuSheet);
  }

  /**
    * Notifies the owner that the caption of the handle's display has changed.
    *
    * @override Never
    *
    * @param sNewCaption the new caption
    */
  private final void fireDisplayCaptionChanged (String sNewCaption) {
    if (m_mwOwner != null) m_mwOwner.onDisplayCaptionChanged (this, sNewCaption);
  }

  /**
    * Notifies the owner that the display of the handle has gained the focus.
    *
    * @override Never
    */
  private final void fireDisplayFocusGained() {
    if (m_mwOwner != null) m_mwOwner.onDisplayFocusGained (this, m_iID);
  }

  /**
    * Notifies the owner that the displaying frame has to be closed.
    * This method is called by {@link #close close}.
    *
    * @override Never
    */
  private final void fireDisplayClosing() {
    if (m_mwOwner != null) m_mwOwner.onDisplayClosing (this);
  }

  /**
    * Notify the owner of a change in the display's component.
    *
    * @override Never
    *
    * @param jcmpNewComponent the new component
    */
  private final void fireComponentChanged (JComponent jcmpNewComponent) {
    if (m_mwOwner != null) m_mwOwner.onComponentChanged (this, jcmpNewComponent);
  }

  //////////////////////////////////////////////////////////////////////////
  // Implementation of sale.FormSheetContainer
  //////////////////////////////////////////////////////////////////////////

  /**
    * Closes the FormSheet of this handle.
    *
    * <p>Since a MultiWindowHandle can display only one (or no) FormSheet at a time,
    * the parameter will be ignored and the current FormSheet will be removed.</p>
    *
    * @override Never
    *
    * @param fs the FormSheet to be closed. Ignored.
    */
  public void closeFormSheet (FormSheet fs) {
    closeFormSheet (true);
  }

  /**
    * Method to be called when the FormSheet's caption has changed.
    *
    * @override Never
    *
    * @param fs the FormSheet. Ignored.
    * @param sNewCaption the new caption
    */
  public void onFormSheetCaptionChanged (FormSheet fs, String sNewCaption) {

    String sTitle = getDisplayCaption() + ((sNewCaption != null)?" - " + sNewCaption:"");

    getFrame().setTitle (sTitle);

    fireDisplayCaptionChanged (sTitle);
  }

  /**
    * Method to be called when the FormSheet's component has changed.
    *
    * @override Never
    *
    * @param fs the FormSheet.
    * @param jcmpNewComponent the new component
    */
  public void onFormSheetComponentChanged (FormSheet fs, JComponent jcmpNewComponent) {
    synchronized (fs.getComponentLock()) {

      if (m_jcmpComponent != null) {
        m_jcmpContentPane.remove (m_jcmpComponent);
      }

      if (jcmpNewComponent != null) {
        m_jcmpContentPane.add (jcmpNewComponent, "Center");
      }
      m_jcmpComponent = jcmpNewComponent;

      fireComponentChanged (m_jcmpComponent);
    }
  }

  /**
    * Method to be called when a new button has been added to the FormSheet.
    *
    * @override Never
    *
    * @param fs the FormSheet.
    * @param fbNewButton the added button
    */
  public void onFormSheetButtonAdded (FormSheet fs, FormSheet.FormButton fbNewButton) {
    synchronized (fs.getButtonsLock()) {
      getButtonPanel().add (fbNewButton.getPeer());
      m_jcmpContentPane.validate();
      m_jcmpContentPane.repaint();
    }
  }

  /**
    * Method to be called when a button has been removed from the FormSheet.
    *
    * @override Never
    *
    * @param fs the FormSheet.
    * @param fbRemovedButton the removed button
    */
  public void onFormSheetButtonRemoved (FormSheet fs, FormSheet.FormButton fbRemovedButton) {
    synchronized (fs.getButtonsLock()) {
      getButtonPanel().remove (fbRemovedButton.getPeer());
      getButtonPanel().validate();
      getButtonPanel().repaint();
    }
  }

  /**
    * Method to be called when all buttons have been removed from the FormSheet.
    *
    * @override Never
    *
    * @param fs the FormSheet.
    */
  public void onFormSheetButtonsCleared (FormSheet fs) {
    synchronized (fs.getButtonsLock()) {
      getButtonPanel().removeAll();
      getButtonPanel().validate();
      getButtonPanel().repaint();
    }
  }

  //////////////////////////////////////////////////////////////////////////
  // Implementation of sale.Display
  //////////////////////////////////////////////////////////////////////////

  /**
    * Sets the FormSheet of this MultiWindowHandle.
    * Additionally, notifies the owning MultiWindow.
    *
    * @param fsNewFormSheet the FormSheet to be set
    *
    * @override Never
    *
    * @exception InterruptedException if an interrupt occurred, while waiting for the FormSheet
    * to be closed.
    */
  public void setFormSheet (FormSheet fsNewFormSheet)
    throws InterruptedException {

    // close any previous FormSheet, making the action explicit if this is a setFormSheet (null)
    closeFormSheet (fsNewFormSheet == null);

    if (fsNewFormSheet != null) {
      synchronized (fsNewFormSheet.getComponentLock()) {
        synchronized (fsNewFormSheet.getButtonsLock()) {
          m_fsFormSheet = fsNewFormSheet;
          fsNewFormSheet.attach (this);

          onFormSheetCaptionChanged (m_fsFormSheet, m_fsFormSheet.getCaption());

          JComponent jcmp = m_fsFormSheet.getComponent();
          onFormSheetComponentChanged (m_fsFormSheet, jcmp);

          m_jpButtonPanel = null;
          m_fsFormSheet.fillBtnPanel (getButtonPanel());

          m_jcmpContentPane.validate();
          //m_jcmpContentPane.repaint();

          fireComponentChanged (jcmp);
        } //sync
      } //sync
    }  //if

    fireFormSheetSet();

    if ((fsNewFormSheet != null) && (fsNewFormSheet.waitResponse())) {
      synchronized (getWaiter()) {
        while (fsNewFormSheet.getDisplay() == this) {
          getWaiter().wait();
        } //while
      } //synchronized
    } //if
  } //setFormSheet

  /**
    * Closes the FormSheet currently rendered by this display.
    *
    * @override Never
    */
  public void closeFormSheet() {
    closeFormSheet (true);
  }

  /**
    * Open a fresh {@link sale.JDisplayDialog} and display the FormSheet in it.
    *
    * @override Never
    *
    * @exception InterruptedException if an interrupt occured while waiting for the
    * FormSheet to be closed.
    */
  public void popUpFormSheet (FormSheet fs)
    throws InterruptedException {

    JDisplayDialog jdd = new JDisplayDialog (m_mwOwner);

    jdd.setVisible (true);

    try {
      jdd.setFormSheet (fs);
    }
    catch (InterruptedException e) {
      if (fs.getDisplay() == jdd) {
        fs.cancel();
      }

      throw e;
    }
  }

  /**
    * Set the MenuSheet of this display.
    *
    * @override Never
    *
    * @param msNewMenuSheet the MenuSheet to be set
    */
  public void setMenuSheet (MenuSheet msNewMenuSheet) {
    if (m_msMenuSheet != null) {
      m_msMenuSheet.setVisible (false);
    }

    m_msMenuSheet = msNewMenuSheet;

    if (msNewMenuSheet == null) {
      getFrame().setJMenuBar (null);
    }
    else {
      getFrame().setJMenuBar (m_msMenuSheet.getMenuBar());
      m_msMenuSheet.setVisible (true);
    }

    fireMenuSheetChanged (msNewMenuSheet);
  }

  /**
    * Returns wether this display is useable or not.
    * For MultiWindowHandles the result is always <code>true</code>.
    *
    * @override Never
    *
    * @return <code>true</code>
    */
  public boolean isUseableDisplay() {
    return true;
  }

  /**
    * Register a new FormSheetListener.
    *
    * @override Never
    *
    * @param fsl the listener to be registered
    */
  public void addFormSheetListener (FormSheetListener fsl) {
    m_lhListeners.add (FormSheetListener.class, fsl);
  }

  /**
    * Un-register the given FormSheetListener.
    *
    * @override Never
    *
    * @param fsl the listener to be un-registered
    */
  public void removeFormSheetListener (FormSheetListener fsl) {
    m_lhListeners.remove (FormSheetListener.class, fsl);
  }

  //////////////////////////////////////////////////////////////////////////
  // other Methods
  //////////////////////////////////////////////////////////////////////////

  /**
    * Returns the ID of this MultiWindowHandle.
    *
    * @override Never
    */
  final Integer getID() { return m_iID; }

  /**
    * Notifies the registered FormSheetListeners that a new FormSheet has been set.
    *
    * @override Never
    */
  private final void fireFormSheetSet() {
    FormSheetEvent e = null;

    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==FormSheetListener.class) {
        if (e == null)
          e = new FormSheetEvent (this, m_fsFormSheet, true);

        ((FormSheetListener) listeners[i+1]).formSheetSet (e);
      }
    }
  }

  /**
    * Notifies the registered FormSheetListeners that the FormSheet has been removed.
    *
    * @override Never
    *
    * @param fs the FormSheet that has been removed
    * @param fExplicit true, if the FormSheet was explicitly removed (e.g. by {@link #closeFormSheet() closeFormSheet}),<br>
    *           false, if the FormSheet was not explicitly removed (e.g. by {@link #setFormSheet setFormSheet}
    */
  private final void fireFormSheetRemoved (FormSheet fs, boolean fExplicit) {
    FormSheetEvent e = null;

    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==FormSheetListener.class) {
        if (e == null)
          e = new FormSheetEvent (this, fs, fExplicit);

        ((FormSheetListener) listeners[i+1]).formSheetRemoved (e);
      }
    }
  }

  /**
    * Get the current MenuSheet.
    *
    * @override Never
    *
    * @return a MenuSheet or <code>null</code> if no MenuSheet has been set.
    */
  public MenuSheet getMenuSheet() {
    return m_msMenuSheet;
  }

  /**
    * Get the current FormSheet.
    *
    * @override Never
    *
    * @return a FormSheet or <code>null</code> if no FormSheet has been set.
    */
  public FormSheet getFormSheet() {
    return m_fsFormSheet;
  }

  /**
    * Close the FormSheet of this handle.
    *
    * @override Never
    *
    * @param fExplicit true, if the FormSheet has to be removed explicitly (e.g. by {@link #closeFormSheet() closeFormSheet}),<br>
    *           false, if the FormSheet has to be removed non-explicitly (e.g. by {@link #setFormSheet setFormSheet}
    */
  private void closeFormSheet (boolean fExplicit) {
    if (m_fsFormSheet != null) {
      FormSheet fs = m_fsFormSheet;

      m_fsFormSheet.detachDisplay();
      m_fsFormSheet = null;

      onFormSheetCaptionChanged (null, null);

      getContentPane().removeAll();
      getContentPane().validate();
      getContentPane().repaint();

      m_jcmpComponent = null;
      m_jpButtonPanel = null;

      synchronized (getWaiter()) {
        getWaiter().notifyAll();
      }

      fireFormSheetRemoved (fs, fExplicit);
    }
  }

  /**
    * Get the current FormSheet's component.
    *
    * @override Never
    *
    * @return the JComponent which is currently used
    */
  final JComponent getComponent() { return m_jcmpComponent; }

  /**
    * Get the ContentPane of the used display.
    * The pane consist of the component and the buttonbar of the current FormSheet.
    *
    * @override Never
    *
    * @return the JComponent acting as ContentPane of this handles display
    */
  final JComponent getContentPane() { return m_jcmpContentPane; }

  /**
    * @return the button bar of this display.
    *
    * @override Never
    */
  final JPanel getButtonPanel() {
    if (m_jpButtonPanel == null) {
      m_jpButtonPanel = new JPanel();

      getContentPane().add (m_jpButtonPanel, "South");
    }

    return m_jpButtonPanel;
  }

  /**
    * Get the display frame of this handle. If none exists, create it.
    *
    * @override Never
    *
    * @return the JInternalFrame used as display
    */
  final JInternalFrame getFrame() {
    if (m_jifFrame == null) {
      // create new internal frame, resizeable, non-closeable, maximizeable, iconifiable
      m_jifFrame = new JInternalFrame ("", true, false, true, true);

      m_jifFrame.setDefaultCloseOperation (JInternalFrame.DO_NOTHING_ON_CLOSE);

      m_jifFrame.addInternalFrameListener (new InternalFrameAdapter() {
        public void internalFrameClosing (InternalFrameEvent evt) {
          closeDisplay();
        }

        public void internalFrameActivated (InternalFrameEvent evt) {
          fireDisplayFocusGained();
        }
      });

      m_jcmpContentPane = (JPanel) m_jifFrame.getContentPane();

      // set initial size, these values are arbitrary and will change with the next FormSheet being set.
      m_jifFrame.setBounds (0, 0, 400, 200);
    }

    return m_jifFrame;
  }

  /**
    * Prepare the internal frame of this handle to be displayed.
    *
    * <p>For this, set the menubar peer of the current MenuSheet and the ContentPane which were removed to be used
    * in tabbed view mode.</p>
    *
    * @override Never
    */
  final void transformToPane() {
    if (m_msMenuSheet != null) {
      getFrame().setJMenuBar (m_msMenuSheet.getMenuBar());
      m_msMenuSheet.setVisible (true);
    }

    m_jcmpContentPane = new JPanel (new java.awt.BorderLayout());
    if (m_jcmpComponent != null) {
      m_jcmpContentPane.add (m_jcmpComponent, "Center");
    }
    if (m_jpButtonPanel != null) {
      m_jcmpContentPane.add (m_jpButtonPanel, "South");
    }

    getFrame().setContentPane (m_jcmpContentPane);

    if (m_fIsNew) getFrame().pack();
    else getFrame().validate();

    m_fIsNew = false;
  }

  /**
    * Prepare the internal frame of this handle so that the display can be
    * rendered as a tabbed pane.
    *
    * @override Never
    */
  final void transformToTab() {
    getFrame().setJMenuBar (null);
    if (m_msMenuSheet != null) {
      m_msMenuSheet.setVisible (false);
    }

    getFrame().setContentPane (new JPanel());
  }

  /**
    * Set a new caption for the handles display.
    *
    * <p>Normally this should be the name of the SalesPoint this display is attached to.
    * The name of the current FormSheet will be appended automatically.</p>
    *
    * @override Never
    *
    * @param sNewCaption the caption to be set
    */
  public void setDisplayCaption (String sNewCaption) {
    m_sDisplayCaption = sNewCaption;

    onFormSheetCaptionChanged (m_fsFormSheet, (m_fsFormSheet != null)?m_fsFormSheet.getCaption():null);
  }

  /**
    * @return a String representing the current caption
    *
    * @override Never
    */
  public String getDisplayCaption() {
    return (m_sDisplayCaption != null)?m_sDisplayCaption:"";
  }

  /**
    * Close the display of the handle.
    *
    * <p>From now on the handle is useless for displaying purposes.</p>
    *
    * @override Never
    */
  void closeDisplay() {
    if (m_jifFrame == null) {
      return;
    }

    closeFormSheet (true);

    fireDisplayClosing();

    JInternalFrame jif = m_jifFrame;
    m_jifFrame = null;
    jif.setVisible (false);
    
    /* 
      For some reason the code below tends to throw NullPointerExceptions at times.
      Because no problems seem to arise if we just ignore them, we do so.
    
      Added 04/27/2000.
    */
    try {
      jif.dispose();
    }
    catch (NullPointerException npe) {
      // Ignore it.
    }
  }
}