/*
 * BICalc.java
 *
 * Copyright © 1994-2004, Oracle. All rights reserved. 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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.util.Vector;

import javax.naming.NamingException;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;

import oracle.dss.calculation.client.calcBuilder.CalcBuilderStepStorage;
import oracle.dss.calculation.client.MDMeasureCalc;

import oracle.dss.crosstab.Crosstab;

import oracle.dss.dataSource.common.Query;
import oracle.dss.dataSource.common.QueryException;

import oracle.dss.dataView.ComponentHandle;
import oracle.dss.dataView.Dataview;
import oracle.dss.dataView.PopupManager;

import oracle.dss.datautil.QueryAccess;

import oracle.dss.metadataManager.client.MetadataManager;
import oracle.dss.metadataManager.common.MDDimension;
import oracle.dss.metadataManager.common.MDMeasure;
import oracle.dss.metadataManager.common.MDFolder;
import oracle.dss.metadataManager.common.MDObject;
import oracle.dss.metadataManager.common.MM;
import oracle.dss.metadataManager.common.MetadataManagerException;

import oracle.dss.selection.OlapQDR;
import oracle.dss.selection.Selection;
import oracle.dss.selection.SelectionException;
import oracle.dss.selection.calcStep.CalcStep;
import oracle.dss.selection.calcStep.CalcValue;
import oracle.dss.selection.calcStep.TimeCalcStep;
import oracle.dss.selection.calcStep.TimeLagLeadStep;
import oracle.dss.selection.step.InvalidStepArgException;
import oracle.dss.selection.step.MemberStep;
import oracle.dss.selection.step.Step;

import oracle.dss.util.ComponentInfo;
import oracle.dss.util.DataAccess;
import oracle.dss.util.LayerMetadataMap;
import oracle.dss.util.QDRMember;

/**
 * This class shows how to use the CalcBuilder to modify a Crosstab by adding
 * new calculations.
 */
public class BICalc extends JFrame {

  /////////////////////
  //
  // Constants
  //
  /////////////////////

  /////////////////////
  //
  // Member Variables
  //
  /////////////////////

  /**
   * The <code>Crosstab</code> used to display queries.
   */
  private Crosstab m_crosstab;
  
  /**
   * The <code>JButton</code> used to invoke the CalcBuilder to create a new
   * calculation.
   */
  private JButton m_jButtonCalc;
  
  /**
   * The <code>JMenuItem</code> used to insert a CalcBuilder.
   */
  private JMenuItem m_jMenuItemCalcBuilder;
  
  /**
   * The <code>JMenuItem</code> used to invoke the CalcBuilder to create a new
   * calculation.
   */
  private JMenuItem m_jMenuItemSample;
 
  /**
   * The <code>Query</code> used to evaluate selections.
   */
  private Query m_query;

  /**
   * The <code>BICalcQueryAdapter</code> used to listen for Query events.
   */
  private BICalcQueryAdapter m_queryListener;
  
  /**
   * The <code>BIFrame</code> used to display this <code>BICalc</code> sample.
   */
  private BIFrame m_biFrame;

  /**
   * The <code>JPanel</code> used to display this <code>BICalc</code> sample.
   */
  private JPanel m_jPanel;
  
  /////////////////////
  //
  // Constructors
  //
  /////////////////////

  /**
   * Constructor for <code>BICalc</code>.
   *
   * @param biFrame A <code>BIFrame</code> used to display <code>BICalc</code> sample.
   * @param strTitle A <code>String</code> which represents the <code>JFrame</code> title.
   */
  public BICalc (BIFrame biFrame, String title) {
    super (title);
    m_biFrame = biFrame;
    init();
  }

  /////////////////////
  //
  // Public Methods
  //
  /////////////////////
  
  /**
   * Saves the <code>CalcStep</code> based on the specified name and folder.
   *
   * @param strFolder a <code>String</code> that represents the name of the 
   *        folder where calculation is.
   * @param strName a <code>String</code> that represents the name of the 
   *        calculation to delete.
   * @param calcStep a <code>CalcStep</code> to save.
   *
   * @return <code>MDObject</code> representing the saved <code>CalcStep</code>.
   *
   * @throws <code>Exception</code>, <code>MetadataManagerException</code> or 
   *         <code>NamingException</code> if calculation cannot be saved.
   */
  public MDObject saveCalcStep (String strFolder, String strName, CalcStep calcStep) 
    throws Exception, MetadataManagerException, NamingException {
    MDObject mdObject = null;
    
    // Retrieve the MDFolder based on the name
    MDFolder mdFolder = getMDFolder (strFolder, true);

    if ((mdFolder != null) && (calcStep != null)) {
      mdObject = new MDMeasureCalc (calcStep, m_biFrame.getMetadataManager());    
      mdFolder.rebind (strName, mdObject);
    }

    return mdObject;
  }

  /**
   * Retrieves the <code>MDFolder</code> object that is associated with the
   * specified path.
   *
   * @param strPath A <code>String</code> value which represents the path
   *        to the <code>MDObject</code> to load.  If a null value is 
   *        specified, the root is assumed.
   *
   * @return The <code>MDFolder</code> object that represents the folder
   *         object, or null.
   *
   * @throws <code>Exception</code> If the folder cannot be found.
   *
   */
  public MDFolder getMDFolder (String strPath) throws Exception {
    return getMDFolder (strPath, false);
  }

  /**
   * Retrieves the <code>MDFolder</code> object that is associated with the
   * specified path.
   *
   * @param strPath A <code>String</code> value which represents the path
   *        to the <code>MDObject</code> to load.  If a null value is 
   *        specified, the root is assumed.
   * @param bCreate A <code>boolean</code> which is <code>true</code> when a
   *        folder should be created if it is not found, and <code>false</code>
   *        otherwise.
   *
   * @return The <code>MDFolder</code> object that represents the folder
   *         object, or null.
   *
   * @throws <code>Exception</code> If the folder cannot be found.
   */
  public MDFolder getMDFolder (String strPath, boolean bCreate) throws Exception {

    // The retrieved folder
    MDFolder mdFolder = null;

    // Retrieve the root folder
    MDFolder mdRoot = m_biFrame.getMetadataManager().getMDRoot();

    // Determine the proper MDFolder associated with the calculations
    // folder name
    if (strPath == null || strPath.equals ("") || strPath.equals ("/")) {
      mdFolder = mdRoot;
    }
    else {
      try {
        // Determine the proper MDFolder associated with the calculation's
        // folder name
        mdFolder = (MDFolder)mdRoot.lookup (strPath);
      }
      
      catch (Exception exception) {
        // If not found, create it.
        if (bCreate) {
          mdFolder = (MDFolder)m_biFrame.getMetadataManager().getMDRoot().createSubcontext (strPath);
        }
        else {
          // If we do not need to create a new folder, simply rethrow exception.
          throw (exception);
        }
      }
    }

    return mdFolder;
  }

  /////////////////////
  //
  // Protected Methods
  //
  /////////////////////

  /**
   * Initialize the user interface.
   */
  protected void init() {
      
    // Create Menu and add associated menu items and buttons
    JMenuBar m_menuBar = new JMenuBar();
    m_jMenuItemSample = new JMenu ("Creating calculations");
    m_jMenuItemCalcBuilder = new JMenuItem ("Insert Calculation");
    
    m_jMenuItemSample.add (m_jMenuItemCalcBuilder);
    m_menuBar.add (m_jMenuItemSample);

    m_jMenuItemSample.setMnemonic ('C');
    m_jMenuItemCalcBuilder.setMnemonic ('I');

    // Create the toolbar
    JToolBar m_toolBar = new JToolBar();
    
    ImageIcon imageIconCalc = new ImageIcon (BICalc.class.getResource("images/calc.gif"));
    m_jButtonCalc = new JButton (imageIconCalc);
    m_toolBar.add (m_jButtonCalc);

    // Add Listener for menu events 
    m_jMenuItemCalcBuilder.addActionListener (new ActionListener() {
      public void actionPerformed (ActionEvent event) {
        runCalcBuilder();
      }
    });

    // Add Listener to Calc button
    m_jButtonCalc.addActionListener (new ActionListener() {
      public void actionPerformed (ActionEvent event) {
        runCalcBuilder();
      }
    });
    
    m_jPanel = new JPanel();
    m_jPanel.setLayout (new BorderLayout());
    
    // put all of these into the frame
    setJMenuBar (m_menuBar);
    getContentPane().removeAll();
    getContentPane().add (m_toolBar, BorderLayout.NORTH);
    getContentPane().add (m_jPanel, BorderLayout.CENTER);
  
    // add a listener to listen for the window closing event
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        cleanUp();
      }
    });
    
    createCrosstab();
  }

  /**
   * This method cleans up before returning to the main menu
   */
  protected void cleanUp() {
  
    if (m_crosstab != null) {
      m_crosstab.setDataSource(null);
      m_jPanel.remove(m_crosstab);
      m_crosstab = null;
    }    
    
    dispose();
  }
  
  /**
   * This method invokes the CalcBuilder and allows the user to add
   * a Calculation to the Crosstab.
   */
  protected void runCalcBuilder() {
    try {
      // Create the CalcBuilder
      CalcBuilderStepStorage calcBuilderStepStorage =
        new CalcBuilderStepStorage (this, null, m_query, m_biFrame.getMetadataManager());

      // Invoke the CalcBuilder and retrieve any generated calculation
      MDMeasure mdMeasure = (MDMeasure)calcBuilderStepStorage.invoke();

      // If a calculation has been generated, add it to the query
      if (mdMeasure != null) {
        updateMeasures (mdMeasure);
      }
   
      // Release any CalcBuilder resources
      calcBuilderStepStorage.cleanup();
    }
    
    catch (Exception exception) {
      m_biFrame.showExceptionDialog (this, exception);
    }
  }

  /**
   * This method creates a new Crosstab, sets the DataSource on the crosstab
   * and adds the Crosstab to the frame.
   */
  protected void createCrosstab() {
    // Create a new Crosstab
    m_crosstab = new Crosstab();
    m_crosstab.setPagingControlVisible(true);
    m_crosstab.setPopupManager(new CalcPopupManager());

    // Create default query and associate it with the Crosstab
    m_query = m_biFrame.getDefaultQuery();
    m_crosstab.setDataSource (m_query);
    
    try {
      // Retrieve the MetadataManager unique IDs for each measure
      String[] strMeasures = m_query.getMeasures();
      
      // If no measures have been found, simply return
      if (strMeasures == null || strMeasures.length == 0) {
        return;
      }
      
      // Retrieve the first measure from the query
      String strMeasureID = strMeasures[0];
      
      // Retrieve the MDMeasure associated with this MetadataManager unique ID
      MDMeasure mdMeasure = (MDMeasure) m_biFrame.getMetadataManager().getMDObject(
        MM.UNIQUE_ID, strMeasureID, MM.MEASURE);

      // Retrieve the dimensions associated with the measure
      MDDimension[] mdDimensions = mdMeasure.getDimensions();
      
      // Attempt to find the MetadataManager unique ID of the Time dimension
      String strTimeDimensionID = "";
      for (int i = 0; i < mdDimensions.length; i++) {
        if (mdDimensions[i].isTimeDimension()) {
          strTimeDimensionID = mdDimensions[i].getUniqueID();
          break;
        }
      }
      
      // Set the selection on Time to be the last March, April and May 2001
      Selection selection = m_query.findSelection (strTimeDimensionID);
      Vector timeMembers = new Vector();
      
      // 1710, 1711, 1712 are the IDs of the months
      timeMembers.addElement ("1710");  
      timeMembers.addElement ("1711");
      timeMembers.addElement ("1712");

      // Create a MemberStep that only includes our chose Time Dimension members
      MemberStep memberStep = new MemberStep (strTimeDimensionID, timeMembers);
      selection.removeAllSteps();
      selection.addStep (memberStep);

      // Update the Time Dimension selection associated with the Query
      m_query.applySelection (selection);
    }
    
    catch (Exception exception) {
      m_biFrame.showExceptionDialog (m_biFrame, exception);
    }

    // Add QueryListener to the Query
    m_queryListener = new BICalcQueryAdapter(m_query);
    m_query.addQueryListener(m_queryListener);

    // Add crosstab to the Frame
    m_jPanel.add(m_crosstab, BorderLayout.CENTER);
    m_jPanel.invalidate();
    m_jPanel.validate();
    m_jPanel.repaint();
  }

  /////////////////////
  //
  // Private Methods
  //
  /////////////////////

  /**
   * This class will be used to implement right-click context menus.
   */
  private class CalcPopupManager implements PopupManager {
    private JPopupMenu m_menu;
    private MDMeasure m_measure;

    /**
     * Constructor
     */
    public CalcPopupManager() {
      m_menu = new JPopupMenu();
      m_measure = null;
    }

    /**
     * Called on a manager to inform it the data source
     * has changed.
     */
    public void dataSourceChanged() {
      // Not used in this sample, but required to implement the
      // PopupManager interface
    }

    /**
     * Return a complete <code>JPopupMenu</code>.  The implementor
     * of this method is responsible for processing its own calls
     * from this popup menu.
     *
     * @param  dataview A <code>Dataview</code> value that represents
     *         the Dataview associated with this popup.
     * @param  componentHandle A <code>ComponentHandle</code> value that
     *         represents the component handle associated with this
     *         popup.
     *
     * @return an entire <code>JPopupMenu</code> structure, or
     * <code>null</code>, in which case <code>getPopupStrings</code>
     * will be called.
     */
    public JPopupMenu getPopupMenu(Dataview dataview,
      ComponentHandle componentHandle) {
      ComponentInfo componentInfo = componentHandle.getComponentInfo();
      
      // Determine which measure has been chosen
      m_measure = getMeasure (componentInfo);

      if (m_measure == null) {
        return null;
      }

      m_menu.removeAll();
      
      JMenuItem item = new JMenuItem ("Insert " + m_measure + " (Year Ago)");
      m_menu.add (item);
      
      item.addActionListener (new ActionListener() {
        public void actionPerformed (ActionEvent menuEvent) {
          // Insert a Year Ago calculation based on the chosen measure
          BICalc.this.insertYearAgo (m_measure);
        }
      });
      
      return(m_menu);
    }

    /**
     * This method retrieves the measure that is associated with the
     * specified component information.
     *
     * @param componentInfo a <code>ComponentInfo</code> value that
     *        represents the component we wish to interrogate.
     *         
     * @return <code>MDMeasure</code> or null.
     */
    private MDMeasure getMeasure (ComponentInfo componentInfo) {
      MDMeasure measure = null;

      // Retrieve the measure dimension
      QueryAccess qa = m_query.createQueryAccess();
      String strMeasureDimension = 
        qa.getMeasureDimension (LayerMetadataMap.LAYER_METADATA_NAME);
      qa.release();

      // Retrieve the MetadataManager unique ID of the measure associated with 
      // this component.
      QDRMember qdrMember = componentInfo.getQDR (m_queryListener.getDataAccess(),
        DataAccess.QDR_WITH_PAGE).getDimMember(strMeasureDimension);
      
      String strMeasureID = null;
      if (qdrMember != null) {
        strMeasureID = (String)qdrMember.getData();
      }

      // Retrieve the measure associated with this MetadataManager unique ID
      if (strMeasureID != null) {
        try {
          measure = m_biFrame.getMetadataManager().getMeasure (MM.UNIQUE_ID, strMeasureID);
        }
        
        catch (MetadataManagerException metadataManagerException) {
          m_biFrame.showExceptionDialog (m_biFrame, metadataManagerException);
        }
      }
      
      return measure;
    }
  }

  /**
   * This method updates the measures based on the specified MDMeasure.
   *
   * @param mdMeasure A <code>MDMeasure</code> value that represents the measure 
   *        to add.
   * 
   * @throws InvalidStepArgException, SelectionException, QueryException or
   *         MetadataManagerException if measures could not be updated
   */
  private void updateMeasures (MDMeasure mdMeasure)
    throws InvalidStepArgException, SelectionException,
      QueryException, MetadataManagerException {
    // Get the MetadataManager unique ID for the measure and add it to the 
    // current cube
    String[] strMeasureID = {mdMeasure.getUniqueID()};
    m_query.addCubeMeasures (strMeasureID);
  }

  /**
   * This method inserts a calculation that represents the value of the input 
   * measure one year ago.
   *
   * @param mdMeasure A <code>MDMeasure</code> to generate the year ago 
   *        calculation for.
   */
  private void insertYearAgo (MDMeasure mdMeasure) {
    // Create a new TimeLagLeadStep and set its name and description
    TimeLagLeadStep timeLagLeadStep = new TimeLagLeadStep();
    timeLagLeadStep.setType (CalcStep.CALCTYPE_LAG);
    timeLagLeadStep.setName (mdMeasure.toString() + " Year Ago");
    timeLagLeadStep.setDescription (Step.SHORT, mdMeasure.toString() + " Year Ago");

    // Retrieve the measure dimension ID
    QueryAccess qa = m_query.createQueryAccess();
    String strMeasureDimensionID = qa.getMeasureDimension (
      LayerMetadataMap.LAYER_METADATA_NAME);
    qa.release();

    // Associate the TimeLagLeadStep with the Measure Dimension 
    timeLagLeadStep.setDimension (strMeasureDimensionID);
    
    // Update the TimeLagLeadStep Measure operand 
    OlapQDR olapQDR = new OlapQDR (strMeasureDimensionID);
    olapQDR.addDimMemberPair (strMeasureDimensionID, mdMeasure.getUniqueID());
    timeLagLeadStep.setMeasure (new CalcValue (olapQDR, OlapQDR.class));

    // Set the dimension and hierarchy on the step
    try {
      // Retrieve the measure's dimensionality
      MDDimension[] mdDimensions = mdMeasure.getDimensions();
     
      for (int i = 0; i < mdDimensions.length; i++) {
        // Find the time dimension
        if (mdDimensions[i].isTimeDimension()) {
          MDDimension mdDimensionTime = mdDimensions[i];
          
          // Update the Time Dimension 
          timeLagLeadStep.setTimeDimension (
            new CalcValue (mdDimensionTime.getUniqueID(), String.class));

          // Update the Time Hierarchy 
          timeLagLeadStep.setTimeHierarchy (
            new CalcValue (mdDimensionTime.getDefaultHierarchy().getUniqueID(), 
              String.class));
        }
      }
    }
    
    catch (MetadataManagerException metadataManagerException) {
      m_biFrame.showExceptionDialog (this, metadataManagerException);
    }

    // Set the time lag to 1 year ago
    timeLagLeadStep.setPeriods (new CalcValue (new Integer(1), Integer.class));
    timeLagLeadStep.setPeriod (new CalcValue (TimeCalcStep.TIMECALC_YEAR, String.class));

    try {
      // Save the calculation
      mdMeasure = (MDMeasure) saveCalcStep ("", timeLagLeadStep.getName(), timeLagLeadStep);

      // Add the calculation to the Query 
      updateMeasures (mdMeasure);
    }
    
    catch (Exception exception) {
      m_biFrame.showExceptionDialog (this, exception);
    }
  }
}