package com.atlassian.bitbucket.mesh.rpc.util;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;
import com.google.protobuf.UnsafeByteOperations;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;

public class ByteStringUtils {

    private ByteStringUtils() {
        throw new UnsupportedOperationException();
    }

    @Nonnull
    public static ByteString copyFrom(@Nonnull byte[] buffer, int offset, int length) {
        requireNonNull(buffer, "buffer");

        if (length == 0) {
            return ByteString.EMPTY;
        }
        return ByteString.copyFrom(buffer, offset, length);
    }

    @Nullable
    public static String emptyToNull(@Nonnull ByteString value) {
        return requireNonNull(value, "value").isEmpty() ? null : value.toStringUtf8();
    }

    @Nullable
    public static String emptyToNull(@Nullable String value) {
        return value == null || value.isEmpty() ? null : value;
    }

    @Nonnull
    public static ByteString nullToEmpty(@Nullable String value) {
        return value == null || value.isEmpty() ? ByteString.EMPTY : ByteString.copyFromUtf8(value);
    }

    //Some code may be taking a List (either received from Protobuf or to send to Protobuf) and handing
    //it into another method that will immediately iterate (and perhaps collect) it. Rather than doing
    //that effectively twice, these toLazy* helpers use Guava's transforms to return a lazy view that
    //transforms on access. If a caller is going to iterate the results multiple times, a non-lazy helper
    //should be used instead.

    @Nonnull
    public static Iterable<ByteString> toByteStringsLazily(@Nonnull Iterable<String> values) {
        return Iterables.transform(requireNonNull(values, "values"), ByteString::copyFromUtf8);
    }

    @Nonnull
    public static List<ByteString> toByteStringsLazily(@Nonnull List<String> values) {
        return Lists.transform(requireNonNull(values, "values"), ByteString::copyFromUtf8);
    }

    //Note that Protobuf does not allow bytes as a map key, so Map<ByteString, ...> is not possible
    @Nonnull
    public static Map<String, ByteString> toByteStringsLazily(@Nonnull Map<String, String> values) {
        return Maps.transformValues(requireNonNull(values, "values"), ByteString::copyFromUtf8);
    }

    @Nonnull
    public static List<String> toStringsLazily(@Nonnull List<ByteString> values) {
        return Lists.transform(requireNonNull(values, "values"), ByteString::toStringUtf8);
    }

    //Note that Protobuf does not allow bytes as a map key, so Map<ByteString, ...> is not possible
    @Nonnull
    public static Map<String, String> toStringsLazily(@Nonnull Map<String, ByteString> values) {
        return Maps.transformValues(requireNonNull(values, "values"), ByteString::toStringUtf8);
    }

    @Nonnull
    public static List<String> toStrings(@Nonnull List<ByteString> values) {
        return requireNonNull(values, "values").stream()
                .map(ByteString::toStringUtf8)
                .collect(toImmutableList());
    }

    @Nonnull
    public static ByteString unsafeWrap(@Nonnull ByteBuffer buffer) {
        if (requireNonNull(buffer, "buffer").hasRemaining()) {
            ByteString bytes = UnsafeByteOperations.unsafeWrap(buffer); //This slice()s internally, so...
            buffer.position(buffer.position() + buffer.remaining()); //...we need to "consume" the data manually

            return bytes;
        }

        return ByteString.EMPTY;
    }

    @Nonnull
    public static ByteString unsafeWrap(@Nonnull byte[] buffer) {
        return unsafeWrap(buffer, 0, buffer.length);
    }

    @Nonnull
    public static ByteString unsafeWrap(@Nonnull byte[] buffer, int offset, int length) {
        requireNonNull(buffer, "buffer");

        if (length == 0) {
            return ByteString.EMPTY;
        }
        return UnsafeByteOperations.unsafeWrap(buffer, offset, length);
    }
}
