/*
 * Copyright (C) 2008 Google Inc.
 *
 * 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 com.android.tools.perflib.heap;

import com.android.annotations.NonNull;
import com.android.tools.perflib.captures.DataBuffer;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class ArrayInstance extends Instance {

    private final Type mType;

    private final int mLength;

    private final long mValuesOffset;

    public ArrayInstance(long id, @NonNull StackTrace stack, @NonNull Type type, int length,
            long valuesOffset) {
        super(id, stack);
        mType = type;
        mLength = length;
        mValuesOffset = valuesOffset;
    }

    @NonNull
    public Object[] getValues() {
        Object[] values = new Object[mLength];

        getBuffer().setPosition(mValuesOffset);
        for (int i = 0; i < mLength; i++) {
            values[i] = readValue(mType);
        }
        return values;
    }

    @NonNull
    public byte[] asRawByteArray(int start, int elementCount) {
        getBuffer().setPosition(mValuesOffset);
        assert mType != Type.OBJECT;
        assert start + elementCount <= mLength;
        byte[] bytes = new byte[elementCount * mType.getSize()];
        getBuffer().readSubSequence(bytes, start * mType.getSize(), elementCount * mType.getSize());
        return bytes;
    }

    @NonNull
    public char[] asCharArray(int offset, int length) {
        assert mType == Type.CHAR;
        // TODO: Make this copy less by supporting offset in asRawByteArray.
        CharBuffer charBuffer = ByteBuffer.wrap(asRawByteArray(offset, length)).order(
                DataBuffer.HPROF_BYTE_ORDER).asCharBuffer();
        char[] result = new char[length];
        charBuffer.get(result);
        return result;
    }

    @Override
    public final int getSize() {
        // TODO: Take the rest of the fields into account: length, type, etc (~16 bytes).
        return mLength * mHeap.mSnapshot.getTypeSize(mType);
    }

    @Override
    public final void resolveReferences() {
        if (mType == Type.OBJECT) {
            for (Object value : getValues()) {
                if (value instanceof Instance) {
                    ((Instance)value).addReverseReference(null, this);
                    mHardForwardReferences.add((Instance)value);
                }
            }
        }
    }

    @Override
    public final void accept(@NonNull Visitor visitor) {
        visitor.visitArrayInstance(this);
        for (Instance instance : mHardForwardReferences) {
            visitor.visitLater(this, instance);
        }
    }

    @Override
    public ClassObj getClassObj() {
        if (mType == Type.OBJECT) {
            return super.getClassObj();
        } else {
            // Primitive arrays don't set their classId, we need to do the lookup manually.
            return mHeap.mSnapshot.findClass(Type.getClassNameOfPrimitiveArray(mType));
        }
    }

    /**
     * Returns the number of elements in the array.
     */
    public int getLength() {
        return mLength;
    }

    public Type getArrayType() {
        return mType;
    }

    public final String toString() {
        String className = getClassObj().getClassName();
        if (className.endsWith("[]")) {
            className = className.substring(0, className.length() - 2);
        }
        return String.format("%s[%d]@%d (0x%x)", className, mLength, getUniqueId(), getUniqueId());
    }
}
