/* 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.
 */

// BEGIN android-note
// address length was changed from long to int for performance reasons.
// END android-note

package org.apache.harmony.luni.platform;

import java.io.IOException;
import java.nio.channels.FileChannel.MapMode;

public class PlatformAddressFactory {

    // BEGIN android-added
    /**
     * Defines the number of PlatformAddress objects to be cached. Must be a
     * power of two. Caching PlatformAddress objects minimizes the creation
     * of garbage and reduces the number of GC-hiccups in OpenGL animations.
     */
    private final static int CACHE_SIZE = 1<<8;

    /**
     * A mask with all bits set, matching the size of the cache.
     */
    private final static int CACHE_MASK = CACHE_SIZE - 1;

    /**
     * Defines the maximum number of probes taken per hash, used for looking
     * up an empty cache slot or a previously stored PlatformAddress.
     */
    private final static int MAX_PROBES = 5;

    /**
     * A cycling index (0 to MAX_PROBES-1) used to replace elements in
     * the cache.
     */
    private static int replacementIndex = 0;

    /**
     * Array of PlatformAddress references kept from garbage collection.
     */
    private static PlatformAddress[] cache = new PlatformAddress[CACHE_SIZE];

    /**
     * Constructs a {@code PlatformAddress} or returns
     * {@link PlatformAddress#NULL} if given a {@code null} address.
     *
     * @param address the start address for the memory; {@code 0} means
     * {@code null}
     * @param size the size of the memory in bytes
     * @return an appropriately-constructed {@code PlatformAddress}
     */
    private static PlatformAddress make(int value, long size) {
        if (value == 0) {
            return PlatformAddress.NULL;
        }

        return new PlatformAddress(value, size);
    }
    // END android-added

    // BEGIN android-changed
    public synchronized static PlatformAddress on(int value, long size) {
        if (value == 0) {
            return PlatformAddress.NULL;
        }
        int idx = value >> 5;
        for (int probe = 0; probe < MAX_PROBES; probe++) {
            PlatformAddress cachedObj = cache[(idx + probe) & CACHE_MASK];
            if (cachedObj == null) {
                return cache[(idx + probe) & CACHE_MASK] =
                    new PlatformAddress(value, size);
            }
            if (cachedObj.osaddr == value && cachedObj.size == size) {
                return cachedObj;
            }
        }
        replacementIndex = (replacementIndex + 1) % MAX_PROBES;
        return cache[(idx + replacementIndex) & CACHE_MASK] =
            new PlatformAddress(value, size);
    }
    // END android-changed

    public static PlatformAddress on(int value) {
        return PlatformAddressFactory.on(value, PlatformAddress.UNKNOWN);
    }

    public static MappedPlatformAddress mapOn(int value, long size) {
        MappedPlatformAddress addr = new MappedPlatformAddress(value, size);
        return addr;
    }

    public static PlatformAddress allocMap(int fd, long start, long size, MapMode mode)
            throws IOException {
        if (size == 0) {
            // if size is 0, call to mmap has incorrect behaviour on
            // unix and windows, so return empty address
            return mapOn(0, 0);
        }
        int osAddress = OSMemory.mmap(fd, start, size, mode);
        PlatformAddress newMemory = mapOn(osAddress, size);
        PlatformAddress.memorySpy.alloc(newMemory);
        return newMemory;
    }

    /**
     * Allocates a contiguous block of OS heap memory and initializes it to
     * a given value.
     *
     * @param size The number of bytes to allocate from the system heap.
     * @param init The value to initialize the memory.
     * @return PlatformAddress representing the memory block.
     */
    public static PlatformAddress alloc(int size, byte init) {
        int osAddress = OSMemory.malloc(size);
        OSMemory.memset(osAddress, init, size);
        /*
         * We use make() and not on() here, for a couple reasons:
         * First and foremost, doing so means that if the client uses
         * address.autoFree() (to enable auto-free on gc) the cache
         * won't prevent the freeing behavior. Second, this avoids
         * polluting the cache with addresses that aren't likely to be
         * reused anyway.
         */
        PlatformAddress newMemory = make(osAddress, size);
        PlatformAddress.memorySpy.alloc(newMemory);
        return newMemory;
    }
}
