/*
 * Copyright (C) 2014 Indeed 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.indeed.imhotep;

import com.google.common.base.Throwables;

/**
* @author jsadun
*/
public final class MemoryReservationContext extends MemoryReserver {
    private final MemoryReserver memoryReserver;

    private long reservationSize = 0;

    private boolean closed = false;

    public MemoryReservationContext(MemoryReserver memoryReserver) {
        this.memoryReserver = memoryReserver;
    }

    public long usedMemory() {
        return reservationSize;
    }

    public long totalMemory() {
        return memoryReserver.totalMemory();
    }

    public synchronized boolean claimMemory(long numBytes) {
        if (closed) throw new IllegalStateException("cannot allocate memory after reservation context has been closed");
        if (memoryReserver.claimMemory(numBytes)) {
            reservationSize += numBytes;
            return true;
        }
        return false;
    }

    public synchronized void releaseMemory(long numBytes) {
        if (closed) throw new IllegalStateException("cannot free memory after reservation context has been closed");
        if (numBytes > reservationSize) {
            throw new IllegalArgumentException("trying to free too many bytes: " + numBytes + ", current size: " + (reservationSize));
        }
        reservationSize -= numBytes;
        try {
            memoryReserver.releaseMemory(numBytes);
        } catch (Exception e) {
            reservationSize += numBytes;
            throw Throwables.propagate(e);
        }
    }

    public synchronized void hoist(long numBytes) {
        if (closed) throw new IllegalStateException("cannot hoist memory after reservation context has been closed");
        if (numBytes > reservationSize) {
            throw new IllegalArgumentException("trying to hoist too many bytes: " + numBytes + ", current size: " + reservationSize);
        }
        reservationSize -= numBytes;
    }

    public synchronized void dehoist(long numBytes) {
        if (closed) throw new IllegalStateException("cannot dehoist memory after reservation context has been closed");
        reservationSize += numBytes;
    }

    @Override
    public synchronized void close() {
        if (reservationSize > 0) {
            memoryReserver.releaseMemory(reservationSize);
            reservationSize = 0;
        }
        closed = true;
    }
}
