package org.infinispan.client.hotrod.impl.operations;

import java.io.InputStream;
import java.util.concurrent.atomic.AtomicInteger;

import org.infinispan.client.hotrod.VersionedMetadata;
import org.infinispan.client.hotrod.configuration.Configuration;
import org.infinispan.client.hotrod.impl.VersionedMetadataImpl;
import org.infinispan.client.hotrod.impl.protocol.Codec;
import org.infinispan.client.hotrod.impl.protocol.HeaderParams;
import org.infinispan.client.hotrod.impl.protocol.HotRodConstants;
import org.infinispan.client.hotrod.impl.transport.Transport;
import org.infinispan.client.hotrod.impl.transport.TransportFactory;

import net.jcip.annotations.Immutable;

/**
 * Streaming Get operation
 *
 * @author Tristan Tarrant
 * @since 9.0
 */
@Immutable
public class GetStreamOperation<T extends InputStream & VersionedMetadata> extends AbstractKeyOperation<T> {
   private final int offset;
   private boolean retryable;

   public GetStreamOperation(Codec codec, TransportFactory transportFactory,
                             Object key, byte[] keyBytes, int offset, byte[] cacheName, AtomicInteger topologyId, int flags, Configuration cfg) {
      super(codec, transportFactory, key, keyBytes, cacheName, topologyId, flags, cfg);
      this.offset = offset;
      retryable = true;
   }

   @Override
   public T executeOperation(Transport transport) {
      HeaderParams params = writeHeader(transport, GET_STREAM_REQUEST);
      transport.writeArray(keyBytes);
      transport.writeVInt(offset);
      transport.flush();
      short status = readHeaderAndValidate(transport, params);
      T result = null;
      if (HotRodConstants.isNotExist(status)) {
         result = null;
      } else {
         if (HotRodConstants.isSuccess(status)) {
            retryable = false;
            short flags = transport.readByte();
            long creation = -1;
            int lifespan = -1;
            long lastUsed = -1;
            int maxIdle = -1;
            if ((flags & INFINITE_LIFESPAN) != INFINITE_LIFESPAN) {
               creation = transport.readLong();
               lifespan = transport.readVInt();
            }
            if ((flags & INFINITE_MAXIDLE) != INFINITE_MAXIDLE) {
               lastUsed = transport.readLong();
               maxIdle = transport.readVInt();
            }
            long version = transport.readLong();
            transport.setBusy(true);
            result = codec.readAsStream(transport,
                  new VersionedMetadataImpl(creation, lifespan, lastUsed, maxIdle, version),
                  () -> {
                     transport.setBusy(false);
                     transport.getTransportFactory().releaseTransport(transport);
                  }
            );
         }
      }
      return result;
   }

   @Override
   protected boolean shouldRetry(int retryCount) {
      return retryable && super.shouldRetry(retryCount);
   }
}
