context: //
  // File.cs: Provides tagging and properties support for Apple's AIFF 
  // files.
  //
  // Author:
  //   Helmut Wahrmann
  //
  // Copyright (C) 2009 Helmut Wahrmann
  //
  // 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 TagLib.Id3v2;
  
  namespace TagLib.Aiff
  {
  	/// <summary>
  	///    This class extends <see cref="TagLib.File" /> to provide
  	///    support for reading and writing tags and properties for files
  	///    using the AIFF file format.
  	/// </summary>
  	[SupportedMimeType("taglib/aif", "aif")]
  	[SupportedMimeType("audio/x-aiff")]
  	[SupportedMimeType("audio/aiff")]
  	[SupportedMimeType("sound/aiff")]
  	[SupportedMimeType("application/x-aiff")]
  	public class File : TagLib.File
  	{
  		#region Private Fields
  
  		/// <summary>
  		///    Contains the address of the AIFF header block.
  		/// </summary>
  		private ByteVector header_block = null;
  
  		/// <summary>
  		///  Contains the Id3v2 tag.
  		/// </summary>
  		private Id3v2.Tag tag = null;
  
  		/// <summary>
  		///  Contains the media properties.
  		/// </summary>
  		private Properties properties = null;
  
  		#endregion
  
  		#region Public Static Fields
  
  		/// <summary>
  		///    The identifier used to recognize a AIFF files.
  		/// </summary>
  		/// <value>
  		///    "FORM"
  		/// </value>
  		public static readonly ReadOnlyByteVector FileIdentifier = "FORM";
  
  		/// <summary>
  		///    The identifier used to recognize a AIFF Common chunk.
  		/// </summary>
  		/// <value>
  		///    "COMM"
  		/// </value>
  		public static readonly ReadOnlyByteVector CommIdentifier = "COMM";
  
  		/// <summary>
  		///    The identifier used to recognize a AIFF Sound Data Chunk.
  		/// </summary>
  		/// <value>
  		///    "SSND"
  		/// </value>
  		public static readonly ReadOnlyByteVector SoundIdentifier = "SSND";
  
  		/// <summary>
  		///    The identifier used to recognize a AIFF ID3 chunk.
  		/// </summary>
  		/// <value>
  		///    "ID3 "
  		/// </value>
  		public static readonly ReadOnlyByteVector ID3Identifier = "ID3 ";
  
  		#endregion
  
  		#region Public 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="File.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)
  		{
  			Mode = AccessMode.Read;
  			try
  			{
  				uint aiff_size;
  				long tag_start, tag_end;
  				Read(true, propertiesStyle, out aiff_size,
  				     out tag_start, out tag_end);
  			}
  			finally
  			{
  				Mode = AccessMode.Closed;
  			}
  
  			TagTypesOnDisk = TagTypes;
  
  			GetTag(TagTypes.Id3v2, true);
  		}
  
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="File" /> for a specified file abstraction with an
  		///    average read style.
  		/// </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>
  		public File(File.IFileAbstraction abstraction)
  			: this(abstraction, ReadStyle.Average)
  		{
  		}
  
  		#endregion
  
  		#region Public Properties
  
  		/// <summary>
  		///    Gets a abstract representation of all tags stored in the
  		///    current instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="TagLib.Tag" /> object representing all tags
  		///    stored in the current instance.
  		/// </value>
  		public override Tag Tag
  		{
  			get { return tag; }
  		}
  
  		/// <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 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
  			{
  				ByteVector data = new ByteVector();
  
  				// Add the ID3 chunk and ID32 tag to the vector
  				if (tag != null)
  				{
  					ByteVector tag_data = tag.Render();
  					if (tag_data.Count > 10)
  					{
  						if (tag_data.Count%2 == 1)
  							tag_data.Add(0);
  
  						data.Add("ID3 ");
  						data.Add(ByteVector.FromUInt(
  						         	(uint) tag_data.Count,
  						         	true));
  						data.Add(tag_data);
  					}
  				}
  
  				// Read the file to determine the current AIFF
  				// size and the area tagging is in.
  				uint aiff_size;
  				long tag_start, tag_end;
  				Read(false, ReadStyle.None, out aiff_size,
  				     out tag_start, out tag_end);
  
  				// If tagging info cannot be found, place it at
  				// the end of the file.
  				if (tag_start < 12 || tag_end < tag_start)
  					tag_start = tag_end = Length;
  
  				int length = (int) (tag_end - tag_start + 8);
  
  				// Insert the tagging data.
  				Insert(data, tag_start, length);
  
  				// If the data size changed update the aiff size.
  				if (data.Count - length != 0 &&
  				    tag_start <= aiff_size)
  				{
  					// Depending, if a Tag has been added or removed, 
  					// the length needs to be adjusted
  					if (tag == null)
  					{
  						length -= 16;
  					}
  					else
  					{
  						length -= 8;
  					}
  
  					Insert(ByteVector.FromUInt((uint)
  					                           (aiff_size + data.Count - length),
  					                           true), 4, 4);
  				}
  				// Update the tag types.
  				TagTypesOnDisk = TagTypes;
  			}
  			finally
  			{
  				Mode = AccessMode.Closed;
  			}
  		}
  
  		/// <summary>
  		///    Removes a set of tag types from the current instance.
  		/// </summary>
  		/// <param name="types">
  		///    A bitwise combined <see cref="TagLib.TagTypes" /> value
  		///    containing tag types to be removed from the file.
  		/// </param>
  		/// <remarks>
  		///    In order to remove all tags from a file, pass <see
  		///    cref="TagTypes.AllTags" /> as <paramref name="types" />.
  		/// </remarks>
  		public override void RemoveTags(TagTypes types)
  		{
  			if (types == TagLib.TagTypes.Id3v2 ||
  			    types == TagLib.TagTypes.AllTags)
  			{
  				tag = null;
  			}
  		}
  
  		/// <summary>
  		///    Gets a tag of a specified type from the current instance,
  		///    optionally creating a new tag if possible.
  		/// </summary>
  		/// <param name="type">
  		///    A <see cref="TagLib.TagTypes" /> value indicating the
  		///    type of tag to read.
  		/// </param>
  		/// <param name="create">
  		///    A <see cref="bool" /> value specifying whether or not to
  		///    try and create the tag if one is not found.
  		/// </param>
  		/// <returns>
  		///    A <see cref="Tag" /> object containing the tag that was
  		///    found in or added to the current instance. If no
  		///    matching tag was found and none was created, <see
  		///    langword="null" /> is returned.
  		/// </returns>
  		public override TagLib.Tag GetTag(TagTypes type, bool create)
  		{
  			TagLib.Tag id32_tag = null;
  
  			switch (type)
  			{
  				case TagTypes.Id3v2:
  					if (tag == null && create)
  					{
  						tag = new Id3v2.Tag();
  						tag.Version = 2;
  					}
  
  					id32_tag = tag;
  					break;
  			}
  
  			return id32_tag;
  		}
  
  		#endregion
  
  		#region Private Methods
  
  		/// <summary>
  		///    Reads the contents of the current instance determining
  		///    the size of the riff data, the area the tagging is in,
  		///    and optionally reading in the tags and media properties.
  		/// </summary>
  		/// <param name="read_tags">
  		///    If <see langword="true" />, any tags found will be read
  		///    into the current instance.
  		/// </param>
  		/// <param name="style">
  		///    A <see cref="ReadStyle"/> value specifying how the media
  		///    data is to be read into the current instance.
  		/// </param>
  		/// <param name="aiff_size">
  		///    A <see cref="uint"/> value reference to be filled with
  		///    the size of the RIFF data as read from the file.
  		/// </param>
  		/// <param name="tag_start">
  		///    A <see cref="long" /> value reference to be filled with
  		///    the absolute seek position at which the tagging data
  		///    starts.
  		/// </param>
  		/// <param name="tag_end">
  		///    A <see cref="long" /> value reference to be filled with
  		///    the absolute seek position at which the tagging data
  		///    ends.
  		/// </param>
  		/// <exception cref="CorruptFileException">
  		///    The file does not begin with <see cref="FileIdentifier"
  		///    />.
  		/// </exception>
  		private void Read(bool read_tags, ReadStyle style,
  		                  out uint aiff_size, out long tag_start,
  		                  out long tag_end)
  		{
  			Seek(0);
  			if (ReadBlock(4) != FileIdentifier)
  				throw new CorruptFileException(
  					"File does not begin with AIFF identifier");
  
  			aiff_size = ReadBlock(4).ToUInt(true);
  			tag_start = -1;
  			tag_end = -1;
  
  			// Get the properties of the file
  			if (header_block == null &&
  			    style != ReadStyle.None)
  			{
  				long common_chunk_pos = Find(CommIdentifier, 0);
  
  				if (common_chunk_pos == -1)
  				{
  					throw new CorruptFileException(
  						"No Common chunk available in AIFF file.");
  				}
  
  				Seek(common_chunk_pos);
  				header_block = ReadBlock((int) StreamHeader.Size);
  
  				StreamHeader header = new StreamHeader(header_block, aiff_size);
  				properties = new Properties(TimeSpan.Zero, header);
  			}
  
  			// Now we search for the ID3 chunk.
  			// Normally it appears after the Sound data chunk. But as the order of
  			// chunks is free, it might be the case that the ID3 chunk appears before 
  			// the sound data chunk.
  			// So we search first for the Sound data chunk and see, if an ID3 chunk appears before
  			long id3_chunk_pos = -1;
  			long sound_chunk_pos = Find(SoundIdentifier, 0, ID3Identifier);
  			if (sound_chunk_pos == -1)
  			{
  				// The ID3 chunk appears before the Sound chunk
  				id3_chunk_pos = Find(ID3Identifier, 0);
  			}
  
  			// Now let's look for the Sound chunk again
  			// Since a previous return value of -1 does mean, that the ID3 chunk was found first
  
gt: 			sound_chunk_pos = Find(SoundIdentifier, 0);