using System;
using System.IO;
using System.Text;
namespace ICSharpCode.SharpZipLib.Zip
{
	/// 
	/// Holds data pertinent to a data descriptor.
	/// 
	public class DescriptorData
	{
		/// 
		/// Get /set the compressed size of data.
		/// 
		public long CompressedSize {
			get { return compressedSize; }
			set { compressedSize = value; }
		}
		/// 
		/// Get / set the uncompressed size of data
		/// 
		public long Size {
			get { return size; }
			set { size = value; }
		}
		/// 
		/// Get /set the crc value.
		/// 
		public long Crc {
			get { return crc; }
			set { crc = (value & 0xffffffff); }
		}
		#region Instance Fields
		long size;
		long compressedSize;
		long crc;
		#endregion
	}
	class EntryPatchData
	{
		public long SizePatchOffset {
			get { return sizePatchOffset_; }
			set { sizePatchOffset_ = value; }
		}
		public long CrcPatchOffset {
			get { return crcPatchOffset_; }
			set { crcPatchOffset_ = value; }
		}
		#region Instance Fields
		long sizePatchOffset_;
		long crcPatchOffset_;
		#endregion
	}
	/// 
	/// This class assists with writing/reading from Zip files.
	/// 
	internal class ZipHelperStream : Stream
	{
		#region Constructors
		/// 
		/// Initialise an instance of this class.
		/// 
		/// The name of the file to open.
		public ZipHelperStream(string name)
		{
			stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite);
			isOwner_ = true;
		}
		/// 
		/// Initialise a new instance of .
		/// 
		/// The stream to use.
		public ZipHelperStream(Stream stream)
		{
			stream_ = stream;
		}
		#endregion
		/// 
		/// Get / set a value indicating wether the the underlying stream is owned or not.
		/// 
		/// If the stream is owned it is closed when this instance is closed.
		public bool IsStreamOwner {
			get { return isOwner_; }
			set { isOwner_ = value; }
		}
		#region Base Stream Methods
		public override bool CanRead {
			get { return stream_.CanRead; }
		}
		public override bool CanSeek {
			get { return stream_.CanSeek; }
		}
		public override bool CanTimeout {
			get { return stream_.CanTimeout; }
		}
		public override long Length {
			get { return stream_.Length; }
		}
		public override long Position {
			get { return stream_.Position; }
			set { stream_.Position = value; }
		}
		public override bool CanWrite {
			get { return stream_.CanWrite; }
		}
		public override void Flush()
		{
			stream_.Flush();
		}
		public override long Seek(long offset, SeekOrigin origin)
		{
			return stream_.Seek(offset, origin);
		}
		public override void SetLength(long value)
		{
			stream_.SetLength(value);
		}
		public override int Read(byte[] buffer, int offset, int count)
		{
			return stream_.Read(buffer, offset, count);
		}
		public override void Write(byte[] buffer, int offset, int count)
		{
			stream_.Write(buffer, offset, count);
		}
		/// 
		/// Close the stream.
		/// 
		/// 
		/// The underlying stream is closed only if  is true.
		/// 
		protected override void Dispose(bool disposing)
		{
			Stream toClose = stream_;
			stream_ = null;
			if (isOwner_ && (toClose != null)) {
				isOwner_ = false;
				toClose.Dispose();
			}
		}
		#endregion
		// Write the local file header
		// TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage
		void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData)
		{
			CompressionMethod method = entry.CompressionMethod;
			bool headerInfoAvailable = true; // How to get this?
			bool patchEntryHeader = false;
			WriteLEInt(ZipConstants.LocalHeaderSignature);
			WriteLEShort(entry.Version);
			WriteLEShort(entry.Flags);
			WriteLEShort((byte)method);
			WriteLEInt((int)entry.DosTime);
			if (headerInfoAvailable == true) {
				WriteLEInt((int)entry.Crc);
				if (entry.LocalHeaderRequiresZip64) {
					WriteLEInt(-1);
					WriteLEInt(-1);
				} else {
					WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
					WriteLEInt((int)entry.Size);
				}
			} else {
				if (patchData != null) {
					patchData.CrcPatchOffset = stream_.Position;
				}
				WriteLEInt(0);  // Crc
				if (patchData != null) {
					patchData.SizePatchOffset = stream_.Position;
				}
				// For local header both sizes appear in Zip64 Extended Information
				if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) {
					WriteLEInt(-1);
					WriteLEInt(-1);
				} else {
					WriteLEInt(0);  // Compressed size
					WriteLEInt(0);  // Uncompressed size
				}
			}
			byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
			if (name.Length > 0xFFFF) {
				throw new ZipException("Entry name too long.");
			}
			var ed = new ZipExtraData(entry.ExtraData);
			if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) {
				ed.StartNewEntry();
				if (headerInfoAvailable) {
					ed.AddLeLong(entry.Size);
					ed.AddLeLong(entry.CompressedSize);
				} else {
					ed.AddLeLong(-1);
					ed.AddLeLong(-1);
				}
				ed.AddNewEntry(1);
				if (!ed.Find(1)) {
					throw new ZipException("Internal error cant find extra data");
				}
				if (patchData != null) {
					patchData.SizePatchOffset = ed.CurrentReadIndex;
				}
			} else {
				ed.Delete(1);
			}
			byte[] extra = ed.GetEntryData();
			WriteLEShort(name.Length);
			WriteLEShort(extra.Length);
			if (name.Length > 0) {
				stream_.Write(name, 0, name.Length);
			}
			if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) {
				patchData.SizePatchOffset += stream_.Position;
			}
			if (extra.Length > 0) {
				stream_.Write(extra, 0, extra.Length);
			}
		}
		/// 
		/// Locates a block with the desired .
		/// 
		/// The signature to find.
		/// Location, marking the end of block.
		/// Minimum size of the block.
		/// The maximum variable data.
		/// Eeturns the offset of the first byte after the signature; -1 if not found
		public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
		{
			long pos = endLocation - minimumBlockSize;
			if (pos < 0) {
				return -1;
			}
			long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
			// TODO: This loop could be optimised for speed.
			do {
				if (pos < giveUpMarker) {
					return -1;
				}
				Seek(pos--, SeekOrigin.Begin);
			} while (ReadLEInt() != signature);
			return Position;
		}
		/// 
		/// Write Zip64 end of central directory records (File header and locator).
		/// 
		/// The number of entries in the central directory.
		/// The size of entries in the central directory.
		/// The offset of the dentral directory.
		public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset)
		{
			long centralSignatureOffset = stream_.Position;
			WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature);
			WriteLELong(44);    // Size of this record (total size of remaining fields in header or full size - 12)
			WriteLEShort(ZipConstants.VersionMadeBy);   // Version made by
			WriteLEShort(ZipConstants.VersionZip64);   // Version to extract
			WriteLEInt(0);      // Number of this disk
			WriteLEInt(0);      // number of the disk with the start of the central directory
			WriteLELong(noOfEntries);       // No of entries on this disk
			WriteLELong(noOfEntries);       // Total No of entries in central directory
			WriteLELong(sizeEntries);       // Size of the central directory
			WriteLELong(centralDirOffset);  // offset of start of central directory
											// zip64 extensible data sector not catered for here (variable size)
			// Write the Zip64 end of central directory locator
			WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature);
			// no of the disk with the start of the zip64 end of central directory
			WriteLEInt(0);
			// relative offset of the zip64 end of central directory record
			WriteLELong(centralSignatureOffset);
			// total number of disks
			WriteLEInt(1);
		}
		/// 
		/// Write the required records to end the central directory.
		/// 
		/// The number of entries in the directory.
		/// The size of the entries in the directory.
		/// The start of the central directory.
		/// The archive comment.  (This can be null).
		public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries,
			long startOfCentralDirectory, byte[] comment)
		{
			if ((noOfEntries >= 0xffff) ||
				(startOfCentralDirectory >= 0xffffffff) ||
				(sizeEntries >= 0xffffffff)) {
				WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory);
			}
			WriteLEInt(ZipConstants.EndOfCentralDirectorySignature);
			// TODO: ZipFile Multi disk handling not done
			WriteLEShort(0);                    // number of this disk
			WriteLEShort(0);                    // no of disk with start of central dir
			// Number of entries
			if (noOfEntries >= 0xffff) {
				WriteLEUshort(0xffff);  // Zip64 marker
				WriteLEUshort(0xffff);
			} else {
				WriteLEShort((short)noOfEntries);          // entries in central dir for this disk
				WriteLEShort((short)noOfEntries);          // total entries in central directory
			}
			// Size of the central directory
			if (sizeEntries >= 0xffffffff) {
				WriteLEUint(0xffffffff);    // Zip64 marker
			} else {
				WriteLEInt((int)sizeEntries);
			}
			// offset of start of central directory
			if (startOfCentralDirectory >= 0xffffffff) {
				WriteLEUint(0xffffffff);    // Zip64 marker
			} else {
				WriteLEInt((int)startOfCentralDirectory);
			}
			int commentLength = (comment != null) ? comment.Length : 0;
			if (commentLength > 0xffff) {
				throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength));
			}
			WriteLEShort(commentLength);
			if (commentLength > 0) {
				Write(comment, 0, comment.Length);
			}
		}
		#region LE value reading/writing
		/// 
		/// Read an unsigned short in little endian byte order.
		/// 
		/// Returns the value read.
		/// 
		/// An i/o error occurs.
		/// 
		/// 
		/// The file ends prematurely
		/// 
		public int ReadLEShort()
		{
			int byteValue1 = stream_.ReadByte();
			if (byteValue1 < 0) {
				throw new EndOfStreamException();
			}
			int byteValue2 = stream_.ReadByte();
			if (byteValue2 < 0) {
				throw new EndOfStreamException();
			}
			return byteValue1 | (byteValue2 << 8);
		}
		/// 
		/// Read an int in little endian byte order.
		/// 
		/// Returns the value read.
		/// 
		/// An i/o error occurs.
		/// 
		/// 
		/// The file ends prematurely
		/// 
		public int ReadLEInt()
		{
			return ReadLEShort() | (ReadLEShort() << 16);
		}
		/// 
		/// Read a long in little endian byte order.
		/// 
		/// The value read.
		public long ReadLELong()
		{
			return (uint)ReadLEInt() | ((long)ReadLEInt() << 32);
		}
		/// 
		/// Write an unsigned short in little endian byte order.
		/// 
		/// The value to write.
		public void WriteLEShort(int value)
		{
			stream_.WriteByte((byte)(value & 0xff));
			stream_.WriteByte((byte)((value >> 8) & 0xff));
		}
		/// 
		/// Write a ushort in little endian byte order.
		/// 
		/// The value to write.
		public void WriteLEUshort(ushort value)
		{
			stream_.WriteByte((byte)(value & 0xff));
			stream_.WriteByte((byte)(value >> 8));
		}
		/// 
		/// Write an int in little endian byte order.
		/// 
		/// The value to write.
		public void WriteLEInt(int value)
		{
			WriteLEShort(value);
			WriteLEShort(value >> 16);
		}
		/// 
		/// Write a uint in little endian byte order.
		/// 
		/// The value to write.
		public void WriteLEUint(uint value)
		{
			WriteLEUshort((ushort)(value & 0xffff));
			WriteLEUshort((ushort)(value >> 16));
		}
		/// 
		/// Write a long in little endian byte order.
		/// 
		/// The value to write.
		public void WriteLELong(long value)
		{
			WriteLEInt((int)value);
			WriteLEInt((int)(value >> 32));
		}
		/// 
		/// Write a ulong in little endian byte order.
		/// 
		/// The value to write.
		public void WriteLEUlong(ulong value)
		{
			WriteLEUint((uint)(value & 0xffffffff));
			WriteLEUint((uint)(value >> 32));
		}
		#endregion
		/// 
		/// Write a data descriptor.
		/// 
		/// The entry to write a descriptor for.
		/// Returns the number of descriptor bytes written.
		public int WriteDataDescriptor(ZipEntry entry)
		{
			if (entry == null) {
				throw new ArgumentNullException("nameof(entry)");
			}
			int result = 0;
			// Add data descriptor if flagged as required
			if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) {
				// The signature is not PKZIP originally but is now described as optional
				// in the PKZIP Appnote documenting trhe format.
				WriteLEInt(ZipConstants.DataDescriptorSignature);
				WriteLEInt(unchecked((int)(entry.Crc)));
				result += 8;
				if (entry.LocalHeaderRequiresZip64) {
					WriteLELong(entry.CompressedSize);
					WriteLELong(entry.Size);
					result += 16;
				} else {
					WriteLEInt((int)entry.CompressedSize);
					WriteLEInt((int)entry.Size);
					result += 8;
				}
			}
			return result;
		}
		/// 
		/// Read data descriptor at the end of compressed data.
		/// 
		/// if set to true [zip64].
		/// The data to fill in.
		/// Returns the number of bytes read in the descriptor.
		public void ReadDataDescriptor(bool zip64, DescriptorData data)
		{
			int intValue = ReadLEInt();
			// In theory this may not be a descriptor according to PKZIP appnote.
			// In practise its always there.
			if (intValue != ZipConstants.DataDescriptorSignature) {
				throw new ZipException("Data descriptor signature not found");
			}
			data.Crc = ReadLEInt();
			if (zip64) {
				data.CompressedSize = ReadLELong();
				data.Size = ReadLELong();
			} else {
				data.CompressedSize = ReadLEInt();
				data.Size = ReadLEInt();
			}
		}
		#region Instance Fields
		bool isOwner_;
		Stream stream_;
		#endregion
	}
}