/* Copyright 2015 Google Inc. All Rights Reserved.

Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
namespace Org.Brotli.Dec
{
	/// <summary>
	/// <see cref="System.IO.Stream"/>
	/// decorator that decompresses brotli data.
	/// <p> Not thread-safe.
	/// </summary>
	public class BrotliInputStream : System.IO.Stream
	{
		public const int DefaultInternalBufferSize = 16384;

		/// <summary>Internal buffer used for efficient byte-by-byte reading.</summary>
		private byte[] buffer;

		/// <summary>Number of decoded but still unused bytes in internal buffer.</summary>
		private int remainingBufferBytes;

		/// <summary>Next unused byte offset.</summary>
		private int bufferOffset;

		/// <summary>Decoder state.</summary>
		private readonly Org.Brotli.Dec.State state = new Org.Brotli.Dec.State();

		/// <summary>
		/// Creates a
		/// <see cref="System.IO.Stream"/>
		/// wrapper that decompresses brotli data.
		/// <p> For byte-by-byte reading (
		/// <see cref="ReadByte()"/>
		/// ) internal buffer with
		/// <see cref="DefaultInternalBufferSize"/>
		/// size is allocated and used.
		/// <p> Will block the thread until first kilobyte of data of source is available.
		/// </summary>
		/// <param name="source">underlying data source</param>
		/// <exception cref="System.IO.IOException">in case of corrupted data or source stream problems</exception>
		public BrotliInputStream(System.IO.Stream source)
			: this(source, DefaultInternalBufferSize, null)
		{
		}

		/// <summary>
		/// Creates a
		/// <see cref="System.IO.Stream"/>
		/// wrapper that decompresses brotli data.
		/// <p> For byte-by-byte reading (
		/// <see cref="ReadByte()"/>
		/// ) internal buffer of specified size is
		/// allocated and used.
		/// <p> Will block the thread until first kilobyte of data of source is available.
		/// </summary>
		/// <param name="source">compressed data source</param>
		/// <param name="byteReadBufferSize">
		/// size of internal buffer used in case of
		/// byte-by-byte reading
		/// </param>
		/// <exception cref="System.IO.IOException">in case of corrupted data or source stream problems</exception>
		public BrotliInputStream(System.IO.Stream source, int byteReadBufferSize)
			: this(source, byteReadBufferSize, null)
		{
		}

		/// <summary>
		/// Creates a
		/// <see cref="System.IO.Stream"/>
		/// wrapper that decompresses brotli data.
		/// <p> For byte-by-byte reading (
		/// <see cref="ReadByte()"/>
		/// ) internal buffer of specified size is
		/// allocated and used.
		/// <p> Will block the thread until first kilobyte of data of source is available.
		/// </summary>
		/// <param name="source">compressed data source</param>
		/// <param name="byteReadBufferSize">
		/// size of internal buffer used in case of
		/// byte-by-byte reading
		/// </param>
		/// <param name="customDictionary">
		/// custom dictionary data;
		/// <see langword="null"/>
		/// if not used
		/// </param>
		/// <exception cref="System.IO.IOException">in case of corrupted data or source stream problems</exception>
		public BrotliInputStream(System.IO.Stream source, int byteReadBufferSize, byte[] customDictionary)
		{
			if (byteReadBufferSize <= 0)
			{
				throw new System.ArgumentException("Bad buffer size:" + byteReadBufferSize);
			}
			else if (source == null)
			{
				throw new System.ArgumentException("source is null");
			}
			this.buffer = new byte[byteReadBufferSize];
			this.remainingBufferBytes = 0;
			this.bufferOffset = 0;
			try
			{
				Org.Brotli.Dec.State.SetInput(state, source);
			}
			catch (Org.Brotli.Dec.BrotliRuntimeException ex)
			{
				throw new System.IO.IOException("Brotli decoder initialization failed", ex);
			}
			if (customDictionary != null)
			{
				Org.Brotli.Dec.Decode.SetCustomDictionary(state, customDictionary);
			}
		}

		/// <summary><inheritDoc/></summary>
		/// <exception cref="System.IO.IOException"/>
		public override void Close()
		{
			Org.Brotli.Dec.State.Close(state);
		}

		/// <summary><inheritDoc/></summary>
		/// <exception cref="System.IO.IOException"/>
		public override int ReadByte()
		{
			if (bufferOffset >= remainingBufferBytes)
			{
				remainingBufferBytes = Read(buffer, 0, buffer.Length);
				bufferOffset = 0;
				if (remainingBufferBytes == -1)
				{
					return -1;
				}
			}
			return buffer[bufferOffset++] & unchecked((int)(0xFF));
		}

		/// <summary><inheritDoc/></summary>
		/// <exception cref="System.IO.IOException"/>
		public override int Read(byte[] destBuffer, int destOffset, int destLen)
		{
			if (destOffset < 0)
			{
				throw new System.ArgumentException("Bad offset: " + destOffset);
			}
			else if (destLen < 0)
			{
				throw new System.ArgumentException("Bad length: " + destLen);
			}
			else if (destOffset + destLen > destBuffer.Length)
			{
				throw new System.ArgumentException("Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.Length);
			}
			else if (destLen == 0)
			{
				return 0;
			}
			int copyLen = System.Math.Max(remainingBufferBytes - bufferOffset, 0);
			if (copyLen != 0)
			{
				copyLen = System.Math.Min(copyLen, destLen);
				System.Array.Copy(buffer, bufferOffset, destBuffer, destOffset, copyLen);
				bufferOffset += copyLen;
				destOffset += copyLen;
				destLen -= copyLen;
				if (destLen == 0)
				{
					return copyLen;
				}
			}
			try
			{
				state.output = destBuffer;
				state.outputOffset = destOffset;
				state.outputLength = destLen;
				state.outputUsed = 0;
				Org.Brotli.Dec.Decode.Decompress(state);
				if (state.outputUsed == 0)
				{
					return 0;
				}
				return state.outputUsed + copyLen;
			}
			catch (Org.Brotli.Dec.BrotliRuntimeException ex)
			{
				throw new System.IO.IOException("Brotli stream decoding failed", ex);
			}
		}
		// <{[INJECTED CODE]}>
		public override bool CanRead {
			get {return true;}
		}

		public override bool CanSeek {
			get {return false;}
		}
		public override long Length {
			get {throw new System.NotSupportedException();}
		}
		public override long Position {
			get {throw new System.NotSupportedException();}
			set {throw new System.NotSupportedException();}
		}
		public override long Seek(long offset, System.IO.SeekOrigin origin) {
			throw new System.NotSupportedException();
		}
		public override void SetLength(long value){
			throw new System.NotSupportedException();
		}

		public override bool CanWrite{get{return false;}}
		public override System.IAsyncResult BeginWrite(byte[] buffer, int offset,
				int count, System.AsyncCallback callback, object state) {
			throw new System.NotSupportedException();
		}
		public override void Write(byte[] buffer, int offset, int count) {
			throw new System.NotSupportedException();
		}

		public override void Flush() {}
	}
}
