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

package org.apache.ignite.internal.portable.streams;

import org.apache.ignite.internal.util.GridUnsafe;
import org.apache.ignite.internal.util.typedef.internal.U;
import sun.misc.Unsafe;

import static org.apache.ignite.IgniteSystemProperties.IGNITE_MARSHAL_BUFFERS_RECHECK;

/**
 * Memory allocator chunk.
 */
public class PortableMemoryAllocatorChunk {
    /** Unsafe instance. */
    protected static final Unsafe UNSAFE = GridUnsafe.unsafe();

    /** Array offset: byte. */
    protected static final long BYTE_ARR_OFF = UNSAFE.arrayBaseOffset(byte[].class);

    /** Buffer size re-check frequency. */
    private static final Long CHECK_FREQ = Long.getLong(IGNITE_MARSHAL_BUFFERS_RECHECK, 10000);

    /** Data array */
    private byte[] data;

    /** Max message size detected between checks. */
    private int maxMsgSize;

    /** Last time array size is checked. */
    private long lastCheck = U.currentTimeMillis();

    /** Whether the holder is acquired or not. */
    private boolean acquired;

    /**
     * Allocate.
     *
     * @param size Desired size.
     * @return Data.
     */
    public byte[] allocate(int size) {
        if (acquired)
            return new byte[size];

        acquired = true;

        if (data == null || size > data.length)
            data = new byte[size];

        return data;
    }

    /**
     * Reallocate.
     *
     * @param data Old data.
     * @param size Size.
     * @return New data.
     */
    public byte[] reallocate(byte[] data, int size) {
        byte[] newData = new byte[size];

        if (this.data == data)
            this.data = newData;

        UNSAFE.copyMemory(data, BYTE_ARR_OFF, newData, BYTE_ARR_OFF, data.length);

        return newData;
    }

    /**
     * Shrinks array size if needed.
     */
    public void release(byte[] data, int maxMsgSize) {
        if (this.data != data)
            return;

        if (maxMsgSize > this.maxMsgSize)
            this.maxMsgSize = maxMsgSize;

        this.acquired = false;

        long now = U.currentTimeMillis();

        if (now - this.lastCheck >= CHECK_FREQ) {
            int halfSize = data.length >> 1;

            if (this.maxMsgSize < halfSize)
                this.data = new byte[halfSize];

            this.lastCheck = now;
        }
    }

    /**
     * @return {@code True} if acquired.
     */
    public boolean isAcquired() {
        return acquired;
    }
}
