/*
* 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.cassandra.io.util;

import net.nicoulaj.compilecommand.annotations.Inline;

import org.apache.cassandra.utils.concurrent.Ref;
import org.apache.cassandra.utils.concurrent.RefCounted;
import org.apache.cassandra.utils.concurrent.SharedCloseable;
import org.apache.cassandra.utils.memory.MemoryUtil;

public class SafeMemory extends Memory implements SharedCloseable
{
    private final Ref<?> ref;
    public SafeMemory(long size)
    {
        super(size);
        ref = new Ref<>(null, new MemoryTidy(peer, size));
    }

    private SafeMemory(SafeMemory copyOf)
    {
        super(copyOf);
        ref = copyOf.ref.ref();
        /** see {@link Memory#Memory(long)} re: null pointers*/
        if (peer == 0 && size != 0)
        {
            ref.ensureReleased();
            throw new IllegalStateException("Cannot create a sharedCopy of a SafeMemory object that has already been closed");
        }
    }

    public SafeMemory sharedCopy()
    {
        return new SafeMemory(this);
    }

    public void free()
    {
        ref.release();
        peer = 0;
    }

    public void close()
    {
        ref.ensureReleased();
        peer = 0;
    }

    public Throwable close(Throwable accumulate)
    {
        return ref.ensureReleased(accumulate);
    }

    public SafeMemory copy(long newSize)
    {
        SafeMemory copy = new SafeMemory(newSize);
        copy.put(0, this, 0, Math.min(size(), newSize));
        return copy;
    }

    private static final class MemoryTidy implements RefCounted.Tidy
    {
        final long peer;
        final long size;
        private MemoryTidy(long peer, long size)
        {
            this.peer = peer;
            this.size = size;
        }

        public void tidy()
        {
            /** see {@link Memory#Memory(long)} re: null pointers*/
            if (peer != 0)
                MemoryUtil.free(peer);
        }

        public String name()
        {
            return Memory.toString(peer, size);
        }
    }

    @Inline
    protected void checkBounds(long start, long end)
    {
        assert peer != 0 || size == 0 : ref.printDebugInfo();
        super.checkBounds(start, end);
    }

    public void addTo(Ref.IdentityCollection identities)
    {
        identities.add(ref);
    }
}
