/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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 dex.reader;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public final class DexBuffer {

    private ByteBuffer b;

    public DexBuffer(String fileName) throws IOException {

        // FIXME channel? allocate fix size?
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(fileName);

            BufferedInputStream bis = new BufferedInputStream(fis);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int len;
            while ((len = bis.read(buf)) > 0) {
                bos.write(buf, 0, len);
            }
            byte[] bytes = bos.toByteArray();
            initialize(ByteBuffer.wrap(bytes));
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

    public DexBuffer(byte[] bytes) {
        initialize(ByteBuffer.wrap(bytes));
    }

    private DexBuffer(ByteBuffer slice) {
        initialize(slice);
    }

    private void initialize(ByteBuffer buffer) {
        b = buffer.asReadOnlyBuffer();
        b.clear();
        b.order(ByteOrder.LITTLE_ENDIAN);
    }

    public void setPosition(int offset) {
        b.position(offset);
    }

    public void readBytes(byte[] dst) {
        b.get(dst, 0, dst.length);
    }

    /**
     * FIXME make endian dependent
     */
    public int readUleb128() {
        int endValue = 0;
        int value = 0;
        int nr = 0;
        do {
            value = (b.get() & 0xFF);
            endValue |= ((value & 0x7F) << 7 * nr);// cut away left most bit
            nr++;
        } while ((value & 0x80) != 0); // highest bit set?
        return endValue;
    }

    /**
     * pre 0 < nBytes <=4
     */
    public int readInt(int nBytes) {
        int endValue = 0;
        int tmp = 0;
        for (int i = 0; i < nBytes; i++) {
            tmp = b.get() & 0xFF;
            endValue |= (tmp << i * 8);
        }
        return endValue;
    }

    /**
     * pre 0 < nBytes <=1 FIXME: Sign extension
     */
    public short readShort(int nBytes) {
        short endValue = 0;
        int tmp = 0;
        for (int i = 0; i < nBytes; i++) {
            tmp = b.get() & 0xFF;
            endValue |= (tmp << i * 8);
        }
        return endValue;
    }

    /**
     * pre 0 < nBytes <=1
     */
    public char readChar(int nBytes) {
        char endValue = 0;
        int tmp = 0;
        for (int i = 0; i < nBytes; i++) {
            tmp = b.get() & 0xFF;
            endValue |= (tmp << i * 8);
        }
        return endValue;
    }

    /**
     * pre 0 < nBytes <=7 FIXME: Sign extension
     */
    public long readLong(int nBytes) {
        long endValue = 0;
        int tmp = 0;
        for (int i = 0; i < nBytes; i++) {
            tmp = b.get() & 0xFF;
            endValue |= (tmp << i * 8);
        }
        return endValue;
    }

    /**
     * pre 0 < nBytes <=4
     */
    public float readFloat(int nBytes) {
        int bits = readInt(nBytes);
        int bytesToMove = (4 - nBytes) * 8;
        bits <<= bytesToMove;
        return Float.intBitsToFloat(bits);
    }

    // returns int form current position
    public int readUInt() {
        int value = b.getInt();
        // assert value >= 0;
        return value;
    }

    public int readUShort() {
        return b.getShort() & 0xFFFF;
    }

    // returns byte form current position
    public byte readUByte() {
        return b.get();
    }

    public DexBuffer createCopy() {
        return new DexBuffer(b.duplicate());
    }

    public double readDouble(int nBytes) {
        long bits = readLong(nBytes);
        int bytesToMove = (8 - nBytes) * 8;
        bits <<= bytesToMove;
        return Double.longBitsToDouble(bits);
    }

    public void skip(int nBytes) {
        b.position(b.position() + nBytes);
    }
}