/*
 * Copyright 2010-2012 Luca Garulli (l.garulli--at--orientechnologies.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.orientechnologies.orient.core.memory;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import com.orientechnologies.common.io.OFileUtils;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE;
import com.orientechnologies.common.profiler.OProfiler.OProfilerHookValue;
import com.orientechnologies.orient.core.Orient;

/**
 * This memory warning system will call the listener when we exceed the percentage of available memory specified. There should only
 * be one instance of this object created, since the usage threshold can only be set to one number.
 */
public class OMemoryWatchDog extends Thread {
  private final Set<Listener>      listeners    = new HashSet<Listener>(128);
  private int                      alertTimes   = 0;
  protected ReferenceQueue<Object> monitorQueue = new ReferenceQueue<Object>();
  protected SoftReference<Object>  monitorRef   = new SoftReference<Object>(new Object(), monitorQueue);

  public static interface Listener {
    /**
     * Execute a soft free of memory resources.
     * 
     * @param iType
     *          OS or JVM
     * @param iFreeMemory
     *          Current used memory
     * @param iFreeMemoryPercentage
     *          Max memory
     */
    public void memoryUsageLow(long iFreeMemory, long iFreeMemoryPercentage);
  }

  /**
   * Create the memory watch dog with the default memory threshold.
   * 
   * @param iThreshold
   */
  public OMemoryWatchDog() {
    super("OrientDB MemoryWatchDog");

    setDaemon(true);
    start();
  }

  public void run() {
    Orient
        .instance()
        .getProfiler()
        .registerHookValue("system.memory.alerts", "Number of alerts received by JVM to free memory resources",
            METRIC_TYPE.COUNTER, new OProfilerHookValue() {
              public Object getValue() {
                return alertTimes;
              }
            });

    while (true) {
      try {
        // WAITS FOR THE GC FREE
        monitorQueue.remove();

        // GC is freeing memory!
        alertTimes++;
        long maxMemory = Runtime.getRuntime().maxMemory();
        long freeMemory = Runtime.getRuntime().freeMemory();
        int freeMemoryPer = (int) (freeMemory * 100 / maxMemory);

        if (OLogManager.instance().isDebugEnabled())
          OLogManager.instance().debug(this, "Free memory is low %s of %s (%d%%), calling listeners to free memory...",
              OFileUtils.getSizeAsString(freeMemory), OFileUtils.getSizeAsString(maxMemory), freeMemoryPer);

        final long timer = Orient.instance().getProfiler().startChrono();

        synchronized (listeners) {
          for (Listener listener : listeners) {
            try {
              listener.memoryUsageLow(freeMemory, freeMemoryPer);
            } catch (Exception e) {
              e.printStackTrace();
            }
          }
        }

        Orient.instance().getProfiler().stopChrono("OMemoryWatchDog.freeResources", "WatchDog free resources", timer);

      } catch (Exception e) {
      } finally {
        // RE-INSTANTIATE THE MONITOR REF
        monitorRef = new SoftReference<Object>(new Object(), monitorQueue);
      }
    }
  }

  public Collection<Listener> getListeners() {
    synchronized (listeners) {
      return listeners;
    }
  }

  public Listener addListener(final Listener listener) {
    synchronized (listeners) {
      listeners.add(listener);
    }
    return listener;
  }

  public boolean removeListener(final Listener listener) {
    synchronized (listeners) {
      return listeners.remove(listener);
    }
  }

  public static void freeMemory(final long iDelayTime) {
    // INVOKE GC AND WAIT A BIT
    System.gc();
    if (iDelayTime > 0)
      try {
        Thread.sleep(iDelayTime);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
  }
}
@@ -823,23 +823,31 @@
 va.util.
+Identity
 Hash
-Set
+Map
 ;%0D%0Aimpor
@@ -858,19 +858,19 @@
 va.util.
-Set
+Map
 ;%0D%0A%0D%0Aimp
@@ -1476,19 +1476,19 @@
 e final 
-Set
+Map
 %3CListene
@@ -1492,14 +1492,17 @@
 ener
-%3E     
+, Object%3E
  lis
@@ -1521,15 +1521,23 @@
 new 
+Identity
 Hash
-Set
+Map
 %3CLis
@@ -1541,16 +1541,24 @@
 Listener
+, Object
 %3E(128);%0D
@@ -1585,32 +1585,35 @@
                 
+   
 alertTimes   = 0
@@ -1649,16 +1649,19 @@
 %3CObject%3E
+   
  monitor
@@ -1732,16 +1732,19 @@
 %3CObject%3E
+   
   monito
@@ -3568,32 +3568,41 @@
 ener : listeners
+.keySet()
 ) %7B%0D%0A           
@@ -4207,16 +4207,25 @@
 isteners
+.keySet()
 ;%0D%0A    %7D
@@ -4343,12 +4343,22 @@
 ers.
-add(
+put(listener, 
 list
@@ -4485,32 +4485,32 @@
  (listeners) %7B%0D%0A
-
       return lis
@@ -4524,32 +4524,40 @@
 remove(listener)
+ != null
 ;%0D%0A    %7D%0D%0A  %7D%0D%0A%0D

Patch by Sylvain to safely handle WatchDog listeners that override hasCode/equals methods