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

import java.io.IOException;
import java.io.InputStream;

import org.infinispan.client.hotrod.VersionedMetadata;
import org.infinispan.client.hotrod.impl.transport.Transport;
import org.infinispan.client.hotrod.impl.transport.tcp.TcpTransport;

import net.jcip.annotations.NotThreadSafe;

/**
 * Implements an {@link InputStream} around a {@link Transport}.
 * The transport is marked as <b>busy</b> so that it won't be released automatically
 *
 * @author Tristan Tarrant
 * @since 9.0
 */
@NotThreadSafe
class TransportInputStream extends InputStream implements VersionedMetadata {
   private final Transport transport;
   private final int totalLength;
   private final Runnable afterClose;
   private final VersionedMetadata versionedMetadata;
   private int totalPosition;
   private byte[] buffer;
   private int bufferPosition;
   private int bufferLimit;

   TransportInputStream(Transport transport, VersionedMetadata versionedMetadata, Runnable afterClose) {
      this.transport = transport;
      totalLength = transport.readVInt();
      buffer = new byte[TcpTransport.SOCKET_STREAM_BUFFER];
      this.versionedMetadata = versionedMetadata;
      this.afterClose = afterClose;
   }

   @Override
   public long getVersion() {
      return versionedMetadata.getVersion();
   }

   @Override
   public long getCreated() {
      return versionedMetadata.getCreated();
   }

   @Override
   public int getLifespan() {
      return versionedMetadata.getLifespan();
   }

   @Override
   public long getLastUsed() {
      return versionedMetadata.getLastUsed();
   }

   @Override
   public int getMaxIdle() {
      return versionedMetadata.getMaxIdle();
   }

   private void fill() {
      bufferLimit = Math.min(TcpTransport.SOCKET_STREAM_BUFFER, totalLength - totalPosition);
      if (bufferLimit > 0) {
         transport.readByteArray(buffer, bufferLimit);
         totalPosition += bufferLimit;
      }
      bufferPosition = 0;
   }

   @Override
   public int read() throws IOException {
      if (bufferPosition >= bufferLimit) {
         fill();
         if (bufferPosition >= bufferLimit)
            return -1;
      }
      return buffer[bufferPosition++] & 0xff;
   }

   private int read1(byte[] b, int off, int len) {
      int available = bufferLimit - bufferPosition;
      if (available <= 0) {
         fill();
         available = bufferLimit - bufferPosition;
         if (available <= 0) return -1;
      }
      int amount = (available < len) ? available : len;
      System.arraycopy(buffer, bufferPosition, b, off, amount);
      bufferPosition += amount;
      return amount;
   }

   @Override
   public synchronized int read(byte b[], int off, int len)
         throws IOException
   {
      if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
         throw new IndexOutOfBoundsException();
      } else if (len == 0) {
         return 0;
      }

      int n = 0;
      for (;;) {
         int nread = read1(b, off + n, len - n);
         if (nread <= 0)
            return (n == 0) ? nread : n;
         n += nread;
         if (n >= len)
            return n;
      }
   }

   @Override
   public void close() throws IOException {
      super.close();
      if (afterClose != null) {
         afterClose.run();
      }
   }
}
