package nxm.sys.libg;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import nxm.sys.inc.InternalUseOnly;

/** A top-level window for displaying graphical panels and widgets. <br>
    <br>
    This class uses AWT while its sister class ({@link MJFrame}) uses swing.

    NOTE: MFrame does NOT currently support Java Serialization, even though it extends classes that support it.

    @see MWindow
    @see MJFrame

    @author  Jeff Schoen
 */
public class MFrame extends Frame implements MidasFrame {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2012020900311L; // for Serializable interface

  /** Message given when user tries to serialize an MFrame object */
  @InternalUseOnly
  public static final String UNSERIALIZABLE_MFRAME_OBJECT_MESSAGE =
      "nxm.sys.libg.MFrame does NOT currently support Java Serialization";

  /** MWindow associated with this frame */ transient MWindow MW;
  // private String registryName;
  /** insets (borders) of this frame */     private static Insets insets;
  /** position */                           private transient MBox pos;

  /** Creates a new frame.  <br>
      This is identical to <code>MFrame(name,pos,false,(String)null)</code>.
      @param name    The name/title of the frame.
      @param pos     The position of the frame.
      @see MFrame#MFrame(String,MBox,String)
   */
  public MFrame (String name, MBox pos) {
    this(name,pos,(String)null);
  }

  /** Creates a new frame.  <br>
      This is identical to <code>MFrame(name,pos,false,MFrame.getDisplay(display))</code>.
      @param name    The name/title of the frame.
      @param pos     The position of the frame.
      @param display The display to show the frame on (or <code>null</code> for the current display).
      @see MJFrame#getDisplay(String)
      @see MFrame#MFrame(String,MBox,GraphicsConfiguration)
   */
  public MFrame (String name, MBox pos, String display) {
    this(name,pos,MJFrame.getDisplay(display));
  }

  /** Creates a new frame.  This is NOT visible by default.
      @param name    The name/title of the frame.
      @param pos     The position of the frame.
      @param display The display to show the frame on (or <code>null</code> for the current display).
   */
  public MFrame (String name, MBox pos, GraphicsConfiguration display) {
    super(name,display);
    this.pos = pos;
    //registryName = name;
    setBackground(Color.black);
    setForeground(Color.white);
    /* Specify the events of interest */
    addWindowListener(this);
    addComponentListener(this);
    setBounds(pos);
  }

  /** Gets the {@link GraphicsConfiguration} for the given display.
      <br>
      At this time this only supports simple screen device selection on the same system.
      In the future this may be extended to permit more advanced display device selection.
      <br>
      For example within X (running on display <code>':0'</code>) this works as:
      <pre>
        getDisplay(null)     --&gt; null  (the default display)
        getDisplay("0")      --&gt; <i>X-Display ':0.0'</i>
        getDisplay("1")      --&gt; <i>X-Display ':0.1'</i> (if available)
        getDisplay(???)      --&gt; null (if ??? does not match a valid display)
                                    plus a warning printed to {@link System#out}
      </pre>
      @param display The number of the display to get (or <code>null</code> for the default).

      @return The {@link GraphicsConfiguration} for the specified display or
              <code>null</code> on error.
      @deprecated Since 3.1.0, this method was moved to {@link MJFrame#getDisplay(String)}
   */
  @Deprecated public static final GraphicsConfiguration getDisplay (String display) {
    return MJFrame.getDisplay(display);
  }

  /** Moves and resizes this component. The new location of the top-left
      corner is specified by <code>pos.x</code> and <code>pos.y</code>, and the
      new size is specified by <code>pos.w</code> and <code>pos.h</code>.
     @param pos contains the coordinates and size of the MFrame
   */
  public void setBounds (MBox pos) {
    if (insets==null) {
      super.setBounds (pos.x,pos.y,pos.w,pos.h);
    } else {
      super.setBounds (pos.x-insets.left, pos.y-insets.top,
                       pos.w+insets.left+insets.right, pos.h+insets.top+insets.bottom);
    }
  }

  /** Resize the top-level panel in this MFrame according to the current
     values for insets, width, and height.
   */
  public void resizePanel() {
    if (MW==null || MW.panel==null) return;
    else if (insets==null) MW.panel.setBounds(0,0,pos.w,pos.h);
    else MW.panel.setBounds(insets.left,insets.top,pos.w,pos.h);
  }

  /** Returns the dimensions of this MFrame, minus insets.
     @return the dimensions of this MFrame
   */
  public Dimension getSize () {
    Dimension d = super.getSize();
    if (insets==null) return d;
    d.width -= (insets.left+insets.right);
    d.height -= (insets.top+insets.bottom);
    return d;
  }

  public void display (int mode) {
    // Note that this should match MJFrame.display(int)
    if (mode>=MWindow.BACK)  { setVisible(true);               }
    if (mode==MWindow.EXIT)  { setVisible(false);              }
    if (mode==MWindow.FRONT) { setExtendedState(NORMAL); setState(NORMAL); toFront(); }
    if (mode==MWindow.BACK)  {                      toBack();  }
    if (mode==MWindow.ICON)  { setState(ICONIFIED);            }
    if (mode==MWindow.MAXIM) { setExtendedState(MAXIMIZED_BOTH); setState(NORMAL); toFront(); }
  }

  private void process (String name, int info, Object data) {
    if (MW!=null) MW.process(name,info,data);
  }

  /** Sets the MWindow associated with this MFrame.
     @param mw the MWindow
   */
  public void setMWindow (MWindow mw) {
    MW = mw;
  }

  /** Gets the MWindow associated with this MFrame.
     @return The MWindow
   */
  public MWindow getMWindow () {
    return MW;
  }

  public void componentMoved (ComponentEvent e) {
    if (MW==null || insets==null) return;
    Point p = getLocation();
    MW.pos.x = p.x+insets.left;
    MW.pos.y = p.y+insets.top;
  }

  public void componentResized (ComponentEvent e) {
    if (MW==null) return;
    Dimension d = getSize();
    MW.panel.setSize(d);
  }

  public void componentShown (ComponentEvent e) {}

  public void componentHidden (ComponentEvent e) {}

  public void windowOpened (WindowEvent e) {
    insets = getInsets();
    setBounds(pos);
    process("WINDOW",1,"OPENED");
  }

  public void windowActivated (WindowEvent e) { }

  public void windowDeactivated (WindowEvent e) { }

  public void windowIconified (WindowEvent e) {
    process("WINDOW",-1,"ICONIFIED");
  }

  public void windowDeiconified (WindowEvent e) {
    process("WINDOW",1,"DEICONIFIED");
  }

  public void windowClosing (WindowEvent e) {
    process("WINDOW",0,"CLOSING");
  }

  public void windowClosed (WindowEvent e) {
    final MWindow mw = MW; // DR872 save reference to prevent NPE in multi-threaded environment
    if (mw==null || mw.panel==null) return;
    final Container panel = MW.panel; // save reference to prevent NPE in multi-threaded env
    for (int ic=panel.getComponentCount()-1; ic>=0; ic--) {  // count backward to prevent NPE
      try {
        Object sub = panel.getComponent(ic);
        if (sub instanceof MPanel) {
          MWindow subpanelMW = ((MPanel)sub).MW; // save reference to prevent NPE in threaded env
          if (subpanelMW != null) subpanelMW.process("WINDOW",-1,"CLOSED");
        }
      }
      catch (Exception ex) { }
    }
  }

  public void dispose() {
    removeAll(); // JC4: I put this here, but not sure if it's needed.

    super.dispose(); // JC4: DO NOT REMOVE THIS LINE!!! (may cause problems for other Java versions)

    if (true) { // <-- Allows patch to be easily turned on/off
      /* JC4: The AWT focus managers keep pointers to the last window/component to have focus
              until a new one comes along to take the focus. This is a major annoyance when
              trying to search for possible memory leaks in NeXtMidas since none of the memory
              used by a graphical primitive gets freed until another graphical primitive is run.

              To make life easier, try to get the KeyboardFocusManager to throw out most of
              its pointers to this frame and its subcomponents (this gets most, but not all
              of them) and then explicitly break the link to MW so that the garbage collector
              can clean it up without waiting for the rest of the pointers to this instance
              to disappear.

              See also: MPanel.removeNotify()
       */
      KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
      if(kfm.getActiveWindow() == this) {
        kfm.clearGlobalFocusOwner();
      }
      MW = null;
    }
  }

  /** Overwrite default serialization for writeObject
   *  to throw an UnsupportedOperationException since Java Serialization currently not supported
   *
   *  @param oos Object Output Stream
   *  @throws IOException Exception thrown by writeObject
   */
  private void writeObject (ObjectOutputStream oos) throws IOException {
    throw new UnsupportedOperationException(UNSERIALIZABLE_MFRAME_OBJECT_MESSAGE);
  }

  /** Overwrite default deserialization for readObject
   *  to throw an UnsupportedOperationException since Java Serialization currently not supported
   *
   *  @param ois Object Input Stream
   *  @throws ClassNotFoundException Exception thrown by readObject
   *  @throws IOException Exception thrown by readObject
   */
  private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
    throw new UnsupportedOperationException(UNSERIALIZABLE_MFRAME_OBJECT_MESSAGE);
  }
}
