/****************************************************************************
  $Workfile: NSIBrowserFrame.java $
  $Revision: 1 $
  $Modtime:: $
  $Copyright:

  Copyright (c) 1997 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.$

 ***************************************************************************/

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;
import java.io.*;

import javax.naming.*;
import javax.naming.directory.*;

/**
 * This class is responsible for displaying, processing and updating the
 * program's one or more browser window(s) and its components.
 *
 * The components of the main window include the following:
 *   title bar - displays the application's name and version
 *   menu bar - provides structure of main- and sub-menus
 *   current path area - shows the current path
 *   name list area - shows the object available at the current path
 *   initial context area - displays the initial context setting
 */

public class NSIBrowserFrame extends Frame
                             implements WindowListener,
                                        ActionListener,
                                        ItemListener
{
   public static final int width = 420;
   public static final int height = 400;

   NSIBrowserFrame parent;       // used for sub-browser frames
   Properties props;

   Context initCtx;
   CompositeName currName;    // constructed in resetInitCtx
   Context currCtx;              // current context (relative to initCtx)
   Vector currCtxVector = new Vector ();

   Label initCtxLabel;           // component for showing init context impl
   Label currentPathLabel;       // component for showing current path

   List nameList;                // component for showing name list
   Vector nameListVector;        // parallel vector of names only

   // components for checkbox menu items
   CheckboxMenuItem listClassesItem = new CheckboxMenuItem ("List Classes");
   CheckboxMenuItem listBindingsItem = new CheckboxMenuItem ("List Bindings");
   public boolean doListClasses = false;
   public boolean doListBindings = false;

   Button attrButton;
   Button lookupButton;
   Button specialButton;

   static final String jndiInitCtxPropShort = "Init Ctx:";

   /**
    * Constructs the browser's main window (frame) and its components.
    *
    * @param     title           (in) Specifies the title for the main window
    */
   NSIBrowserFrame (
      String title,
      NSIBrowserFrame parent,
      Properties props)
   {
      super (title);
      this.parent = parent;
      this.props = props;

      if (null != parent)
      {  // We're going to use our parent's current context to start
         currCtx = parent.currCtx;
         initCtx = currCtx;
         currName = new CompositeName ();
      }

      // Construct the main menu bar
      MenuBar mainMenu = new MenuBar ();

      // Add this frame into the listener list for the CheckBoxMenuItems

      listClassesItem.addItemListener(this);
      listBindingsItem.addItemListener(this);

      // Construct and add the file menu
      Menu file = new Menu ("File");
      file.add ("Exit");
      file.addActionListener(this);
      mainMenu.add (file);

      // Construct and add the search menu
      Menu find = new Menu ("Find");
      find.add ("Lookup...");
      find.addActionListener(this);
//      find.add ("Search");
      mainMenu.add (find);

      // Construct and add the options menu
      Menu options = new Menu ("Options");
      options.add (listClassesItem);
      options.add (listBindingsItem);
      options.add ("-");
      options.add ("Initial Context...");
      if (null == parent)     // only for the root frame
         options.add ("Save Settings");
      options.addActionListener(this);
      mainMenu.add (options);

      // Construct and add the help menu
      Menu help = new Menu ("Help");
      help.add ("About...");
      help.addActionListener(this);
      mainMenu.add (help);

      // Set the window's menu bar to the one we just constructed
      setMenuBar (mainMenu);

      attrButton = new Button ("Attributes");
      attrButton.addActionListener(this);
      lookupButton = new Button ("Lookup");
      lookupButton.addActionListener(this);
      specialButton = new Button ("Special");
      specialButton.addActionListener(this);

      // Construct and add the name list display area
      Panel listPanel = new Panel ();
      listPanel.setLayout (new BorderLayout ());
      nameList = new List ();
      nameList.addActionListener(this);
      nameListVector = new Vector ();
      nameList.setMultipleMode (false);
      listPanel.add ("Center", nameList);
      Panel buttonPanel = new Panel ();
      buttonPanel.setLayout (new GridLayout (1, 0));
      buttonPanel.add (attrButton);
      buttonPanel.add (lookupButton);
      buttonPanel.add (specialButton);
      listPanel.add ("South", buttonPanel);
      add ("Center", listPanel);

      // Add the current path line
      currentPathLabel = new Label ();
      add ("North", currentPathLabel);

      // Add the status line
      initCtxLabel = new Label ();
      add ("South", initCtxLabel);

      // Make sure we get notified of window events
      addWindowListener(this);

      // Get rid of wasted space before the frame gets displayed
      pack ();

   } // NSIBrowserFrame

   /**
    * This is the action handler for the main window (frame).
    *
    * @param     evt             (in) The event
    */
   public void actionPerformed (
      ActionEvent evt)
   {
      Frame f;
      Object what = evt.getSource ();

      // Handle any menu item selections
      if ("Exit".equals (evt.getActionCommand ()))
      {
         // Close the window and terminate the application
         dispose ();
         if (null == parent)     // only the main window can exit
            System.exit (0);
      }
      else if ("Lookup...".equals (evt.getActionCommand ()))
      {
         f = new NSILookupFrame (this, initCtx, currCtx);
         f.setSize (NSILookupFrame.width, NSILookupFrame.height);
         f.show ();
      }
      else if ("Initial Context...".equals (evt.getActionCommand ()))
      {
         f = new NSIInitialContextFrame (this);
         f.setSize (
            NSIInitialContextFrame.width,
            NSIInitialContextFrame.height);
         f.show ();
      }
      else if ("Save Settings".equals (evt.getActionCommand ()))
      {
         NSIBrowser.saveConfigFile ();
      }
      else if ("About...".equals (evt.getActionCommand ()))
      {
         new NSIMessageBox ("About", "NSIBrowser by Novell Inc.");
      }

      // Handle Buttons
      else if (what == attrButton)
      {
         showAttributes ();
      }
      else if (what == lookupButton)
      {
         lookupContext ();
      }
      else if (what == specialButton)
      {
         callSpecialHandler ();
      }

      // Handle events in the listbox
      else if (what == nameList)
      {
         int itemIndex = nameList.getSelectedIndex ();
         if (-1 != itemIndex)
         {
            SortableString sortStr;
            String atomicName;

            sortStr = (SortableString) nameListVector.elementAt (itemIndex);
            atomicName = sortStr.getKeyStr ();
            Context tempCtx;

            if (atomicName.equals (".."))
            {  // We need to back up one context on the 'stack'
               try
               {
                  currName.remove (
                        currName.size () - 1);
               }
               catch (InvalidNameException e)
               {
                  System.out.println ("Unexpected naming exception " + e);
                  e.printStackTrace ();
               }

               // POP the currCtx off the 'stack'
               int stackSize = currCtxVector.size ();
               currCtx = (Context) currCtxVector.elementAt (stackSize - 1);
               currCtxVector.removeElementAt (stackSize - 1);
            }
            else
            {  // We can walk this down one with a simple lookup
               try
               {
                  currName.add (atomicName);
                  // PUSH the currCtx onto the 'stack'
                  currCtxVector.addElement (currCtx);

                  tempCtx = (Context) currCtx.lookup (atomicName);
                  currCtx = tempCtx;   // If things went ok, keep it
               }
               catch (NameNotFoundException e)
               {  // This could be a next naming system problem
                  try
                  {  // Try again with a leading slash for federation
                     tempCtx = (Context) currCtx.lookup ("/" + atomicName);
                     currCtx = tempCtx;
                  }
                  catch (Exception e2)
                  {
                     System.out.println ("Unable to update current context");
                     System.out.println ("Unexpected exception " + e2);
                     e2.printStackTrace ();
                  }
               }
               catch (NamingException e)
               {
                  System.out.println ("Unexpected naming exception " + e);
                  e.printStackTrace ();
               }
            }
            updateList ();
         }
      }
   } // action ()


   public void itemStateChanged(
      ItemEvent evt)
   {
      Object what = evt.getItem ();

      if ("List Classes".equals (what))
      {
         doListClasses = ! doListClasses;
         doListBindings = false;
         listClassesItem.setState (doListClasses);
         listBindingsItem.setState (doListBindings);
         updateList ();
      }
      else if ("List Bindings".equals (what))
      {
         doListBindings = ! doListBindings;
         doListClasses = false;
         listClassesItem.setState (doListClasses);
         listBindingsItem.setState (doListBindings);
         updateList ();
      }
   }

   /**
    * Implement WindowListener functions.  We really only want the
    * windowClosing one.
    */

   public void windowClosed (
      WindowEvent event)
   {
   }

   public void windowDeiconified (
      WindowEvent event)
   {
   }

   public void windowIconified (
      WindowEvent event)
   {
   }

   public void windowActivated (
      WindowEvent event)
   {
   }

   public void windowDeactivated (
      WindowEvent event)
   {
   }

   public void windowOpened (
      WindowEvent event)
   {
   }

   public void windowClosing (
      WindowEvent event)
   {
      dispose ();
      if (null == parent)  // only the main window can exit
         System.exit (0);
   }

   /**
    * Set the properties for the browser frame.
    *
    * @param     props             (in) Properties to be set.
    */
   public void setProperties (
      Properties props)
   {
      this.props = props;
   }

   /**
    * Updates the name list component using the initial context and the
    * current name using the initCtx and currName variables.
    */
   public synchronized void updateList ()
   {
      int sortStart = 0;
      int lastElement;
      SortableString sortStr;

      if (null == currCtx)
         throw new NullPointerException ("currCtx");

      this.setCursor (Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));    // We're probably going to be a while

      // Clear out any remaining entries in our internal list
      //  leave the visual one until later
      nameListVector.removeAllElements ();

      // Update the current path area
      if (0 != currName.size ())
         currentPathLabel.setText (currName.toString ());
      else
         currentPathLabel.setText ("[root]");

      // Add a '..' entry for everything but the root context
      if (0 != currName.size ())
      {
         sortStart = 1; // Make sure we skip sorting the .. out of place
         nameListVector.addElement (new SortableString (".."));
      }

      try
      {
         if (true == doListBindings)
         {
            // Enumerate the subordinates and add them into the name list
            NamingEnumeration bindingEnum;
            Binding binding;

            // Do the default/current name system first
            bindingEnum = currCtx.listBindings ("");
            while (true == bindingEnum.hasMoreElements ())
            {
               binding = (Binding) bindingEnum.next ();
               sortStr = new SortableString (
                                 binding.getName (),
                                 binding.getClassName ());
               nameListVector.addElement (sortStr);
            }
            // List the next name system (if there is one) next
            try
            {  // Not everyone has a next naming system
               bindingEnum = currCtx.listBindings ("/");
               while (true == bindingEnum.hasMoreElements ())
               {
                  binding = (Binding) bindingEnum.next ();
                  sortStr = new SortableString (
                                    binding.getName (),
                                    binding.getClassName ());
                  nameListVector.addElement (sortStr);
               }
            }
            catch (NameNotFoundException e)
            {  // We expect this to happen fairly often so just skip it
System.out.println ("Next naming system not supported (Bindings)");
            }
         } // if (true == doListBindings)
         else
         {
            // Enumerate the subordinates and add them into the name list
            NamingEnumeration ncEnum;
            NameClassPair ncPair;

            // Do the default/current name system first
            ncEnum = currCtx.list ("");
            while (true == ncEnum.hasMoreElements ())
            {
               ncPair = (NameClassPair) ncEnum.next ();
               if (doListClasses)
                  sortStr = new SortableString (
                                    ncPair.getName (),
                                    ncPair.getClassName ());
               else
                  sortStr = new SortableString (ncPair.getName ());
               nameListVector.addElement (sortStr);
            }
            // List the next name system (if there is one) next
            try
            {  // Not everyone has a next naming system
               ncEnum = currCtx.list ("/");
               if (null == ncEnum)
                  throw new NameNotFoundException ("/");
               while (true == ncEnum.hasMoreElements ())
               {
                  ncPair = (NameClassPair) ncEnum.next ();
                  if (doListClasses)
                     sortStr = new SortableString (
                                       ncPair.getName (),
                                       ncPair.getClassName ());
                  else
                     sortStr = new SortableString (ncPair.getName ());
                  nameListVector.addElement (sortStr);
               }
            }
            catch (NameNotFoundException e)
            {  // We expect this to happen fairly often so just skip it
System.out.println ("Next naming system not supported (NameClassPairs)");
            }
         }
      } // try (the big one)
      catch (NamingException e)
      {
         System.out.println ("Unable to update name list");
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }

      // Now we can sort the internal list
      lastElement = nameListVector.size() - 1;
      QuickSort.sort(nameListVector, sortStart, lastElement);

      // Now begin updating the visual list
	  nameList.setVisible(false);
      nameList.removeAll ();
	  nameList.setVisible(true);
      for (int i = 0; i <= lastElement; i++)
      {
         sortStr = (SortableString) nameListVector.elementAt (i);
         nameList.addItem (sortStr.toString ());   // Display key/extra
      }
      this.setCursor (Cursor.getDefaultCursor());    // Finally, restore the cursor
   } // updateList ()

   /**
    * Updates the initial context variable initCtx using the new initial
    * context as specified in the system properties.
    */
   public void resetInitCtx ()
   {
      String initPropVal;

      initCtx = null;         // Throw away the old one
      currCtx = null;
      currName = null;

      currName = new CompositeName ();

      // Now see if we can allocate an initial context
      initPropVal = props.getProperty (NSIBrowser.jndiInitCtxProp);
      if ((null != initPropVal) && (false == initPropVal.equals ("")))
      {
         try
	  {
            // We could have an initial context, but we don't so go get one
            initCtx = new InitialContext ( props );
	  }
	  catch (NamingException e)
	  {
	     // Ignore for now
	  }
      }
      currCtx = initCtx;

      // Update the initial context area
      if ((null == initPropVal) || (true == initPropVal.equals ("")))
         initCtxLabel.setText (jndiInitCtxPropShort + "[not set]");
      else
         initCtxLabel.setText (jndiInitCtxPropShort + initPropVal);
      updateList ();          // Reflect changes to the list
   } // resetInitCtx ()

   /**
    * Displays the attributes (if any) for the currently selected item.
    */
   public void showAttributes ()
   {
      String atomicName;
      int itemIndex = nameList.getSelectedIndex ();

      if (-1 == itemIndex)
      {
         atomicName = "";
      }
      else
      {
         SortableString sortStr;
         sortStr = (SortableString) nameListVector.elementAt (itemIndex);
         atomicName = sortStr.getKeyStr ();
      }

      if (atomicName.equals (".."))
      {
         new NSIMessageBox ("Error", "No attributes for '..'");
         return;
      }

      try
      {
         Object obj = currCtx.lookup (atomicName);
         if (obj instanceof DirContext)
         {
            Frame f;
            f = new NSIAttributeFrame ((DirContext) obj);
            f.setSize (NSIAttributeFrame.width, NSIAttributeFrame.height);
            f.show ();
         }
         else
            new NSIMessageBox ("Error", atomicName + " is not a DirContext");
      }
      catch (NamingException e)
      {
         System.out.println ("Unable to lookup " + atomicName);
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }

   } // showAttributes ()

   /**
    * Looks up a context and begins a new browser frame for that context.
    */
   public void lookupContext ()
   {
      String atomicName;
      int itemIndex = nameList.getSelectedIndex ();

      if (-1 == itemIndex)
      {
         atomicName = "";
      }
      else
      {
         SortableString sortStr;
         sortStr = (SortableString) nameListVector.elementAt (itemIndex);
         atomicName = sortStr.getKeyStr ();
      }

      if (atomicName.equals (".."))
      {
         new NSIMessageBox ("Error", "Cannot perform lookup on '..'");
         return;
      }

      try
      {
         Object obj = currCtx.lookup (atomicName);
         if (obj instanceof Context)
            startNewContextFrame (
                  currName.toString () + atomicName,
                  (Context) obj);
         else
            new NSIMessageBox ("Error", atomicName + " is not a DirContext");
      }
      catch (NamingException e)
      {
         System.out.println ("Unable to lookup " + atomicName);
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }

   } // lookupContext ()

   /**
    * Starts a new browser frame for a given context.
    *
    * @param   name              (in) String name for title.
    * @param   ctx               (in) Starting context for new frame.
    */
   public void startNewContextFrame (
         String name,
         Context ctx)
   {
      NSIBrowserFrame f;

      // Temporarily switch the current context so the child starts OK
      Context tempCtx = currCtx;
      currCtx = ctx;

      f = new NSIBrowserFrame (
                  name,
                  this,
                  props);
      f.setSize (width, height);
      f.show ();
      f.updateList ();
      currCtx = tempCtx;      // Restore our current context
   }

   /**
    * Handles 'special' button requests.
    *
    * <p>A 'special' request indicates that the user wants to interact
    * with the 'real' object. Since this can happen in so many different
    * ways, we use a list of property entries to find a special handler
    * for the object selected, based on its class name. This allows
    * anyone who's interested to extend this browser to do just about
    * anything.</p>
    */
   public void callSpecialHandler ()
   {
      String atomicName;
      int itemIndex = nameList.getSelectedIndex ();

      if (-1 == itemIndex)
      {
         atomicName = "";
      }
      else
      {
         SortableString sortStr;
         sortStr = (SortableString) nameListVector.elementAt (itemIndex);
         atomicName = sortStr.getKeyStr ();
      }

      if (atomicName.equals (".."))
      {
         new NSIMessageBox ("Error", "No attributes for '..'");
         return;
      }

      try
      {
         Object obj = currCtx.lookup (atomicName);

         // Loads object handler specified in properties
         // Property name: nsi.browser.handler.<obj_class_name>
         // Property value: <class_name> that implements NSISpecialHandler
Properties props = new Properties ();     // override current props for debug
String fileProp = "jnos.browser.handler.com.novell.service.file.nw.naming.FileDirContext";
props.put (fileProp, "FileSpecialHandler");

         String handlerPropName = "jnos.browser.handler." + obj.getClass ().getName ();
         String handlerClassName = props.getProperty (handlerPropName);

         if (null == handlerClassName || handlerClassName.equals (""))
            new NSIMessageBox ("Error", "No handler registered");
         else try
         {
            Object handlerObj = Class.forName (handlerClassName).newInstance ();

            if (handlerObj instanceof NSISpecialHandler)
            {
               NSISpecialHandler handler = (NSISpecialHandler) handlerObj;

               CompositeName tempName = new CompositeName ();
               tempName.addAll (currName);
               tempName.add (atomicName);

               this.setCursor (Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));    // this may take a while
               if (handler.initialize (tempName, obj))
                  handler.handleObject ();
               else
                  new NSIMessageBox ("Error", "Unable to initialize handler");
               this.setCursor (Cursor.getDefaultCursor());
            }
            else
            {
               new NSIMessageBox ("Error", "Special object handler not " +
                     "registered for this object type (" +
                     obj.getClass ().getName () + ")");
            }
         }
         catch (Exception e)
         {
            System.out.println ("Unable to instantiate handler");
            System.out.println ("Unexpected exception " + e);
            e.printStackTrace ();
         }
      }
      catch (NamingException e)
      {
         System.out.println ("Unable to lookup " + atomicName);
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }
   } // callSpecialHandler ()

} // class NSIBrowserFrame

class ImageCanvas extends Canvas
{
    int scale = 10;
    Image image;
    Image scaledImage;

    ImageCanvas(Image image)
    {
        this.image = image;
        prepareImage(image, this);
    }

    public void paint(Graphics g)
    {
        update(g);
    }

    public void update(Graphics g)
    {
        int w = image.getWidth(this);
        int h = image.getHeight(this);

        if (w >= 0 || h >= 0)
        {
            if (g.drawImage(image, 0, 0, this) && scaledImage == null)
            {
                // The image has been completely reloaded.

                // Display any comments.
                if (image.getProperty("comment", this) !=
                    Image.UndefinedProperty)
                {
                    System.out.println(
                       image.getProperty("comment", this));
                }

                // Create a pixel grabber and retrieve the pixels.
                int[] pixels = new int[w * h];
                try
                {
                    PixelGrabber pg = new PixelGrabber(
                       image, 0, 0, w, h, pixels, 0, w);
                    pg.grabPixels();

                    // Check for errors
                    if ((pg.status() & ImageObserver.ABORT) != 0)
                    {
                        System.err.println("Error while fetching image");
                        System.exit(1);
                    }
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }

                // Now replicate the pixels
                int d = 0, s = 0;
                int[] newpixels = new int[w * h * scale * scale];
                for (int i = 0; i < h; i++)
                {
                    for (int j = 0; j < scale; j++)
                    {
                        for (int k = 0; k < w; k++)
                        {
                            for (int l = 0; l < scale; l++)
                            {
                                newpixels[d++] = pixels[i*w + k];
                            }
                        }
                    }
                }
                scaledImage = getToolkit().createImage(
                   new MemoryImageSource(w*scale, h*scale,
                   ColorModel.getRGBdefault(), newpixels, 0, w*scale));
            }
        }
        if (scaledImage != null)
        {
            g.drawImage(scaledImage, w, 0, this);
        }
    }
}

