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


/**
 * The platform address class is an unsafe virtualization of an OS memory block.
 * 
 */
public class PlatformAddress implements ICommonDataTypes, Comparable {

    /**
     * This final field defines the size of an address on this platform.
     */
    static final int SIZEOF = Platform.getMemorySystem().getPointerSize();

    /**
     * NULL is the canonical address with address value zero.
     */
    public static final PlatformAddress NULL = new PlatformAddress(0, 0);

    public static final IMemorySpy memorySpy = new RuntimeMemorySpy();

    static final IMemorySystem osMemory = Platform.getMemorySystem();

    static final long UNKNOWN = -1;

    final int osaddr;
    
    final long size;

    PlatformAddress(int address, long size) {
        super();
        osaddr = address;
        this.size = size;
    }

    /**
     * Sending auto free to an address means that, when this subsystem has
     * allocated the memory, it will automatically be freed when this object is
     * collected by the garbage collector if the memory has not already been
     * freed explicitly.
     * 
     */
    public final void autoFree() {
        memorySpy.autoFree(this);
    }
    
    public PlatformAddress duplicate(){
        return PlatformAddressFactory.on(osaddr, size);
    }

    public PlatformAddress offsetBytes(int offset) {
        return PlatformAddressFactory.on(osaddr + offset, size - offset);
    }

    // BEGIN android-removed
    // public PlatformAddress offsetBytes(long offset) {
    //     return PlatformAddressFactory.on(osaddr + offset, size - offset);
    // }
    // END android-removed

    public final void moveTo(PlatformAddress dest, long numBytes) {
        osMemory.memmove(dest.osaddr, osaddr, numBytes);
    }

    public final boolean equals(Object other) {
        return (other instanceof PlatformAddress)
                && (((PlatformAddress) other).osaddr == osaddr);
    }

    public final int hashCode() {
        return (int) osaddr;
    }

    public final boolean isNULL() {
        return this == NULL;
    }

    public void free() {
        // Memory spys can veto the basic free if they determine the memory was
        // not allocated.
        if (memorySpy.free(this)) {
            osMemory.free(osaddr);
        }
    }

    public final void setAddress(int offset, PlatformAddress address) {
        osMemory.setAddress(osaddr + offset, address.osaddr);
    }

    public final PlatformAddress getAddress(int offset) {
        int addr = getInt(offset);
        return PlatformAddressFactory.on(addr);
    }

    public final void setByte(int offset, byte value) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JBYTE);
        osMemory.setByte(osaddr + offset, value);
    }

    public final void setByteArray(int offset, byte[] bytes, int bytesOffset,
            int length) {
        memorySpy.rangeCheck(this, offset, length * SIZEOF_JBYTE);
        osMemory.setByteArray(osaddr + offset, bytes, bytesOffset, length);
    }
    
    // BEGIN android-added
    public final void setShortArray(int offset, short[] shorts,
            int shortsOffset, int length, boolean swap) {
        memorySpy.rangeCheck(this, offset, length * SIZEOF_JSHORT);
        osMemory.setShortArray(osaddr + offset, shorts, shortsOffset, length,
            swap);
    }

    public final void setIntArray(int offset, int[] ints,
            int intsOffset, int length, boolean swap) {
        memorySpy.rangeCheck(this, offset, length * SIZEOF_JINT);
        osMemory.setIntArray(osaddr + offset, ints, intsOffset, length, swap);
    }
    // END android-added

    public final byte getByte(int offset) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JBYTE);
        return osMemory.getByte(osaddr + offset);
    }

    public final void getByteArray(int offset, byte[] bytes, int bytesOffset,
            int length) {
        memorySpy.rangeCheck(this, offset, length * SIZEOF_JBYTE);
        osMemory.getByteArray(osaddr + offset, bytes, bytesOffset, length);
    }

    public final void setShort(int offset, short value, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JSHORT);
        osMemory.setShort(osaddr + offset, value, order);
    }

    public final void setShort(int offset, short value) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JSHORT);
        osMemory.setShort(osaddr + offset, value);
    }

    public final short getShort(int offset, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JSHORT);
        return osMemory.getShort(osaddr + offset, order);
    }

    public final short getShort(int offset) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JSHORT);
        return osMemory.getShort(osaddr + offset);
    }

    public final void setInt(int offset, int value, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JINT);
        osMemory.setInt(osaddr + offset, value, order);
    }

    public final void setInt(int offset, int value) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JINT);
        osMemory.setInt(osaddr + offset, value);
    }

    public final int getInt(int offset, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JINT);
        return osMemory.getInt(osaddr + offset, order);
    }

    public final int getInt(int offset) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JINT);
        return osMemory.getInt(osaddr + offset);
    }

    public final void setLong(int offset, long value, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JLONG);
        osMemory.setLong(osaddr + offset, value, order);
    }

    public final void setLong(int offset, long value) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JLONG);
        osMemory.setLong(osaddr + offset, value);
    }

    public final long getLong(int offset, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JLONG);
        return osMemory.getLong(osaddr + offset, order);
    }

    public final long getLong(int offset) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JLONG);
        return osMemory.getLong(osaddr + offset);
    }

    public final void setFloat(int offset, float value, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JFLOAT);
        osMemory.setFloat(osaddr + offset, value, order);
    }

    public final void setFloat(int offset, float value) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JFLOAT);
        osMemory.setFloat(osaddr + offset, value);
    }

    public final float getFloat(int offset, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JFLOAT);
        return osMemory.getFloat(osaddr + offset, order);
    }

    public final float getFloat(int offset) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JFLOAT);
        return osMemory.getFloat(osaddr + offset);
    }

    public final void setDouble(int offset, double value, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JDOUBLE);
        osMemory.setDouble(osaddr + offset, value, order);
    }

    public final void setDouble(int offset, double value) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JDOUBLE);
        osMemory.setDouble(osaddr + offset, value);
    }

    public final double getDouble(int offset, Endianness order) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JDOUBLE);
        return osMemory.getDouble(osaddr + offset, order);
    }

    public final double getDouble(int offset) {
        memorySpy.rangeCheck(this, offset, SIZEOF_JDOUBLE);
        return osMemory.getDouble(osaddr + offset);
    }

    // BEGIN android-added
    public final int toInt() {
        return osaddr;
    }
    // END android-added

    public final long toLong() {
        return osaddr;
    }

    public final String toString() {
        return "PlatformAddress[" + osaddr + "]"; //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    public final long getSize(){
        return size;
    }

    public final int compareTo(Object other) {
        if (other == null) {
            throw new NullPointerException(); // per spec.
        }
        if (other instanceof PlatformAddress) {
            int otherPA = ((PlatformAddress) other).osaddr;
            if (osaddr == otherPA) {
                return 0;
            }
            return osaddr < otherPA ? -1 : 1;
        }

        throw new ClassCastException(); // per spec.
    }
}
