/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Memory.java
 * Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

import javax.swing.JCheckBox;
import javax.swing.JOptionPane;

/**
 * A little helper class for Memory management. The memory management can be
 * disabled by using the setEnabled(boolean) method.
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 9493 $
 * @see #setEnabled(boolean)
 */
public class Memory implements RevisionHandler {

  /** whether memory management is enabled */
  protected static boolean m_Enabled = true;

  /** whether a GUI is present */
  protected boolean m_UseGUI = false;

  /** the managed bean to use */
  protected static MemoryMXBean m_MemoryMXBean = ManagementFactory
      .getMemoryMXBean();

  /** the last MemoryUsage object obtained */
  protected MemoryUsage m_MemoryUsage = null;

  /**
   * initializes the memory management without GUI support
   */
  public Memory() {
    this(false);
  }

  /**
   * initializes the memory management
   * 
   * @param useGUI whether a GUI is present
   */
  public Memory(boolean useGUI) {
    m_UseGUI = useGUI;
  }

  /**
   * returns whether the memory management is enabled
   * 
   * @return true if enabled
   */
  public boolean isEnabled() {
    return m_Enabled;
  }

  /**
   * sets whether the memory management is enabled
   * 
   * @param value true if the management should be enabled
   */
  public void setEnabled(boolean value) {
    m_Enabled = value;
  }

  /**
   * whether to display a dialog in case of a problem (= TRUE) or just print on
   * stderr (= FALSE)
   * 
   * @return true if the GUI is used
   */
  public boolean getUseGUI() {
    return m_UseGUI;
  }

  /**
   * returns the initial size of the JVM heap, obtains a fresh MemoryUsage
   * object to do so.
   * 
   * @return the initial size in bytes
   */
  public long getInitial() {
    m_MemoryUsage = m_MemoryMXBean.getHeapMemoryUsage();
    return m_MemoryUsage.getInit();
  }

  /**
   * returns the currently used size of the JVM heap, obtains a fresh
   * MemoryUsage object to do so.
   * 
   * @return the used size in bytes
   */
  public long getCurrent() {
    m_MemoryUsage = m_MemoryMXBean.getHeapMemoryUsage();
    return m_MemoryUsage.getUsed();
  }

  /**
   * returns the maximum size of the JVM heap, obtains a fresh MemoryUsage
   * object to do so.
   * 
   * @return the maximum size in bytes
   */
  public long getMax() {
    m_MemoryUsage = m_MemoryMXBean.getHeapMemoryUsage();
    return m_MemoryUsage.getMax();
  }

  /**
   * checks if there's still enough memory left by checking whether there is
   * still a 50MB margin between getUsed() and getMax(). if ENABLED is true,
   * then false is returned always. updates the MemoryUsage variable before
   * checking.
   * 
   * @return true if out of memory (only if management enabled, otherwise always
   *         false)
   */
  public boolean isOutOfMemory() {
    m_MemoryUsage = m_MemoryMXBean.getHeapMemoryUsage();
    if (isEnabled()) {
      return ((m_MemoryUsage.getMax() - m_MemoryUsage.getUsed()) < 52428800);
    } else
      return false;
  }

  /**
   * Checks to see if memory is running low. Low is defined as available memory
   * less than 20% of max memory.
   * 
   * @return true if memory is running low
   */
  public boolean memoryIsLow() {
    m_MemoryUsage = m_MemoryMXBean.getHeapMemoryUsage();

    if (isEnabled()) {
      long lowThreshold = (long) (0.2 * m_MemoryUsage.getMax());

      // min threshold of 100Mb
      if (lowThreshold < 104857600) {
        lowThreshold = 104857600;
      }

      long avail = m_MemoryUsage.getMax() - m_MemoryUsage.getUsed();

      return (avail < lowThreshold);
    } else {
      return false;
    }
  }

  /**
   * returns the amount of bytes as MB
   * 
   * @return the MB amount
   */
  public static double toMegaByte(long bytes) {
    return (bytes / (double) (1024 * 1024));
  }

  /**
   * prints an error message if OutOfMemory (and if GUI is present a dialog),
   * otherwise nothing happens. isOutOfMemory() has to be called beforehand,
   * since it sets all the memory parameters.
   * 
   * @see #isOutOfMemory()
   * @see #m_Enabled
   */
  public void showOutOfMemory() {
    if (!isEnabled() || (m_MemoryUsage == null))
      return;

    System.gc();

    String msg = "Not enough memory (less than 50MB left on heap). Please load a smaller "
        + "dataset or use a larger heap size.\n"
        + "- initial heap size:   "
        + Utils.doubleToString(toMegaByte(m_MemoryUsage.getInit()), 1)
        + "MB\n"
        + "- current memory (heap) used:  "
        + Utils.doubleToString(toMegaByte(m_MemoryUsage.getUsed()), 1)
        + "MB\n"
        + "- max. memory (heap) available: "
        + Utils.doubleToString(toMegaByte(m_MemoryUsage.getMax()), 1)
        + "MB\n"
        + "\n"
        + "Note:\n"
        + "The Java heap size can be specified with the -Xmx option.\n"
        + "E.g., to use 128MB as heap size, the command line looks like this:\n"
        + "   java -Xmx128m -classpath ...\n"
        + "This does NOT work in the SimpleCLI, the above java command refers\n"
        + "to the one with which Weka is started. See the Weka FAQ on the web\n"
        + "for further info.";

    System.err.println(msg);

    if (getUseGUI())
      JOptionPane.showMessageDialog(null, msg, "OutOfMemory",
          JOptionPane.WARNING_MESSAGE);
  }

  /**
   * Prints a warning message if memoryIsLow (and if GUI is present a dialog).
   * 
   * @return true if user opts to continue, disabled or GUI is not present.
   */
  public boolean showMemoryIsLow() {
    if (!isEnabled() || (m_MemoryUsage == null))
      return true;

    String msg = "Warning: memory is running low - available heap space is less than "
        + "20% of maximum or 100MB (whichever is greater)\n\n"
        + "- initial heap size:   "
        + Utils.doubleToString(toMegaByte(m_MemoryUsage.getInit()), 1)
        + "MB\n"
        + "- current memory (heap) used:  "
        + Utils.doubleToString(toMegaByte(m_MemoryUsage.getUsed()), 1)
        + "MB\n"
        + "- max. memory (heap) available: "
        + Utils.doubleToString(toMegaByte(m_MemoryUsage.getMax()), 1)
        + "MB\n\n"
        + "Consider deleting some results before continuing.\nCheck the Weka FAQ "
        + "on the web for suggestions on how to save memory.\n"
        + "Note that Weka will shut down when less than 50MB remain."
        + "\nDo you wish to continue regardless?\n\n";

    System.err.println(msg);

/*    if (getUseGUI()) {
      if (!Utils.getDontShowDialog("weka.core.Memory.LowMemoryWarning")) {
        JCheckBox dontShow = new JCheckBox("Do not show this message again");
        Object[] stuff = new Object[2];
        stuff[0] = msg;
        stuff[1] = dontShow;

        int result = JOptionPane.showConfirmDialog(null, stuff, "Memory",
            JOptionPane.YES_NO_OPTION);

        if (dontShow.isSelected()) {
          try {
            Utils.setDontShowDialog("weka.core.Memory.LowMemoryWarning");
          } catch (Exception ex) {
            // quietly ignore
          }
        }

        return (result == JOptionPane.YES_OPTION);
      }
    }
*/
    return true;
  }

  /**
   * stops all the current threads, to make a restart possible
   */
  public void stopThreads() {
    int i;
    Thread[] thGroup;
    Thread t;

    thGroup = new Thread[Thread.activeCount()];
    Thread.enumerate(thGroup);

    for (i = 0; i < thGroup.length; i++) {
      t = thGroup[i];
      if (t != null) {
        if (t != Thread.currentThread()) {
          if (t.getName().startsWith("Thread"))
            t.stop();
          else if (t.getName().startsWith("AWT-EventQueue"))
            t.stop();
        }
      }
    }

    thGroup = null;

    System.gc();
  }

  /**
   * Returns the revision string.
   * 
   * @return the revision
   */
  @Override
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 9493 $");
  }

  /**
   * prints only some statistics
   * 
   * @param args the commandline arguments - ignored
   */
  public static void main(String[] args) {
    Memory mem = new Memory();
    System.out.println("Initial memory: "
        + Utils.doubleToString(Memory.toMegaByte(mem.getInitial()), 1) + "MB"
        + " (" + mem.getInitial() + ")");
    System.out.println("Max memory: "
        + Utils.doubleToString(Memory.toMegaByte(mem.getMax()), 1) + "MB"
        + " (" + mem.getMax() + ")");
  }
}
