/*
 * BICatalog.java
 *
 * Copyright © 1994-2004, Oracle. All rights reserved. Oracle grants
 * you ("Licensee") a non-exclusive, royalty-free, worldwide license to use,
 * modify and redistribute this software in source and binary code form, provided
 * that (i) this copyright notice and license appear on all copies of the
 * software; (ii) Licensee does not utilize the software in a manner disparaging
 * to Oracle; and (iii) Licensee does not utilize the software in a manner that
 * violates the terms of any Licensee agreement with Oracle.
 */
package samples.javaclient;

import java.awt.BorderLayout;
import java.awt.Component;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

import java.text.MessageFormat;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.naming.NameNotFoundException;

import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;

import oracle.dss.bicontext.Acl;
import oracle.dss.bicontext.BINameNotFoundException;
import oracle.dss.bicontext.ObjectFactory;
import oracle.dss.bicontext.Privilege;
import oracle.dss.bicontext.User;

import oracle.dss.persistence.security.common.AclEntry;
import oracle.dss.persistence.security.common.AclEntryImpl;
import oracle.dss.persistence.security.common.UserImpl;

import oracle.dss.dataSource.common.Query;

import oracle.dss.persistence.PSRConstants;
import oracle.dss.persistence.persistencemanager.client.InitialPersistenceManager;
import oracle.dss.persistence.gui.Explorer;
import oracle.dss.persistence.gui.PersistenceObjectChooser;
import oracle.dss.persistence.persistencemanager.common.PersistenceManager;
import oracle.dss.security.BIUser;
import oracle.dss.util.BIException;
import oracle.dss.util.persistence.Persistable;

/**
 * The main class for the BICatalog sample.
 * <P>
 * The BICatalog provides the following functionality:
 * <ol>
 * <li>Installs a sample user directory, see Tools/Create Folder Structure</li>
 * <li>Show the BIBeans Explorer, see Tools/BI Explorer</li>
 * <li>Export all the contents of the BI Catalog to an XML file on the
 * filesystem</li>
 * <li>Open a view. This opens objects from the Catalog, and if they
 * implement Component, adds them to the display panel</li>
 * <li>Open any component and display the XML of the top level component</li>
 * <li>Application settings. These are loaded and saved from the users folder
 * in the BI Beans catalog. The user settings include the 6 Most Recently Used
 * (MRU) opened objects and the path from which the last object has been
 * opened</li>
 * </ol>
 */
public class BICatalog extends JFrame {
  private JMenu m_sampleMenu; // A reference to the sample's menu, so 
                              // we can update the MRU list
  private BICatalogAppSettings m_settings; // The current application settings
  private BIFrame m_frame;
  private JPanel m_panel;
  /**
   * Constructor
   *
   * @param title the window title of the application
   */
  public BICatalog(BIFrame parentFrame, String title) {
    super(title);
    m_frame = parentFrame;
    init();
  }

  /**
   * Create a sample user structure with the appropriate access controls setup
   * <p>
   * This will create a folder structure with sample users and assign privileges
   * to these users and the folder structure.
   * The structure looks as follows:
   *  + root
   *    + bi_samples
   *      + users
   *        + anna
   *        + chadwick
   *        + henry
   *        + yekesa
   *      + public
   */
  protected void createFolderStructures() {
    // Here we define the folder structure we want to create.
    // Every String array represents one folder and the access controls
    // associated with it.
    // One folder definition String array has as the first element the folder
    // to create and then every consecutive 2 elements a userlist
    // (users separated by a semicolon and the privilege for the users)
    String[][] _folders = new String[][] {
      {"",
            "yekesa;chadwick;anna;henry", Privilege.LIST.toString()},
      {"bi_samples",
            "{0}", Privilege.FULL_CONTROL.toString(),
            "yekesa;chadwick;anna;henry", Privilege.LIST.toString()},
      {"bi_samples/users",
            "{0}", Privilege.FULL_CONTROL.toString(),
            "yekesa;chadwick;anna;henry", Privilege.LIST.toString()},
      {"bi_samples/users/yekesa",
            "{0};yekesa", Privilege.FULL_CONTROL.toString(),
            "chadwick;anna;henry", Privilege.READ.toString()},
      {"bi_samples/users/chadwick",
            "{0};chadwick", Privilege.FULL_CONTROL.toString(),
            "yekesa;anna;henry", Privilege.READ.toString()},
      {"bi_samples/users/anna",
            "{0};anna", Privilege.FULL_CONTROL.toString(),
            "yekesa;chadwick;henry", Privilege.READ.toString()},
      {"bi_samples/users/henry",
            "{0};henry", Privilege.FULL_CONTROL.toString(),
            "yekesa;chadwick;anna", Privilege.READ.toString()},
      {"bi_samples/users/{0}",
            "{0}", Privilege.FULL_CONTROL.toString(),
            "yekesa;chadwick;henry;anna", Privilege.READ.toString()},
      {"bi_samples/public",
            "{0}", Privilege.FULL_CONTROL.toString(),
            "yekesa;chadwick;anna;henry", Privilege.WRITE.toString()}
    };

    // Check if the application is connected
    if (validConnection()) {
      // Get the root
      PersistenceManager _root = m_frame.getPersistenceManager();

      // Get the current user
      String _theUser = m_frame.getBISession().getBIUser().getName();

      // If anything unexpected happens we catch it here
      try {
        // Loop over all the folder definitions and create them if they don't
        // exist.
        for (int i = 0; i < _folders.length; i++) {
          // get the foldername
          String _folderName =
            new MessageFormat(_folders[i][0]).format(new Object[]{_theUser});
          PersistenceManager _folder = null;
          try {
            // try to load it in case it already exists
            _folder = (PersistenceManager)_root.lookup(_folderName);
          }
          catch (NameNotFoundException ne) {
            // Does not exist, create it
            _folder = (PersistenceManager)_root.createSubcontext(_folderName);
          }

          if (!"Local User".equals(_theUser)) {
            // Apply the access controls
            for (int j = 1; j < _folders[i].length; j += 2) {
              // Parse the list of users based on the semicolon separator
              String _userList = new MessageFormat(_folders[i][j])
                                   .format(new Object[]{_theUser});
              StringTokenizer _tokens = new StringTokenizer(_userList, ";");
  
              // Get the privilege for the users
              Privilege _privilege = getPrivilege(_folders[i][j+1]);
  
              // Build a vector of ACLEntries
              Vector _aclVector = new Vector();
              while (_tokens.hasMoreTokens()) {
                // Build an acl entry based on the user and the privilege
                User _user = new UserImpl(_tokens.nextToken());
                AclEntry _aclEntry = new AclEntryImpl(_user, _privilege);
  
                // Add to the vector of acls
                _aclVector.addElement(_aclEntry);
              }
              // get the acl object, that manages the access controls for a folder
              Acl _acl = _folder.getAcl();
  
              // Apply the access controls to the acl object
              _acl.addEntries(_aclVector, false);
            }
          }
        }
      }
      catch (Exception e) {
        m_frame.showExceptionDialog(m_frame, e);
      }
    }
  }

 /**
  * Clean up and does the following:
  * <li>Save the user's application settings</li>
  * <p>
  * Try to save the current user's settings before going back to the main menu
  */
  protected void cleanUp() {
  
    // save the current settings for the user
    
    // try to save the user's settings, if it fails then continue.
    try {
      // To construct the path for the users settings,
      // retrieve the user name
      BIUser _user = m_frame.getBISession().getBIUser();

      // save the settings in the user directory
      String _savePath = getUserSettingsPath(_user.getName());
      m_frame.getPersistenceManager().rebind(_savePath, m_settings);
    }
    catch (Exception e) {
      m_frame.showExceptionDialog(this, e);
    }
    // Initialize the settings
    m_settings = new BICatalogAppSettings();
    
    dispose();
  }

  /**
   * Export the contents of the BI Beans Catalog to an XML file on filesystem
   */
  protected void exportCatalog()
  {
    if (validConnection()) {
      // Create a file object chooser
      JFileChooser _fileChooser = new JFileChooser();

      // Show it in save mode and check if the user clicked on OK
      if (_fileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
        try {
          // Now Export all the contents from the root
          String _path = _fileChooser.getSelectedFile().getAbsolutePath();
          m_frame.getPersistenceManager().exportSubcontext(null, _path);
        }
        catch (Exception e) {
          JOptionPane.showMessageDialog(m_frame,
            "Error exporting catalog, see the stacktrace for details.",
            m_frame.getTitle(), JOptionPane.ERROR_MESSAGE);
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * Find a menu given its name
   *
   * @param bar the menubar we to be searched
   * @param name the name/label of the menu to search for
   *
   * @return the found JMenu or null if not found.
   */
  protected JMenu findMenu(JMenuBar bar, String name) {
    int _len = bar.getMenuCount();

    // Loop of the menus in this bar
    for (int i = 0; i < _len; i++) {
      if (name.equals(bar.getMenu(i).getText())) {
        // Found it and return immediately
        return bar.getMenu(i);
      }
    }
    // Not found return null
    return null;
  }

  /**
   * Find privilege based on its String representation
   * @param privilige to find
   *
   * @return the Privilege constant
   */
  protected static Privilege getPrivilege(String privilege) {
    if (privilege.equals("List")) {
      return Privilege.LIST;
    }
    else if (privilege.equals("Read")) {
      return Privilege.READ;
    }
    else if (privilege.equals("Add Folder")) {
      return Privilege.ADD_FOLDER;
    }
    else if (privilege.equals("Write")) {
      return Privilege.WRITE;
    }
    else if (privilege.equals("Full Control")) {
      return Privilege.FULL_CONTROL;
    }
    else {
      return null;
    }
  }

 /**
  * Build a path for the users settings component in the catalog based on its
  * name
  *
  * @param user the username
  *
  * @return the fullpathname to the user settings component
  */
  protected String getUserSettingsPath(String user) {
    return "bi_samples/users/" + user + "/settings";
  }

  /**
   * Initialize the user interface. Calls the superclass initialization
   * and then initializes the applicationsettings and adds additional menu items
   * to the menu.
   */
  protected void init() {
    JMenuBar _menuBar = new JMenuBar();
    
    // Initialize the application settings
    m_settings = new BICatalogAppSettings();

    // Create an open menu item
    JMenuItem _openMenu = new JMenuItem("Open");

    // when the open item is selected, open the component
    _openMenu.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        openComponent(false);
      }
    });

    // Create an XML open menu item
    JMenuItem _openXMLMenu = new JMenuItem("Open as XML");

    // when the xml open item is selected, open the component and display
    // its XML
    _openXMLMenu.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        openComponent(true);
      }
    });

    // create the sample menu
    m_sampleMenu = new JMenu("Exploring the BI Beans Catalog");
    // add the menu items
    m_sampleMenu.add(_openMenu);
    m_sampleMenu.add(_openXMLMenu);
    _openMenu.setMnemonic('O');
    _openXMLMenu.setMnemonic('a');

    // create an Explorer menu item and add it to the tools menu
    JMenuItem _explorerMenu = new JMenuItem("Launch BI Explorer");
    m_sampleMenu.add(_explorerMenu);

    // when the Explorer item is selected, open the BI Explorer.
    _explorerMenu.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        showExplorer();
      }
    });

    // Create a Create Folder structure menu item and add it to the tools menu
    JMenuItem _folderStructureMenu = new JMenuItem("Create Folder structure");
    m_sampleMenu.add(_folderStructureMenu);

    // when the Folder Structure item is selected, create the folder structures.
    _folderStructureMenu.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        createFolderStructures();
      }
    });

    // create an export menu and add it to the tools menu
    JMenuItem _exportMenu = new JMenuItem("Export Catalog to file");
    m_sampleMenu.add(_exportMenu);

    // when the export item is selected, export the contents of the catalog.
    _exportMenu.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        exportCatalog();
      }
    });

    // Add the tool menu to the menubar
    _menuBar.add(m_sampleMenu);
    
    m_panel = new JPanel();
    m_panel.setLayout(new BorderLayout());
    
    // put all of these into the frame
    setJMenuBar(_menuBar);
    getContentPane().removeAll();
    getContentPane().add(m_panel, BorderLayout.CENTER);
  
    // add a listener to listen for the window closing event
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        cleanUp();
      }
    });
    
    // Get the initial PersistenceManager
    InitialPersistenceManager _initial =
      (InitialPersistenceManager)m_frame.getPersistenceManager();

      // To register the new ApplicationSettings object type,
      // obtain the ObjectFactory for the client
      ObjectFactory _factory = _initial.getObjectFactory(false);

      // Register the type with its type id and the classname
      _factory.setObjectInstanceClassName("ApplicationSettings",
        BICatalogAppSettings.class.getName());

      // try to load the user's settings, if it fails, then provide the default
      // settings
      try {
        // To construct the path for the users settings, obtain the user name
        BIUser _user = m_frame.getBISession().getBIUser();

        // lookup the settings in the user directory
        String _userName = _user.getName();
        Object _object = _initial.lookup(getUserSettingsPath(_userName));

        // Test whether the loaded object is a settings object
        if (_object instanceof BICatalogAppSettings) {
          m_settings = (BICatalogAppSettings)_object;
          // then update the menu
          updateMRUMenu();
        }
        else {
          // Use default settings
          m_settings = new BICatalogAppSettings();
        }
      }
      catch (BINameNotFoundException e) {
        // Couldn't find the settings, accept the defaults
        m_settings = new BICatalogAppSettings();
      }
      catch (Exception e) {
        // Couldn't find the settings, accept the defaults
        m_settings = new BICatalogAppSettings();
        m_frame.showExceptionDialog(m_frame, e);
      }
  }

  /**
   * Open a component from the BI Beans Catalog showing the ObjectChooser
   * <p>
   * @param showXML true to view the XML and false to view the component itself
   */
  protected void openComponent(boolean showXML) {
    // Let the user choose an object and open the component
    openComponent(openObjectChooser(), showXML);
  }

  /**
   * Open a component from the BI Beans Catalog showing the ObjectChooser
   *  
   * @param path of the component to load. If the loaded component cannot be
   * displayed; it will behave as if showXML were set to true. That is, the XML
   * is displayed.
   * @param showXML true to view the XML and false to view the component.
   */
  protected void openComponent(String path, boolean showXML) {
    // If something was selected and the application is connected
    if (path != null) {
      Object loadedObject = null;
      try {
        // Load the object
        Hashtable _env = new Hashtable();
        _env.put(Query.QUERY_MANAGER, m_frame.getQueryManager());
        loadedObject = m_frame.getPersistenceManager().lookup(path, null, _env);
      }
      catch (Exception e) {
        // Failed to open so remove it from our MRU list
        m_settings.removeMRU(path);
        updateMRUMenu();
        m_frame.showExceptionDialog(m_frame, e);
      }
      // If not loaded for some reason, alert the user
      if (loadedObject == null) {
        JOptionPane.showMessageDialog(m_frame, "Error loading object.",
          m_frame.getTitle(), JOptionPane.ERROR_MESSAGE);
      }
      else {
        JComponent _jComponent = null;

        // If an object had a visual representation, then show it
        if (!showXML && loadedObject instanceof JComponent) {
          _jComponent = (JComponent)loadedObject;
        }
        else if (!showXML){
          // Give warning that the object has no visual representation
          JOptionPane.showMessageDialog(m_frame,
            "Object loaded succcessfully, but has no visual representation,"
            + " so we will show the XML",
            m_frame.getTitle(), JOptionPane.WARNING_MESSAGE);
          showXML = true;
        }
        if (showXML) {
          // Retrieve the top component's XML
          Persistable _persistable = (Persistable)loadedObject;
          String _xml = null;
          try {
            _xml = _persistable.getXMLAsString();
          }
          catch (Exception e) {
            // Apparently something went wrong, we just put the error in the xml
            _xml = "Error retrieving XML";
          }
          // Create the textarea, for displaying the XML.
          JTextArea _text = new JTextArea();
          // Set the xml
          _text.setText(_xml);

          // Set linewrap on
          _text.setLineWrap(true);
          _jComponent = _text;
        }
        // Now we are ready to add the component to the panel
        // Remove any existing components
        m_panel.removeAll();

        // Add the newly loaded object
        m_panel.add(_jComponent, BorderLayout.CENTER);

        // Revalidate the parent
        m_panel.revalidate();
      }
    }
  }

  /**
   * Show the ObjectChooser in open mode, if the user clicked ok it will also
   * update our MRU menu
   * return the selected objectname or null if the user cancelled
   */
  protected String openObjectChooser() {
    // Check if we are connected
    if (validConnection()) {
      // Create new Chooser
      PersistenceObjectChooser _chooser =
        new PersistenceObjectChooser(m_frame.getPersistenceManager());
      // Set the current path from the settings
      _chooser.setCurrentDirectoryPath(m_settings.getLastPath());
      // Show it and wait until the user closes the dialog
      int _result = _chooser.showOpenDialog(this);
      // Check if the user clicked on OK
      if (_result == PersistenceObjectChooser.OK_OPTION) {
        // Add opened object to our MRU list
        m_settings.addMRU(_chooser.getSelectedObjectPathName());
        // Update the menu
        updateMRUMenu();

        // Update the last path in the settings
        m_settings.setLastPath(_chooser.getCurrentDirectoryPathName());

        return _chooser.getSelectedObjectPathName();
      }
    }
    return null;
  }

  /**
   * Show the BI Explorer to browse the BI Beans Catalog.
   */
  protected void showExplorer() {
    // Check if we are connected
    if (validConnection()) {
      Explorer _explorer = new Explorer(m_frame.getPersistenceManager());
      _explorer.setVisible(true);
    }
  }

  /**
   * Display the MRU list in the file menu. We identify the MenuItem by putting
   * mru in the tooltip text. So can identify them alter if we need to update
   * them
   */
  protected void updateMRUMenu() {
    // First remove all the old mrus, they are identified
    // by a tooltip text of mru
    m_sampleMenu.addSeparator();
    int len = m_sampleMenu.getMenuComponentCount()-1;
    for (int i = len; i >= 0; i--) {
      Component _comp = m_sampleMenu.getMenuComponent(i);
      if (_comp instanceof JMenuItem
        && "mru".equals(((JMenuItem)_comp).getToolTipText())){
        m_sampleMenu.remove(i);
      }
    }

    // Add the latest mrus
    for (Enumeration en = m_settings.MRUs(); en.hasMoreElements();) {
      String _path = (String)en.nextElement();
      JMenuItem _mru = new JMenuItem(_path);

      // mark them so we can identify them later
      _mru.setToolTipText("mru");
      m_sampleMenu.insert(_mru, 7);
      _mru.addActionListener(new MRUActionListener(this, _path));
    }
  }

  /**
   * Verify that the connection  is valid
   * <p>
   * @return true if valid else return false and display a warning
   */
  protected boolean validConnection() {
    boolean _valid = false;
    // Check if we are connected
    if (m_frame.getPersistenceManager() == null) {
      JOptionPane.showMessageDialog(this,
        "Not connected, please verify that you have a valid connection",
        this.getTitle(), JOptionPane.ERROR_MESSAGE);
    }
    else {
      _valid = true;
    }
    return _valid;
  }

  /**
   * This Actionlistener knows what MRUpath it represents when it is activated
   * and can then open the item using the Catalog instance.
   */
  private class MRUActionListener implements ActionListener {
    BICatalog m_catalog; // Handle to the containing class
    String m_path; // Path the menu represents 

    /**
     * Constructor
     * @param catalog, the catalog instance to open the component with
     * @param path, the path to the component to open
     */
    MRUActionListener(BICatalog catalog, String path) {
      m_catalog = catalog;
      m_path    = path;
    }

    /**
     * @event, the action performed event, we ignore this
     */
    public void actionPerformed(ActionEvent event) {
      // Use the catalog to open the component with its path
      m_catalog.openComponent(m_path, false);
    }
  }
}
