context: //
  // EBMLElement.cs:
  //
  // Author:
  //   Julien Moutte <julien@fluendo.com>
  //   Sebastien Mouy <starwer@laposte.net>
  //
  // Copyright (C) 2011 FLUENDO S.A.
  //
  // 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;
  
  
  namespace TagLib.Matroska
  {
  	/// <summary>
  	/// Read a Matroska EBML element from a file, but also provides basic modifications to an 
  	/// EBML element directly on the file (write). This can also represent an abstract EBML 
  	/// on the file (placeholder).
  	/// </summary>
  	/// <remarks>
  	///  This was intitialy called <see cref="EBMLelement"/>, but this was in fact a file-reader.
  	///  The name <see cref="EBMLelement"/> correspond more to the class which has been created to
  	///  represent an EBML structure (regardless of file-issues) to support the EBML writing to file.
  	/// </remarks>
  	public class EBMLreader
  	{
  		#region Private Fields
  
  		File file;
  		ulong offset;
  		uint ebml_id;
  
  		#endregion
  
  		#region Constructors
  
  		/// <summary>
  		/// Constructs a root <see cref="EBMLreader" /> instance, by reading from
  		/// the provided file position.
  		/// </summary>
  		/// <param name="_file"><see cref="File" /> File instance to read from.</param>
  		/// <param name="position">Position in the file to start reading from.</param>
  		public EBMLreader (File _file, ulong position)
  		{
  			// Keep a reference to the file
  			file = _file;
  			Parent = null;
  
  			// Initialize attributes
  			offset = position;
  			DataOffset = position;
  			ebml_id = 0;
  			DataSize = 0;
  
  			// Actually read the EBML on the file
  			Read (true);
  		}
  
  
  		/// <summary>
  		/// Constructs a child <see cref="EBMLreader" /> reading the data from the
  		/// EBML parent at the provided file position.
  		/// </summary>
  		/// <param name="parent">The <see cref="EBMLreader" /> that contains the instance to be created.</param>
  		/// <param name="position">Position in the file to start reading from.</param>
  		public EBMLreader (EBMLreader parent, ulong position)
  		{
  			if (parent == null)
  				throw new ArgumentNullException (nameof (parent));
  
  			// Keep a reference to the file
  			file = parent.file;
  			Parent = parent;
  
  			// Initialize attributes
  			offset = position;
  			DataOffset = position;
  			ebml_id = 0;
  			DataSize = 0;
  
  
  			// Actually read the EBML on the file
  			Read (true);
  		}
  
  
  		/// <summary>
  		/// Create a new abstract <see cref="EBMLreader" /> with arbitrary attributes, 
  		/// without reading its information on the file.
  		/// </summary>
  		/// <param name="parent">The <see cref="EBMLreader" /> that contains the instance to be described.</param>
  		/// <param name="position">Position in the file.</param>
  		/// <param name="ebmlid">EBML ID of the element</param>
  		/// <param name="size">Total size of the EBML, in bytes</param>
  		public EBMLreader (EBMLreader parent, ulong position, MatroskaID ebmlid, ulong size = 0)
  		{
  			// Keep a reference to the file
  			if (parent != null)
  				file = parent.file;
  
  			Parent = parent;
  
  			// Initialize attributes
  			offset = position;
  			DataOffset = offset;
  			ebml_id = (uint)ebmlid;
  			DataSize = size;
  		}
  
  		#endregion
  
  		#region Public Properties
  
  		/// <summary>
  		/// EBML Element Identifier.
  		/// </summary>
  		public MatroskaID ID {
  			get { return (MatroskaID)ebml_id; }
  		}
  
  		/// <summary>
  		/// EBML Parent instance.
  		/// </summary>
  		public EBMLreader Parent { get; }
  
  		/// <summary>
  		/// EBML Element size in bytes.
  		/// </summary>
  		public ulong Size {
  			set { DataSize = value - (DataOffset - offset); }
  			get { return (DataOffset - offset) + DataSize; }
  		}
  
  		/// <summary>
  		/// EBML Element data size in bytes.
  		/// </summary>
  		public ulong DataSize { set; get; }
  
  		/// <summary>
  		/// EBML Element data offset position in file in bytes.
  		/// </summary>
  		public ulong DataOffset { get; set; }
  
  		/// <summary>
  		/// EBML Element offset position in file in bytes.
  		/// </summary>
  		public ulong Offset {
  			set {
  				DataOffset = (ulong)((long)DataOffset + ((long)value - (long)offset));
  				offset = value;
  			}
  			get { return offset; }
  		}
  
  		/// <summary>
  		/// Defines that the EBML element is not read-out from file,
  		/// but is an abstract representation of an element on the disk.
  		/// </summary>
  		public bool Abstract {
  			get { return offset == DataOffset; }
  		}
  
  
  		#endregion
  
  		#region Public Methods for Reading
  
  		/// <summary>
  		/// Read EBML header and data-size if it is an abstract one. 
  		/// It then becomes a non abstract EBML.
  		/// </summary>
  		/// <param name="throwException">Throw exception on invalid EBML read if true (Default: false).</param>
  		/// <returns>True if successful.</returns>
  		public bool Read (bool throwException = false)
  		{
  			if (!Abstract) return true;
  
  			if (file == null)
  				throw new ArgumentNullException (nameof(file));
  
  			try {
  				var ex = new InvalidOperationException ("Invalid EBML format Read");
  
  				if (offset >= (ulong)(file.Length) - 1) throw ex;
  
  				// Prepare for Consitency check
  				uint ebml_id_check = ebml_id;
  				ulong ebml_size_check = Size;
  
  
  				file.Seek ((long)offset);
  
  				// Get the header byte
  				ByteVector vector = file.ReadBlock (1);
  				byte header_byte = vector[0];
  				// Define a mask
  				byte mask = 0x80, id_length = 1;
  				// Figure out the size in bytes
  				while (id_length <= 4 && (header_byte & mask) == 0) {
  					id_length++;
  					mask >>= 1;
  				}
  				if (id_length > 4) throw ex;
  
  				// Now read the rest of the EBML ID
  				if (id_length > 1) {
  					vector.Add (file.ReadBlock (id_length - 1));
  				}
  
  				ebml_id = vector.ToUInt ();
  
  				vector.Clear ();
  
  				// Get the size length
  				vector = file.ReadBlock (1);
  				header_byte = vector[0];
  				mask = 0x80;
  				byte size_length = 1;
  
  				// Iterate through various possibilities
  				while (size_length <= 8 && (header_byte & mask) == 0) {
  					size_length++;
  					mask >>= 1;
  				}
  
  
  				if (size_length > 8)
  					size_length = 1; // Special: Empty element (all zero state)
  				else
  					vector[0] &= (byte)(mask - 1);  // Clear the marker bit
  
  
  				// Now read the rest of the EBML element size
  				if (size_length > 1) {
  					vector.Add (file.ReadBlock (size_length - 1));
  				}
  
  				DataSize = vector.ToULong ();
  
  				// Special: Auto-size (0xFF byte)
  				if (size_length == 1 && DataSize == 0x7F) {
  					// Resolve auto-size to fill in to its containing element
  					ulong bound = Parent == null ? (ulong)file.Length : Parent.Offset + Parent.Size;
  					DataSize = bound - offset - (ulong)(id_length + size_length);
  				}
  
  				DataOffset = offset + id_length + size_length;
  
  				// Consistency check: Detect descrepencies between read data and abstract data 
  				if (ebml_id_check != 0 && ebml_id_check != ebml_id) throw ex;
  
gt: 				if (ebml_size_check != 0 && ebml_size_check != Size) throw ex;