// MemoryControl.java
// -------------------------------------------
// (C) 2005 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 22.09.2005 on http://yacy.net
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// LICENSE
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package net.yacy.kelondro.util;

import java.util.concurrent.atomic.AtomicBoolean;

import net.yacy.cora.util.ConcurrentLog;

/**
 * Use this to get information about memory usage or try to free some memory
 */
public class MemoryControl {

    private static AtomicBoolean shortStatus = new AtomicBoolean(false);
    private static boolean simulatedShortStatus = false, usingStandardStrategy = true;
    private static MemoryStrategy strategy;

    private static MemoryStrategy getStrategy() {
    	if (strategy == null || MemoryStrategy.hasError()) {
    		if (!usingStandardStrategy) {
    			strategy = new GenerationMemoryStrategy();
//    			if (strategy.hasError()) { // perhaps we do have a G1
//    				strategy = new G1MemoryStrategy();
//    			}
    	    	// fall back if error detected
    	    	if (MemoryStrategy.hasError()) {
    	    		usingStandardStrategy = true;
    	    		strategy = new StandardMemoryStrategy();
    	    	}
    		} else {
    			strategy = new StandardMemoryStrategy();
    		}
    	}
    	return strategy;
    }

    public final static void setStandardStrategy(final boolean std) {
        if (usingStandardStrategy != std) {
    		usingStandardStrategy = std;
    		strategy = null;
    	}
    }

    /**
     * @return the name of the used strategy
     */
    public final static String getStrategyName() {
    	getStrategy();
        return MemoryStrategy.getName();
    }

    /**
     * Runs the garbage collector if last garbage collection is more than last millis ago
     * @param last time which must be passed since lased gc
     * @param info additional info for log
     */
    public final synchronized static boolean gc(final int last, final String info) { // thq
    	return getStrategy().gc(last, info);
    }

    /**
     * memory that is free without increasing of total memory taken from os
     * @return bytes
     */
    public static final long free() {
        return getStrategy().free();
    }

    /**
     * memory that is available including increasing total memory up to maximum
     * @return bytes
     */
    public static final long available() {
        long available = getStrategy().available();
        return available;
    }

    /**
	 * maximum memory the Java virtual will allocate machine; may vary over time in some cases
	 * @return bytes
	 */
	public static final long maxMemory()
    {
    	return getStrategy().maxMemory();
    }

	/**
	 * currently allocated memory in the Java virtual machine; may vary over time
	 * @return bytes
	 */
	public static final long total()
	{
		return getStrategy().total();
	}

	/**
     * check for a specified amount of bytes
     *
     * @param size the requested amount of free memory in bytes
     * @param force specifies whether risk an expensive GC
     * @return whether enough memory could be freed (or is free) or not
     */
    public static boolean request(final long size, final boolean force) {
        if (size < 1024) return true; // to speed up things. If this would fail, it would be much too late to check this.
        return getStrategy().request(size, force, shortStatus);
    }

    /**
     * the simulated short status can be set to find out if the short status has effects to the system
     * @param status
     */
    public static void setSimulatedShortStatus(final boolean status) {
        simulatedShortStatus = status;
    }

    /**
     * the simulated short status can be retrieved to show that option in online interfaces
     * @return
     */
    public static boolean getSimulatedShortStatus() {
        return simulatedShortStatus;
    }

    /**
     * @return if last request failed
     */
    public static boolean shortStatus() {
        //if (shortStatus) System.out.println("**** SHORT MEMORY ****");
        return simulatedShortStatus || shortStatus.get();
    }

    /**
     * memory that is currently bound in objects
     * @return used bytes
     */
    public static long used() {
        return getStrategy().used();
    }

    /**
     * @return if Memory seams to be in a proper state
     */
    public static boolean properState() {
    	return getStrategy().properState();
    }

    /**
     * forced enable properState - StandardMemoryStrategy only
     */
    public static void resetProperState() {
    	getStrategy().resetProperState();
    }

    /**
     * set the memory to be available for properState - StandardMemoryStrategy only
     */
    public static void setProperMbyte(final long mbyte) {
    	getStrategy().setProperMbyte(mbyte);
    }

    /**
     * main
     * @param args use 'force' to request by force, use 'std' / 'gen' to specify strategy
     */
	public static void main(final String[] args) {
		long beginTime = System.nanoTime();
		int i = 0;
		try {
			// try this with different strategy and compare results
			final int mb = 1024 * 1024;
			boolean force = false;
			for (final String arg : args) {
				if (arg.equals("force"))
					force = true;
				if (arg.equalsIgnoreCase("gen"))
					usingStandardStrategy = false;
				if (arg.equalsIgnoreCase("std"))
					usingStandardStrategy = true;
			}
			System.out.println("vm: " + System.getProperty("java.vm.version"));
			System.out.println("computed max = " + (maxMemory() / mb) + " mb");
			System.out.println("using " + getStrategyName());
			if(force) {
				System.out.println("forcing garbage collector when requesting");
			} else {
				System.out.println("not forcing garbage collector when requesting");
			}
			final byte[][] x = new byte[100000][];

			for (i = 0; i < 100000; i++) {
				if (request(mb, force)) {
					x[i] = new byte[mb];
					System.out
							.println("used = " + i + " / " + (used() / mb) + ", total = " + (total() / mb) + ", free = "
									+ (free() / mb) + ", max = " + (maxMemory() / mb) + ", avail = "
									+ (available() / mb)
									+ (usingStandardStrategy
											? ", averageGC = "
													+ ((StandardMemoryStrategy) getStrategy()).getAverageGCFree()
											: ""));
				} else {
					System.out.println("No more memory available!");
					break;
				}
			}
		} finally {
			long time = System.nanoTime() - beginTime;
			System.out.println("Test run in " + time / 1000000 + " ms");
			ConcurrentLog.shutdown();
			System.out.println("Succesfull requests : " + i);
		}

	}

}
