package com.atlassian.bitbucket.mesh.rpc;

import com.atlassian.bitbucket.mesh.rpc.v1.RpcMarshallerType;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcProtos;
import com.google.protobuf.*;
import io.grpc.MethodDescriptor;
import io.grpc.protobuf.ProtoMethodDescriptorSupplier;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;

public class ZeroCopyUtils {

    private ZeroCopyUtils() {
        throw new UnsupportedOperationException("Do not instantiate ZeroCopyUtils");
    }

    @SuppressWarnings("unchecked")
    public static <T> T deepCopy(@Nullable T value) {
        if (value instanceof MessageLite) {
            MessageLite message = (MessageLite) value;
            try {
                return (T) message.getParserForType().parseFrom(message.toByteArray());
            } catch (InvalidProtocolBufferException e) {
                throw new IllegalArgumentException("Not a valid MessageLite message", e);
            }
        }
        if (value instanceof ByteString) {
            return (T) ByteString.copyFrom(((ByteString) value).toByteArray());
        }
        if (value instanceof Optional) {
            return (T) ((Optional<?>) value).map(ZeroCopyUtils::deepCopy);
        }
        return value;
    }

    @Nonnull
    public static RpcMarshallerType getMarshallerType(@Nonnull MethodDescriptor<?, ?> descriptor) {
        Object schemaDescriptor = descriptor.getSchemaDescriptor();
        if (schemaDescriptor instanceof ProtoMethodDescriptorSupplier) {
            return getMarshallerType(((ProtoMethodDescriptorSupplier) schemaDescriptor).getMethodDescriptor());
        }
        return RpcMarshallerType.MARSHALLER_UNSPECIFIED;
    }

    @Nonnull
    public static RpcMarshallerType getMarshallerType(@Nonnull Descriptors.MethodDescriptor methodDescriptor) {
        DescriptorProtos.MethodOptions methodOptions = methodDescriptor.getOptions();
        if (methodOptions.hasExtension(RpcProtos.methodMarshaller)) {
            return methodOptions.getExtension(RpcProtos.methodMarshaller);
        }

        DescriptorProtos.ServiceOptions serviceOptions = methodDescriptor.getService().getOptions();
        if (serviceOptions.hasExtension(RpcProtos.serviceMarshaller)) {
            return serviceOptions.getExtension(RpcProtos.serviceMarshaller);
        }
        return RpcMarshallerType.MARSHALLER_UNSPECIFIED;
    }
}
