/*
 * 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.db.marshal;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.UUID;

import accord.utils.Invariants;

import org.apache.cassandra.db.Digest;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FastByteOperations;
import org.apache.cassandra.utils.Hex;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.UUIDGen;

public class ByteArrayAccessor implements ValueAccessor<byte[]>
{
    public static final ValueAccessor<byte[]> instance = new ByteArrayAccessor();
    public static final ObjectFactory<byte[]> factory = ByteArrayObjectFactory.instance;
    private static final byte[] EMPTY = new byte[0];

    private ByteArrayAccessor() {}

    @Override
    public int size(byte[] value)
    {
        return value.length;
    }

    @Override
    public byte[][] createArray(int length)
    {
        return new byte[length][];
    }

    @Override
    public void write(byte[] value, DataOutputPlus out) throws IOException
    {
        out.write(value);
    }

    @Override
    public void write(byte[] value, ByteBuffer out)
    {
        out.put(value);
    }

    @Override
    public <V2> int copyTo(byte[] src, int srcOffset, V2 dst, ValueAccessor<V2> dstAccessor, int dstOffset, int size)
    {
        dstAccessor.copyByteArrayTo(src, srcOffset, dst, dstOffset, size);
        return size;
    }

    @Override
    public int copyByteArrayTo(byte[] src, int srcOffset, byte[] dst, int dstOffset, int size)
    {
        FastByteOperations.copy(src, srcOffset, dst, dstOffset, size);
        return size;
    }

    @Override
    public int copyByteBufferTo(ByteBuffer src, int srcOffset, byte[] dst, int dstOffset, int size)
    {
        FastByteOperations.copy(src, src.position() + srcOffset, dst, dstOffset, size);
        return size;
    }

    @Override
    public void digest(byte[] value, int offset, int size, Digest digest)
    {
        digest.update(value, offset, size);
    }

    @Override
    public byte[] read(DataInputPlus in, int length) throws IOException
    {
        byte[] b = new byte[length];
        in.readFully(b);
        return b;
    }

    @Override
    public byte[] slice(byte[] input, int offset, int length)
    {
        Invariants.requireArgument(offset + length <= input.length);
        return Arrays.copyOfRange(input, offset, offset + length);
    }

    @Override
    public <V2> int compare(byte[] left, V2 right, ValueAccessor<V2> accessorR)
    {
        return accessorR.compareByteArrayTo(left, right);
    }

    @Override
    public int compareByteArrayTo(byte[] left, byte[] right)
    {
        return ByteArrayUtil.compareUnsigned(left, right);
    }

    @Override
    public int compareByteBufferTo(ByteBuffer left, byte[] right)
    {
        return ByteBufferUtil.compare(left, right);
    }

    @Override
    public ByteBuffer toBuffer(byte[] value)
    {
        if (value == null)
            return null;
        return ByteBuffer.wrap(value);
    }

    @Override
    public byte[] toArray(byte[] value)
    {
        return value;
    }

    @Override
    public byte[] toArray(byte[] value, int offset, int length)
    {
        if (value == null)
            return null;
        if (offset == 0 && length == value.length)
            return value;
        return slice(value, offset, length);
    }

    @Override
    public String toString(byte[] value, Charset charset) throws CharacterCodingException
    {
        return new String(value, charset);
    }

    @Override
    public String toHex(byte[] value)
    {
        return Hex.bytesToHex(value);
    }

    @Override
    public byte toByte(byte[] value)
    {
        return value[0];
    }

    @Override
    public byte getByte(byte[] value, int offset)
    {
        return value[offset];
    }

    @Override
    public short toShort(byte[] value)
    {
        return getShort(value, 0);
    }

    @Override
    public short getShort(byte[] value, int offset)
    {
        return ByteArrayUtil.getShort(value, offset);
    }

    @Override
    public int getUnsignedShort(byte[] value, int offset)
    {
        return ByteArrayUtil.getUnsignedShort(value, offset);
    }

    @Override
    public int toInt(byte[] value)
    {
        return getInt(value, 0);
    }

    @Override
    public int getInt(byte[] value, int offset)
    {
        return ByteArrayUtil.getInt(value, offset);
    }

    @Override
    public float getFloat(byte[] value, int offset)
    {
        return ByteArrayUtil.getFloat(value, offset);
    }

    @Override
    public double getDouble(byte[] value, int offset)
    {
        return ByteArrayUtil.getDouble(value, offset);
    }

    @Override
    public long toLong(byte[] value)
    {
        return getLong(value, 0);
    }

    @Override
    public long getLong(byte[] value, int offset)
    {
        return ByteArrayUtil.getLong(value, offset);
    }

    @Override
    public float toFloat(byte[] value)
    {
        return ByteArrayUtil.getFloat(value, 0);
    }

    @Override
    public float[] toFloatArray(byte[] value, int dimension)
    {
        float[] array = new float[dimension];
        int offset = 0;
        for (int i = 0; i < dimension; i++)
        {
            array[i] = getFloat(value, offset);
            offset += Float.BYTES;
        }
        return array;
    }

    @Override
    public double toDouble(byte[] value)
    {
        return ByteArrayUtil.getDouble(value, 0);
    }

    @Override
    public UUID toUUID(byte[] value)
    {
        return new UUID(getLong(value, 0), getLong(value, 8));
    }

    @Override
    public TimeUUID toTimeUUID(byte[] value)
    {
        return TimeUUID.fromBytes(getLong(value, 0), getLong(value, 8));
    }

    @Override
    public Ballot toBallot(byte[] value)
    {
        return Ballot.deserialize(value);
    }

    @Override
    public int putByte(byte[] dst, int offset, byte value)
    {
        dst[offset] = value;
        return TypeSizes.BYTE_SIZE;
    }

    @Override
    public int putShort(byte[] dst, int offset, short value)
    {
        ByteArrayUtil.putShort(dst, offset, value);
        return TypeSizes.SHORT_SIZE;
    }

    @Override
    public int putInt(byte[] dst, int offset, int value)
    {
        ByteArrayUtil.putInt(dst, offset, value);
        return TypeSizes.INT_SIZE;
    }

    @Override
    public int putLong(byte[] dst, int offset, long value)
    {
        ByteArrayUtil.putLong(dst, offset, value);
        return TypeSizes.LONG_SIZE;
    }

    @Override
    public int putFloat(byte[] dst, int offset, float value)
    {
        ByteArrayUtil.putFloat(dst, offset, value);
        return TypeSizes.FLOAT_SIZE;
    }

    @Override
    public byte[] empty()
    {
        return EMPTY;
    }

    @Override
    public byte[] valueOf(byte[] bytes)
    {
        return bytes;
    }

    @Override
    public byte[] valueOf(ByteBuffer bytes)
    {
        return ByteBufferUtil.getArray(bytes);
    }

    @Override
    public byte[] valueOf(String s, Charset charset)
    {
        return ByteArrayUtil.bytes(s, charset);
    }

    @Override
    public byte[] valueOf(UUID v)
    {
        return UUIDGen.decompose(v);
    }

    @Override
    public byte[] valueOf(boolean v)
    {
        return v ? new byte[] {1} : new byte[] {0};
    }

    @Override
    public byte[] valueOf(byte v)
    {
        return ByteArrayUtil.bytes(v);
    }

    @Override
    public byte[] valueOf(short v)
    {
        return ByteArrayUtil.bytes(v);
    }

    @Override
    public byte[] valueOf(int v)
    {
        return ByteArrayUtil.bytes(v);
    }

    @Override
    public byte[] valueOf(long v)
    {
        return ByteArrayUtil.bytes(v);
    }

    @Override
    public byte[] valueOf(float v)
    {
        return ByteArrayUtil.bytes(v);
    }

    @Override
    public byte[] valueOf(double v)
    {
        return ByteArrayUtil.bytes(v);
    }

    @Override
    public <V2> byte[] convert(V2 src, ValueAccessor<V2> accessor)
    {
        return accessor.toArray(src);
    }

    @Override
    public byte[] allocate(int size)
    {
        return new byte[size];
    }

    @Override
    public ObjectFactory<byte[]> factory()
    {
        return factory;
    }
}
