context: //
  // File.cs: Provides tagging for GIF files
  //
  // Author:
  //   Mike Gemuende (mike@gemuende.be)
  //
  // Copyright (C) 2010 Mike Gemuende
  //
  // This library is free software; you can redistribute it and/or modify
  // it  under the terms of the GNU Lesser General Public License version
  // 2.1 as published by the Free Software Foundation.
  //
  // This library is distributed in the hope that it will be useful, but
  // WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  // Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public
  // License along with this library; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
  // USA
  //
  
  using System;
  using System.IO;
  
  using TagLib;
  using TagLib.Image;
  using TagLib.Xmp;
  
  
  namespace TagLib.Gif
  {
  
  	/// <summary>
  	///    This class extends <see cref="TagLib.Image.ImageBlockFile" /> to provide tagging
  	///    and property support for Gif files.
  	/// </summary>
  	[SupportedMimeType("taglib/gif", "gif")]
  	[SupportedMimeType("image/gif")]
  	public class File : TagLib.Image.ImageBlockFile
  	{
  
  #region GIF specific constants
  
  		/// <summary>
  		///    Gif file signature which occurs at the begin of the file
  		/// </summary>
  		protected static readonly string SIGNATURE = "GIF";
  
  		/// <summary>
  		///    String which is used to indicate version the gif file format version 87a
  		/// </summary>
  		protected static readonly string VERSION_87A = "87a";
  
  		/// <summary>
  		///    String which is used to indicate version the gif file format version 89a
  		/// </summary>
  		protected static readonly string VERSION_89A = "89a";
  
  		/// <summary>
  		///    Application Extension Identifier for an XMP Block
  		/// </summary>
  		private static readonly string XMP_IDENTIFIER = "XMP Data";
  
  		/// <summary>
  		///    Application Authentication Extension Code for an XMP Block
  		/// </summary>
  		private static readonly string XMP_AUTH_CODE = "XMP";
  
  		/// <summary>
  		///    The Magic Trailer for XMP Data
  		/// </summary>
  		/// <remarks>
  		///    The storage of XMP data in GIF does not follow the GIF specification. According to the
  		///    specification, extension data is stored in so-called sub-blocks, which start with a length
  		///    byte which specifies the number of data bytes contained in the sub block. So a block can at
  		///    most contain 256 data bytes. After a sub-block, the next sub-block begins. The sequence ends,
  		///    when a sub-block starts with 0. So readers, which are not aware of the XMP data not following
  		///    this scheme, will get confused by the XMP data. To fix this, this trailer is added to the end.
  		///    It has a length of 258 bytes, so that it is ensured that a reader which tries to skip the
  		///    XMP data reads one of this bytes as length of a sub-block. But, each byte points with its length
  		///    to the last one. Therefoe, independent of the byte, the reader reads as sub-block length, it is
  		///    redirected to the last byte of the trailer and therfore to the end of the XMP data.
  		/// </remarks>
  		private static readonly byte [] XMP_MAGIC_TRAILER = new byte [] {
  			0x01, 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1,
  			0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2, 0xE1,
  			0xE0, 0xDF, 0xDE, 0xDD, 0xDC, 0xDB, 0xDA, 0xD9, 0xD8, 0xD7, 0xD6, 0xD5, 0xD4, 0xD3, 0xD2, 0xD1,
  			0xD0, 0xCF, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1,
  			0xC0, 0xBF, 0xBE, 0xBD, 0xBC, 0xBB, 0xBA, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1,
  			0xB0, 0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1,
  			0xA0, 0x9F, 0x9E, 0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91,
  			0x90, 0x8F, 0x8E, 0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81,
  			0x80, 0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71,
  			0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61,
  			0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51,
  			0x50, 0x4F, 0x4E, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41,
  			0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
  			0x30, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21,
  			0x20, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11,
  			0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
  			0x00, 0x00
  		};
  
  #endregion
  
  #region private fields
  
  		/// <summary>
  		///    The width of the image
  		/// </summary>
  		private int width;
  
  		/// <summary>
  		///    The height of the image
  		/// </summary>
  		private int height;
  
  		/// <summary>
  		///    The Properties of the image
  		/// </summary>
  		private Properties properties;
  
  		/// <summary>
  		///    The version of the file format
  		/// </summary>
  		private string version;
  
  		/// <summary>
  		///    The start of the first block in file after the header.
  		/// </summary>
  		private long start_of_blocks = -1;
  
  #endregion
  
  #region public Properties
  
  		/// <summary>
  		///    Gets the media properties of the file represented by the
  		///    current instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="TagLib.Properties" /> object containing the
  		///    media properties of the file represented by the current
  		///    instance.
  		/// </value>
  		public override TagLib.Properties Properties {
  			get { return properties; }
  		}
  
  #endregion
  
  #region constructors
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="File" /> for a specified file abstraction and
  		///    specified read style.
  		/// </summary>
  		/// <param name="abstraction">
  		///    A <see cref="IFileAbstraction" /> object to use when
  		///    reading from and writing to the file.
  		/// </param>
  		/// <param name="propertiesStyle">
  		///    A <see cref="ReadStyle" /> value specifying at what level
  		///    of accuracy to read the media properties, or <see
  		///    cref="ReadStyle.None" /> to ignore the properties.
  		/// </param>
  		/// <exception cref="ArgumentNullException">
  		///    <paramref name="abstraction" /> is <see langword="null"
  		///    />.
  		/// </exception>
  		public File (File.IFileAbstraction abstraction,
  		             ReadStyle propertiesStyle) : base (abstraction)
  		{
  			Read (propertiesStyle);
  		}
  
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="File" /> for a specified file abstraction.
  		/// </summary>
  		/// <param name="abstraction">
  		///    A <see cref="IFileAbstraction" /> object to use when
  		///    reading from and writing to the file.
  		/// </param>
  		/// <exception cref="ArgumentNullException">
  		///    <paramref name="abstraction" /> is <see langword="null"
  		///    />.
  		/// </exception>
  		protected File (IFileAbstraction abstraction)
  			: this (abstraction, ReadStyle.Average)
  		{
  		}
  
  #endregion
  
  
  #region Public Methods
  
  		/// <summary>
  		///    Saves the changes made in the current instance to the
  		///    file it represents.
  		/// </summary>
  		public override void Save ()
  		{
  			Mode = AccessMode.Write;
  			try {
  				SaveMetadata ();
  
  				TagTypesOnDisk = TagTypes;
  			} finally {
  				Mode = AccessMode.Closed;
  			}
  		}
  
  #endregion
  
  
  #region Private Methods
  
  		/// <summary>
  		///    Reads the information from file with a specified read style.
  		/// </summary>
  		/// <param name="propertiesStyle">
  		///    A <see cref="ReadStyle" /> value specifying at what level
  		///    of accuracy to read the media properties, or <see
  		///    cref="ReadStyle.None" /> to ignore the properties.
  		/// </param>
  		private void Read (ReadStyle propertiesStyle)
  		{
  			Mode = AccessMode.Read;
  			try {
  				ImageTag = new CombinedImageTag (TagTypes.XMP | TagTypes.GifComment);
  
  				ReadHeader ();
  				ReadMetadata ();
  
  				TagTypesOnDisk = TagTypes;
  
  				if (propertiesStyle != ReadStyle.None)
  					properties = ExtractProperties ();
  
  			} finally {
  				Mode = AccessMode.Closed;
  			}
  		}
  
  
  		/// <summary>
  		///   Reads a single byte form file. This is needed often for Gif files.
  		/// </summary>
  		/// <returns>
  		///   A <see cref="System.Byte"/> with the read data.
  		/// </returns>
  		private byte ReadByte ()
  		{
  			ByteVector data = ReadBlock (1);
  
  			if (data.Count != 1)
  				throw new CorruptFileException ("Unexpected end of file");
  
  			return data[0];
  		}
  
  
  		/// <summary>
  		///    Reads the Header and the Logical Screen Descriptor of the GIF file and,
  		///    if there is one, skips the global color table. It also extracts the
  		///    image width and height from it.
  		/// </summary>
  		private void ReadHeader ()
  		{
  			// The header consists of:
  			//
  			// 3 Bytes        Signature
  			// 3 Bytes        Version
  			//
  			// The Logical Screen Descriptor of:
  			//
  			// 2 Bytes        Width  (little endian)
  			// 2 Bytes        Height (little endian)
  			// 1 Byte         Screen and Color Map (packed field)
  			// 1 Byte         Background Color
  			// 1 Byte         Aspect Ratio
  			//
  			// Whereas the bits of the packed field contains some special information.
  			ByteVector data = ReadBlock (13);
  
  			if (data.Count != 13)
  				throw new CorruptFileException ("Unexpected end of Header");
  
  			if (data.Mid (0, 3).ToString () != SIGNATURE)
  				throw new CorruptFileException (String.Format ("Expected a GIF signature at start of file, but found: {0}", data.Mid (0, 3).ToString ()));
  
  			// We do not care about the version here, because we can read both versions in the same way.
  			// We just care when writing metadata, that, if necessary, the version is increased to 89a.
  			var read_version = data.Mid (3, 3).ToString ();
  			if (read_version == VERSION_87A || read_version == VERSION_89A)
  				version = read_version;
  			else
  				throw new UnsupportedFormatException (
  					String.Format ("Only GIF versions 87a and 89a are currently supported, but not: {0}", read_version));
  
  			// Read Image Size (little endian)
  			width = data.Mid (6, 2).ToUShort (false);
  			height = data.Mid (8, 2).ToUShort (false);
  
  			// Skip optional global color table
  			SkipColorTable (data [10]);
  		}
  
  
  		/// <summary>
  		///    Reads the metadata from file. The current position must point to the
  		///    start of the first block after the Header and Logical Screen
  		///    Descriptor (and, if there is one, the Global Color Table)
  		/// </summary>
  		private void ReadMetadata ()
  		{
  			start_of_blocks = Tell;
  
  			// Read Blocks until end of file is reached.
  			while (true) {
  				byte identifier = ReadByte ();
  
  				switch (identifier) {
  				case 0x2c:
  					SkipImage ();
  					break;
  
  				case 0x21:
  					ReadExtensionBlock ();
  					break;
  
  				case 0x3B:
  					return;
  
  				default:
  					throw new CorruptFileException (
  						String.Format ("Do not know what to do with byte 0x{0:X2} at the beginning of a block ({1}).", identifier, Tell - 1));
  				}
  			}
  		}
  
  		/// <summary>
  		///    Reads an Extension Block at the current position. The current position must
  		///    point to the 2nd byte of the comment block. (The other byte is usually
  		///    read before to identify the comment block)
  		/// </summary>
  		private void ReadExtensionBlock ()
  		{
  			// Extension Block
  			//
  			// 1 Byte       Extension Introducer (0x21)
  			// 1 Byte       Extension Identifier
  			// ....
  			//
  			// Note, the Extension Introducer was read before to
  			// identify the Extension Block. Therefore, it has not
  			// to be handled here.
  			byte identifier = ReadByte ();
  
  			switch (identifier) {
  			case 0xFE:
  				ReadCommentBlock ();
  				break;
  
  			case 0xFF:
  				ReadApplicationExtensionBlock ();
  				break;
  
  			// Control Extension Block, ...
  			case 0xF9:
  			// ... Plain Text Extension ...
  			case 0x01:
  			// ... and all other unknown blocks can be skipped by just
  			// reading sub-blocks.
  			default:
  				SkipSubBlocks ();
  				break;
  			}
  		}
  
  
  		/// <summary>
  		///    Reads an Application Extension Block at the current position. The current
  		///    position must point to the 3rd byte of the comment block. (The other 2 bytes
  		///    are usually read before to identify the comment block)
  		/// </summary>
  		private void ReadApplicationExtensionBlock ()
  		{
  			// Application Extension Block
  			//
  			// 1 Byte       Extension Introducer (0x21)
  			// 1 Byte       Application Extension Label (0xFF)
  			// 1 Byte       Block Size (0x0B - 11)
  			// 8 Bytes      Application Identifier
  			// 3 Bytes      Application Auth. Code
  			// N Bytes      Application Data (sub blocks)
  			// 1 Byte       Block Terminator (0x00)
  			//
  			// Note, the first 2 bytes are still read to identify the Comment Block.
  			// Therefore, we only need to read the sub blocks and extract the data.
  			long position = Tell;
  			ByteVector data = ReadBlock (12);
  
  			if (data.Count != 12)
  				throw new CorruptFileException ("");
  
  			// Contains XMP data
  			if (data.Mid (1, 8) == XMP_IDENTIFIER &&
  			    data.Mid (9, 3) == XMP_AUTH_CODE) {
  				// XMP Data is not organized in sub-blocks
  
  				// start of xmp data
  				long data_start = Tell;
  
  				// start of trailer start
  				// FIXME: Since File.Find is still buggy, the following call does not work to find the end of the
  				// XMP data. Therfore, we use here a different way for now.
  				//long xmp_trailer_start = Find (new ByteVector (0x00), data_start);
  
  				// Since searching just one byte is save, we search for the end of the xmp trailer which
  				// consists of two 0x00 bytes and compute the expected start.
  				long xmp_trailer_start = Find (new byte [] {0x00}, data_start) - XMP_MAGIC_TRAILER.Length + 2;
  
  
gt: 				Seek (data_start, SeekOrigin.Begin);