/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.cache.lru;

import java.util.Properties;

import org.apache.geode.StatisticDescriptor;
import org.apache.geode.StatisticsFactory;
import org.apache.geode.StatisticsType;
import org.apache.geode.StatisticsTypeFactory;
import org.apache.geode.cache.EvictionAction;
import org.apache.geode.cache.EvictionAlgorithm;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.util.ObjectSizer;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.internal.cache.BucketRegion;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.Token;
import org.apache.geode.internal.cache.control.InternalResourceManager;
import org.apache.geode.internal.cache.persistence.DiskRegionView;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.statistics.StatisticsTypeFactoryImpl;

/**
 * A <code>HeapLRUCapacityController</code> controls the contents of {@link Region} based on the
 * percentage of memory that is currently being used. If the percentage of memory in use exceeds the
 * given percentage, then the least recently used entry of the region is evicted.
 * <p>
 * For heap regions: GemStone has found that the <code>HeapLRUCapacityController</code> has the most
 * effect on a VM that is lauched with both the <code>-Xmx</code> and <code>-Xms</code> switches
 * used. Many virtual machine implementations have additional VM switches to control the behavior of
 * the garbage collector. We suggest that you investigate tuning the garbage collector when using a
 * <code>HeapLRUCapacityController</code>. In particular, we have found that when running with Sun's
 * <A href="http://java.sun.com/docs/hotspot/gc/index.html">HotSpot</a> VM, the
 * <code>-XX:+UseConcMarkSweepGC</code> and <code>-XX:+UseParNewGC</code> options improve the
 * behavior of the <code>HeapLRUCapacityController</code>.
 *
 * @since GemFire 3.2
 */
@SuppressWarnings("synthetic-access")
public class HeapLRUCapacityController extends LRUAlgorithm {
  private static final long serialVersionUID = 4970685814429530675L;
  /**
   * The default percentage of VM heap usage over which LRU eviction occurs
   */
  public static final String TOP_UP_HEAP_EVICTION_PERCENTAGE_PROPERTY =
      DistributionConfig.GEMFIRE_PREFIX + "topUpHeapEvictionPercentage";

  public static final float DEFAULT_TOP_UP_HEAP_EVICTION_PERCENTAGE = 4.0f;

  public static final int DEFAULT_HEAP_PERCENTAGE = 75;

  public static final int PER_ENTRY_OVERHEAD = 250;

  private int perEntryOverhead = PER_ENTRY_OVERHEAD;


  /**
   * The default number of milliseconds the evictor thread should wait before evicting the LRU
   * entry.
   */
  public static final int DEFAULT_EVICTOR_INTERVAL = 500;

  protected static final StatisticsType statType;

  static {
    // create the stats type for MemLRU.
    StatisticsTypeFactory f = StatisticsTypeFactoryImpl.singleton();

    final String entryBytesDesc =
        "The amount of memory currently used by regions configured for eviction.";
    final String lruEvictionsDesc = "Number of total entry evictions triggered by LRU.";
    final String lruDestroysDesc =
        "Number of entries destroyed in the region through both destroy cache operations and eviction. Reset to zero each time it exceeds lruDestroysLimit.";
    final String lruDestroysLimitDesc =
        "Maximum number of entry destroys triggered by LRU before scan occurs.";
    final String lruEvaluationsDesc = "Number of entries evaluated during LRU operations.";
    final String lruGreedyReturnsDesc = "Number of non-LRU entries evicted during LRU operations";

    statType = f.createType("HeapLRUStatistics",
        "Statistics about byte based Least Recently Used region entry disposal",
        new StatisticDescriptor[] {f.createLongGauge("entryBytes", entryBytesDesc, "bytes"),
            f.createLongCounter("lruEvictions", lruEvictionsDesc, "entries"),
            f.createLongCounter("lruDestroys", lruDestroysDesc, "entries"),
            f.createLongGauge("lruDestroysLimit", lruDestroysLimitDesc, "entries"),
            f.createLongCounter("lruEvaluations", lruEvaluationsDesc, "entries"),
            f.createLongCounter("lruGreedyReturns", lruGreedyReturnsDesc, "entries"),});
  }

  // //////////////////// Instance Fields /////////////////////

  // ////////////////////// Constructors ///////////////////////

  /**
   * Creates a new <code>HeapLRUCapacityController</code> with the given eviction action.
   * 
   * @param evictionAction The action that will occur when an entry is evicted
   * 
   */
  public HeapLRUCapacityController(EvictionAction evictionAction, Region region) {
    super(evictionAction, region);
  }

  public HeapLRUCapacityController(ObjectSizer sizerImpl, EvictionAction evictionAction,
      Region region) {
    super(evictionAction, region);
    setSizer(sizerImpl);
  }
  // ///////////////////// Instance Methods ///////////////////////

  @Override
  public void setLimit(int maximum) {}

  // Candidate for removal since capacity controller is no longer part of
  // cache.xml
  @Override
  public Properties getProperties() {
    throw new IllegalStateException("Unused properties");
  }

  @Override
  public void close() {
    super.close();
  }

  @Override
  public long getLimit() {
    return 0;
  }

  @Override
  public boolean equals(Object cc) {
    if (!super.equals(cc))
      return false;
    return true;
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#hashCode()
   * 
   * Note that we just need to make sure that equal objects return equal hashcodes; nothing really
   * elaborate is done here.
   */
  @Override
  public int hashCode() {
    int result = super.hashCode();
    return result;
  }

  /**
   * Returns a brief description of this eviction controller.
   * 
   * @since GemFire 4.0
   */
  @Override
  public String toString() {
    return LocalizedStrings.HeapLRUCapacityController_HEAPLRUCAPACITYCONTROLLER_WITH_A_CAPACITY_OF_0_OF_HEAP_AND_AN_THREAD_INTERVAL_OF_1_AND_EVICTION_ACTION_2
        .toLocalizedString(new Object[] {Long.valueOf(this.getLimit()), this.getEvictionAction()});
  }

  /**
   * Sets the {@link ObjectSizer} used to calculate the size of objects placed in the cache.
   *
   * @param sizer The name of the sizer class
   */
  private void setSizer(ObjectSizer sizer) {
    this.sizer = sizer;
  }

  @Override
  protected EnableLRU createLRUHelper() {
    return new AbstractEnableLRU() {

      /**
       * Indicate what kind of <code>EvictionAlgorithm</code> this helper implements
       */
      public EvictionAlgorithm getEvictionAlgorithm() {
        return EvictionAlgorithm.LRU_HEAP;
      }

      /**
       * As far as we're concerned all entries have the same size
       */
      public int entrySize(Object key, Object value) throws IllegalArgumentException {
        // value is null only after eviction occurs. A change in size is
        // required for eviction stats, bug 30974
        /*
         * if (value != null) { return 1; } else { return 0; }
         */
        if (value == Token.TOMBSTONE) {
          return 0;
        }

        int size = HeapLRUCapacityController.this.getPerEntryOverhead();
        size += sizeof(key);
        size += sizeof(value);
        return size;
      }

      /**
       * In addition to initializing the statistics, create an evictor thread to periodically evict
       * the LRU entry.
       */
      @Override
      public LRUStatistics initStats(Object region, StatisticsFactory sf) {
        setRegionName(region);
        final LRUStatistics stats = new HeapLRUStatistics(sf, getRegionName(), this);
        setStats(stats);
        return stats;
      }

      public StatisticsType getStatisticsType() {
        return statType;
      }

      public String getStatisticsName() {
        return "HeapLRUStatistics";
      }

      public int getLimitStatId() {
        throw new UnsupportedOperationException("Limit not used with this LRU type");
      }

      public int getCountStatId() {
        return statType.nameToId("entryBytes");
      }

      public int getEvictionsStatId() {
        return statType.nameToId("lruEvictions");
      }

      public int getDestroysStatId() {
        return statType.nameToId("lruDestroys");
      }

      public int getDestroysLimitStatId() {
        return statType.nameToId("lruDestroysLimit");
      }

      public int getEvaluationsStatId() {
        return statType.nameToId("lruEvaluations");
      }

      public int getGreedyReturnsStatId() {
        return statType.nameToId("lruGreedyReturns");
      }

      /**
       * Okay, deep breath. Instead of basing the LRU calculation on the number of entries in the
       * region or on their "size" (which turned out to be incorrectly estimated in the general
       * case), we use the amount of memory currently in use. If the amount of memory current in use
       * {@linkplain Runtime#maxMemory max memory} - {@linkplain Runtime#freeMemory free memory} is
       * greater than the overflow threshold, then we evict the LRU entry.
       */
      public boolean mustEvict(LRUStatistics stats, Region region, int delta) {
        final InternalCache cache = (InternalCache) region.getRegionService();
        InternalResourceManager resourceManager = cache.getInternalResourceManager();
        boolean offheap = region.getAttributes().getOffHeap();
        final boolean monitorStateIsEviction =
            resourceManager.getMemoryMonitor(offheap).getState().isEviction();
        if (region instanceof BucketRegion) {
          return monitorStateIsEviction && ((BucketRegion) region).getSizeForEviction() > 0;
        }

        return monitorStateIsEviction && ((LocalRegion) region).getRegionMap().sizeInVM() > 0;
      }

      @Override
      public boolean lruLimitExceeded(LRUStatistics lruStatistics, DiskRegionView drv) {
        InternalResourceManager resourceManager =
            drv.getDiskStore().getCache().getInternalResourceManager();
        return resourceManager.getMemoryMonitor(drv.getOffHeap()).getState().isEviction();
      }
    };
  }

  // ////////////////////// Inner Classes ////////////////////////

  private ObjectSizer sizer;

  /**
   * Return the size of an object as stored in GemFire... Typically this is the serialized size in
   * bytes.. This implementation is slow.... Need to add Sizer interface and call it for customer
   * objects.
   */
  protected int sizeof(Object o) throws IllegalArgumentException {
    return MemLRUCapacityController.basicSizeof(o, this.sizer);
  }

  public int getPerEntryOverhead() {
    return perEntryOverhead;
  }

  public void setEntryOverHead(int entryOverHead) {
    this.perEntryOverhead = entryOverHead;
  }

}
