//
// © Copyright Henrik Ravn 2004
//
// Use, modification and distribution are subject to the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace DotZLib
{
	/// 
	/// Implements a compressed , in GZip (.gz) format.
	/// 
	public class GZipStream : Stream, IDisposable
	{
        #region Dll Imports
        [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)]
        private static extern IntPtr gzopen(string name, string mode);
        [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
        private static extern int gzclose(IntPtr gzFile);
        [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
        private static extern int gzwrite(IntPtr gzFile, int data, int length);
        [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
        private static extern int gzread(IntPtr gzFile, int data, int length);
        [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
        private static extern int gzgetc(IntPtr gzFile);
        [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)]
        private static extern int gzputc(IntPtr gzFile, int c);
        #endregion
        #region Private data
        private IntPtr _gzFile;
        private bool _isDisposed = false;
        private bool _isWriting;
        #endregion
        #region Constructors
        /// 
        /// Creates a new file as a writeable GZipStream
        /// 
        /// The name of the compressed file to create
        /// The compression level to use when adding data
        /// If an error occurred in the internal zlib function
		public GZipStream(string fileName, CompressLevel level)
		{
            _isWriting = true;
            _gzFile = gzopen(fileName, String.Format("wb{0}", (int)level));
            if (_gzFile == IntPtr.Zero)
                throw new ZLibException(-1, "Could not open " + fileName);
		}
        /// 
        /// Opens an existing file as a readable GZipStream
        /// 
        /// The name of the file to open
        /// If an error occurred in the internal zlib function
        public GZipStream(string fileName)
        {
            _isWriting = false;
            _gzFile = gzopen(fileName, "rb");
            if (_gzFile == IntPtr.Zero)
                throw new ZLibException(-1, "Could not open " + fileName);
        }
        #endregion
        #region Access properties
        /// 
        /// Returns true of this stream can be read from, false otherwise
        /// 
        public override bool CanRead
        {
            get
            {
                return !_isWriting;
            }
        }
        /// 
        /// Returns false.
        /// 
        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }
        /// 
        /// Returns true if this tsream is writeable, false otherwise
        /// 
        public override bool CanWrite
        {
            get
            {
                return _isWriting;
            }
        }
        #endregion
        #region Destructor & IDispose stuff
        /// 
        /// Destroys this instance
        /// 
        ~GZipStream()
        {
            cleanUp(false);
        }
        /// 
        /// Closes the external file handle
        /// 
        public void Dispose()
        {
            cleanUp(true);
        }
        // Does the actual closing of the file handle.
        private void cleanUp(bool isDisposing)
        {
            if (!_isDisposed)
            {
                gzclose(_gzFile);
                _isDisposed = true;
            }
        }
        #endregion
        #region Basic reading and writing
        /// 
        /// Attempts to read a number of bytes from the stream.
        /// 
        /// The destination data buffer
        /// The index of the first destination byte in buffer
        /// The number of bytes requested
        /// The number of bytes read
        /// If buffer is null
        /// If count or offset are negative
        /// If offset  + count is > buffer.Length
        /// If this stream is not readable.
        /// If this stream has been disposed.
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (!CanRead) throw new NotSupportedException();
            if (buffer == null) throw new ArgumentNullException();
            if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
            if ((offset+count) > buffer.Length) throw new ArgumentException();
            if (_isDisposed) throw new ObjectDisposedException("GZipStream");
            GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            int result;
            try
            {
                result = gzread(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count);
                if (result < 0)
                    throw new IOException();
            }
            finally
            {
                h.Free();
            }
            return result;
        }
        /// 
        /// Attempts to read a single byte from the stream.
        /// 
        /// The byte that was read, or -1 in case of error or End-Of-File
        public override int ReadByte()
        {
            if (!CanRead) throw new NotSupportedException();
            if (_isDisposed) throw new ObjectDisposedException("GZipStream");
            return gzgetc(_gzFile);
        }
        /// 
        /// Writes a number of bytes to the stream
        /// 
        /// 
        /// 
        /// 
        /// If buffer is null
        /// If count or offset are negative
        /// If offset  + count is > buffer.Length
        /// If this stream is not writeable.
        /// If this stream has been disposed.
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (!CanWrite) throw new NotSupportedException();
            if (buffer == null) throw new ArgumentNullException();
            if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
            if ((offset+count) > buffer.Length) throw new ArgumentException();
            if (_isDisposed) throw new ObjectDisposedException("GZipStream");
            GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try
            {
                int result = gzwrite(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count);
                if (result < 0)
                    throw new IOException();
            }
            finally
            {
                h.Free();
            }
        }
        /// 
        /// Writes a single byte to the stream
        /// 
        /// The byte to add to the stream.
        /// If this stream is not writeable.
        /// If this stream has been disposed.
        public override void WriteByte(byte value)
        {
            if (!CanWrite) throw new NotSupportedException();
            if (_isDisposed) throw new ObjectDisposedException("GZipStream");
            int result = gzputc(_gzFile, (int)value);
            if (result < 0)
                throw new IOException();
        }
        #endregion
        #region Position & length stuff
        /// 
        /// Not supported.
        /// 
        /// 
        /// Always thrown
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }
        /// 
        ///  Not suppported.
        /// 
        /// 
        /// 
        /// 
        /// Always thrown
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }
        /// 
        /// Flushes the GZipStream.
        /// 
        /// In this implementation, this method does nothing. This is because excessive
        /// flushing may degrade the achievable compression rates.
        public override void Flush()
        {
            // left empty on purpose
        }
        /// 
        /// Gets/sets the current position in the GZipStream. Not suppported.
        /// 
        /// In this implementation this property is not supported
        /// Always thrown
        public override long Position
        {
            get
            {
                throw new NotSupportedException();
            }
            set
            {
                throw new NotSupportedException();
            }
        }
        /// 
        /// Gets the size of the stream. Not suppported.
        /// 
        /// In this implementation this property is not supported
        /// Always thrown
        public override long Length
        {
            get
            {
                throw new NotSupportedException();
            }
        }
        #endregion
    }
}