/*
 * 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.jackrabbit.oak.commons;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.zip.CRC32;

/**
 * This is a wrapper around {@link ByteBuffer}. It maintains the same semantics
 * and mechanisms of the {@link ByteBuffer}.
 * <p>
 * Java 9 introduced API changes to some methods in {@link ByteBuffer}. Instead
 * of returning instances of {@link java.nio.Buffer Buffer}, those methods were
 * rewritten to return instances of {@link ByteBuffer} instead. While this is
 * perfectly fine at compile time, running "modern" code on Java 8 and earlier
 * throws {@link NoSuchMethodError}. In order to prevent occurrences of this
 * exceptions in the future, {@link Buffer} is used consistently in place of
 * {@link ByteBuffer}. Since it is not possible to directly convert a {@link
 * Buffer} into a {@link ByteBuffer} and the other way around, {@link Buffer}
 * makes it less likely to develop dangerous code in the future.
 */
final public class Buffer {

    private final ByteBuffer buffer;

    private Buffer(ByteBuffer buffer) {
        this.buffer = buffer;
    }

    public static Buffer map(FileChannel channel, MapMode mode, long position, long size) throws IOException {
        return new Buffer(channel.map(mode, position, size));
    }

    public static Buffer wrap(byte[] buffer) {
        return new Buffer(ByteBuffer.wrap(buffer));
    }

    public static Buffer wrap(byte[] buffer, int pos, int len) {
        return new Buffer(ByteBuffer.wrap(buffer, pos, len));
    }

    public static Buffer allocate(int cap) {
        return new Buffer(ByteBuffer.allocate(cap));
    }

    public static Buffer allocateDirect(int cap) {
        return new Buffer(ByteBuffer.allocateDirect(cap));
    }

    public int remaining() {
        return buffer.remaining();
    }

    public Buffer asReadOnlyBuffer() {
        return new Buffer(buffer.asReadOnlyBuffer());
    }

    public Buffer position(int pos) {
        ((java.nio.Buffer) buffer).position(pos);
        return this;
    }

    public int position() {
        return buffer.position();
    }

    public Buffer limit(int lim) {
        ((java.nio.Buffer) buffer).limit(lim);
        return this;
    }

    public int limit() {
        return buffer.limit();
    }

    public Buffer slice() {
        return new Buffer(buffer.slice());
    }

    public int readFully(FileChannel channel, int position) throws IOException {
        int result = 0;
        while (buffer.remaining() > 0) {
            int count = channel.read(buffer, position);
            if (count < 0) {
                break;
            }
            result += count;
            position += count;
        }
        return result;
    }

    public Buffer flip() {
        ((java.nio.Buffer) buffer).flip();
        return this;
    }

    public int getInt() {
        return buffer.getInt();
    }

    public int getInt(int pos) {
        return buffer.getInt(pos);
    }

    public Buffer mark() {
        ((java.nio.Buffer) buffer).mark();
        return this;
    }

    public Buffer get(byte[] b) {
        buffer.get(b);
        return this;
    }

    public Buffer get(byte[] b, int pos, int len) {
        buffer.get(b, pos, len);
        return this;
    }

    public byte get(int pos) {
        return buffer.get(pos);
    }

    public byte get() {
        return buffer.get();
    }

    public Buffer reset() {
        ((java.nio.Buffer) buffer).reset();
        return this;
    }

    public void update(CRC32 checksum) {
        checksum.update(buffer);
    }

    public byte[] array() {
        return buffer.array();
    }

    public int capacity() {
        return buffer.capacity();
    }

    public boolean isDirect() {
        return buffer.isDirect();
    }

    public Buffer put(byte[] b) {
        buffer.put(b);
        return this;
    }

    public Buffer put(byte[] buf, int pos, int len) {
        buffer.put(buf, pos, len);
        return this;
    }

    public Buffer put(byte b) {
        buffer.put(b);
        return this;
    }

    public Buffer put(Buffer b) {
        buffer.put(b.buffer);
        return this;
    }

    public Buffer rewind() {
        ((java.nio.Buffer) buffer).rewind();
        return this;
    }

    public long getLong(int pos) {
        return buffer.getLong(pos);
    }

    public long getLong() {
        return buffer.getLong();
    }

    public short getShort(int pos) {
        return buffer.getShort(pos);
    }

    public Buffer duplicate() {
        return new Buffer(buffer.duplicate());
    }

    public CharBuffer decode(Charset charset) {
        return charset.decode(buffer);
    }

    public boolean hasRemaining() {
        return buffer.hasRemaining();
    }

    public int write(WritableByteChannel channel) throws IOException {
        return channel.write(buffer);
    }

    public Buffer putInt(int i) {
        buffer.putInt(i);
        return this;
    }

    public Buffer putLong(long l) {
        buffer.putLong(l);
        return this;
    }

    @Override
    public int hashCode() {
        return buffer.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj instanceof Buffer) {
            return buffer.equals(((Buffer) obj).buffer);
        }
        return false;
    }

}
