package netflix.karyon.transport.util;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observer;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Nitesh Kant
 */
public class HttpContentInputStream extends InputStream {

    private static final Logger logger = LoggerFactory.getLogger(HttpContentInputStream.class);

    private final Lock lock = new ReentrantLock();
    private volatile boolean isClosed = false;

    private volatile boolean isCompleted = false;
    private volatile Throwable completedWithError = null;
    private final Condition contentAvailabilityMonitor = lock.newCondition();
    private final ByteBuf contentBuffer;

    public HttpContentInputStream(final ByteBufAllocator allocator, final Observable<ByteBuf> content) {
        contentBuffer = allocator.buffer();
        content.subscribe(new Observer<ByteBuf>() {
            @Override
            public void onCompleted() {
                lock.lock();
                try {
                    isCompleted = true;
                  
                    logger.debug( "Processing complete" );
                    contentAvailabilityMonitor.signalAll();
                }
                finally {
                    lock.unlock();
                }
            }

            @Override
            public void onError(Throwable e) {
                lock.lock();
                try {
                    completedWithError = e;
                    isCompleted = true;
                  
                    logger.error("Observer, got error: " + e.getMessage());
                    contentAvailabilityMonitor.signalAll();
                }
                finally {
                    lock.unlock();
                }
            }

            @Override
            public void onNext(ByteBuf byteBuf) {
                lock.lock();
                try {
                    contentBuffer.writeBytes( byteBuf );
                  
                    contentAvailabilityMonitor.signalAll();
                }
                catch( Exception e ) {
                  logger.error("Error on server", e);
                }
                finally {
                    lock.unlock();
                }
            }
        });
    }

    @Override
    public int available() throws IOException {
        return isCompleted ? contentBuffer.readableBytes() : 0;
    }

    @Override
    public void mark(int readlimit) {
        contentBuffer.markReaderIndex();
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public int read() throws IOException {
        lock.lock();
        
        try {
            if( !await() ) {
              return -1;
            }
          
            return contentBuffer.readByte() & 0xff;
        }
        finally {
            lock.unlock();
        }
    }

    @Override
    public int read(final byte[] b, final int off, final int len) throws IOException {
        if( b == null ) {
          throw new NullPointerException( "Null buffer" );
        }
      
        if( len < 0 || off < 0 || len > b.length - off ) {
          throw new IndexOutOfBoundsException( "Invalid index" ); 
        }
      
        if( len == 0 ) {
          return 0;
        }
      
        lock.lock();

        try {
          if( !await() ) {
            return -1;
          }

          int size = Math.min( len, contentBuffer.readableBytes() );
        
          contentBuffer.readBytes( b, off, size );
        
          return size;
        }
        finally {
          lock.unlock();
        }
    }

    @Override
    public void close() throws IOException {
        //we need a sync block here, as the double close sometimes is reality and we want to decrement ref. counter only once
        synchronized ( this ) {
            if ( isClosed ) {
                return;
            }
          
            isClosed = true;
        }
      
        contentBuffer.release();
    }
    
    @Override
    public void reset() throws IOException {
        contentBuffer.resetReaderIndex();
    }

    @Override
    public long skip(long n) throws IOException {
        //per InputStream contract, if n is negative, 0 bytes are skipped
        if( n <= 0 ) {
            return 0;
        }
      
        if (n > Integer.MAX_VALUE) {
            return skipBytes(Integer.MAX_VALUE);
        } else {
            return skipBytes((int) n);
        }
    }

    private int skipBytes(int n) throws IOException {
        int nBytes = Math.min(available(), n);
        contentBuffer.skipBytes(nBytes);
        return nBytes;
    }
    
    private boolean await() throws IOException {
      //await in here
      while( !isCompleted && !contentBuffer.isReadable() ) {
          try {
              contentAvailabilityMonitor.await();
          } 
          catch (InterruptedException e) {
              // Restore interrupt status and bailout
              Thread.currentThread().interrupt();
            
              logger.error("Interrupted: " + e.getMessage());
              throw new IOException( e );
          }
      }

      if( completedWithError != null ) {
          throw new IOException( completedWithError );
      }

      if( isCompleted && !contentBuffer.isReadable() ) {
          return false;
      }
      
      return true;
  }
}
@@ -2119,32 +2119,639 @@
                 
+%0A                    //This is not only to stop writing 0 bytes as it might seems%0A                    //In case of no payload request, like GET%0A                    //We are getting onNext( 0 bytes ), onComplete during the stress conditions%0A                    //AFTER we say subscriber.onCompleted() and tiered down and close%0A                    //request stream, this doesn't contradict logic, in fact 0 bytes is just the same as nothing to write%0A                    //but that save us annoying log record every time this happens%0A                    if( byteBuf.readableBytes() %3E 0 ) %7B%0A                    
   contentBuffer.
@@ -2769,24 +2769,46 @@
  byteBuf );%0A
+                    %7D%0A
             

fix to remove annoying message ( as it was introduced in previous PR ) under the stress conditions we are getting onNext( 0 bytes ), onComplete AFTER we say subscriber.onCompleted() and tiered down and close request stream.