using System;
namespace ICSharpCode.SharpZipLib.Zip.Compression
{
	/// 
	/// This class is general purpose class for writing data to a buffer.
	/// 
	/// It allows you to write bits as well as bytes
	/// Based on DeflaterPending.java
	/// 
	/// author of the original java version : Jochen Hoenicke
	/// 
	public class PendingBuffer
	{
		#region Instance Fields
		/// 
		/// Internal work buffer
		/// 
		readonly byte[] buffer;
		int start;
		int end;
		uint bits;
		int bitCount;
		#endregion
		#region Constructors
		/// 
		/// construct instance using default buffer size of 4096
		/// 
		public PendingBuffer() : this(4096)
		{
		}
		/// 
		/// construct instance using specified buffer size
		/// 
		/// 
		/// size to use for internal buffer
		/// 
		public PendingBuffer(int bufferSize)
		{
			buffer = new byte[bufferSize];
		}
		#endregion
		/// 
		/// Clear internal state/buffers
		/// 
		public void Reset()
		{
			start = end = bitCount = 0;
		}
		/// 
		/// Write a byte to buffer
		/// 
		/// 
		/// The value to write
		/// 
		public void WriteByte(int value)
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) )
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
#endif
			buffer[end++] = unchecked((byte)value);
		}
		/// 
		/// Write a short value to buffer LSB first
		/// 
		/// 
		/// The value to write.
		/// 
		public void WriteShort(int value)
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) )
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
#endif
			buffer[end++] = unchecked((byte)value);
			buffer[end++] = unchecked((byte)(value >> 8));
		}
		/// 
		/// write an integer LSB first
		/// 
		/// The value to write.
		public void WriteInt(int value)
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) )
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
#endif
			buffer[end++] = unchecked((byte)value);
			buffer[end++] = unchecked((byte)(value >> 8));
			buffer[end++] = unchecked((byte)(value >> 16));
			buffer[end++] = unchecked((byte)(value >> 24));
		}
		/// 
		/// Write a block of data to buffer
		/// 
		/// data to write
		/// offset of first byte to write
		/// number of bytes to write
		public void WriteBlock(byte[] block, int offset, int length)
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) ) 
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
#endif
			System.Array.Copy(block, offset, buffer, end, length);
			end += length;
		}
		/// 
		/// The number of bits written to the buffer
		/// 
		public int BitCount {
			get {
				return bitCount;
			}
		}
		/// 
		/// Align internal buffer on a byte boundary
		/// 
		public void AlignToByte()
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) ) 
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
#endif
			if (bitCount > 0) {
				buffer[end++] = unchecked((byte)bits);
				if (bitCount > 8) {
					buffer[end++] = unchecked((byte)(bits >> 8));
				}
			}
			bits = 0;
			bitCount = 0;
		}
		/// 
		/// Write bits to internal buffer
		/// 
		/// source of bits
		/// number of bits to write
		public void WriteBits(int b, int count)
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) ) 
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
			//			if (DeflaterConstants.DEBUGGING) {
			//				//Console.WriteLine("writeBits("+b+","+count+")");
			//			}
#endif
			bits |= (uint)(b << bitCount);
			bitCount += count;
			if (bitCount >= 16) {
				buffer[end++] = unchecked((byte)bits);
				buffer[end++] = unchecked((byte)(bits >> 8));
				bits >>= 16;
				bitCount -= 16;
			}
		}
		/// 
		/// Write a short value to internal buffer most significant byte first
		/// 
		/// value to write
		public void WriteShortMSB(int s)
		{
#if DebugDeflation
			if (DeflaterConstants.DEBUGGING && (start != 0) ) 
			{
				throw new SharpZipBaseException("Debug check: start != 0");
			}
#endif
			buffer[end++] = unchecked((byte)(s >> 8));
			buffer[end++] = unchecked((byte)s);
		}
		/// 
		/// Indicates if buffer has been flushed
		/// 
		public bool IsFlushed {
			get {
				return end == 0;
			}
		}
		/// 
		/// Flushes the pending buffer into the given output array.  If the
		/// output array is to small, only a partial flush is done.
		/// 
		/// The output array.
		/// The offset into output array.
		/// The maximum number of bytes to store.
		/// The number of bytes flushed.
		public int Flush(byte[] output, int offset, int length)
		{
			if (bitCount >= 8) {
				buffer[end++] = unchecked((byte)bits);
				bits >>= 8;
				bitCount -= 8;
			}
			if (length > end - start) {
				length = end - start;
				System.Array.Copy(buffer, start, output, offset, length);
				start = 0;
				end = 0;
			} else {
				System.Array.Copy(buffer, start, output, offset, length);
				start += length;
			}
			return length;
		}
		/// 
		/// Convert internal buffer to byte array.
		/// Buffer is empty on completion
		/// 
		/// 
		/// The internal buffer contents converted to a byte array.
		/// 
		public byte[] ToByteArray()
		{
			AlignToByte();
			
			byte[] result = new byte[end - start];
			System.Array.Copy(buffer, start, result, 0, result.Length);
			start = 0;
			end = 0;
			return result;
		}
	}
}