context: //
  // SynchronizedLyricsFrame.cs:
  //
  // Author:
  //   Brian Nickel (brian.nickel@gmail.com)
  //
  // Copyright (C) 2007 Brian Nickel
  //
  // 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.Collections.Generic;
  using System.Text;
  
  namespace CommonTools.TagLib.Id3v2
  {
  	/// <summary>
  	///    Specifies the timestamp format used by a <see
  	///    cref="SynchronisedLyricsFrame" />.
  	/// </summary>
  	public enum TimestampFormat
  	{
  		/// <summary>
  		///    The timestamp is of unknown format.
  		/// </summary>
  		Unknown              = 0x00,
  		
  		/// <summary>
  		///    The timestamp represents the number of MPEG frames since
  		///    the beginning of the audio stream.
  		/// </summary>
  		AbsoluteMpegFrames   = 0x01,
  		
  		/// <summary>
  		///    The timestamp represents the number of milliseconds since
  		///    the beginning of the audio stream.
  		/// </summary>
  		AbsoluteMilliseconds = 0x02
  	}
  	
  	/// <summary>
  	///    Specifies the type of text contained in a <see
  	///    cref="SynchronisedLyricsFrame" />.
  	/// </summary>
  	public enum SynchedTextType
  	{
  		/// <summary>
  		///    The text is some other type of text.
  		/// </summary>
  		Other             = 0x00,
  		
  		/// <summary>
  		///    The text contains lyrical data.
  		/// </summary>
  		Lyrics            = 0x01,
  		
  		/// <summary>
  		///    The text contains a transcription.
  		/// </summary>
  		TextTranscription = 0x02,
  		
  		/// <summary>
  		///    The text lists the movements in the piece.
  		/// </summary>
  		Movement          = 0x03,
  		
  		/// <summary>
  		///    The text describes events that occur.
  		/// </summary>
  		Events            = 0x04,
  		
  		/// <summary>
  		///    The text contains chord changes that occur in the music.
  		/// </summary>
  		Chord             = 0x05,
  		
  		/// <summary>
  		///    The text contains trivia or "pop up" information about
  		///    the media.
  		/// </summary>
  		Trivia            = 0x06,
  		
  		/// <summary>
  		///    The text contains URL's for relevant webpages.
  		/// </summary>
  		WebpageUrls       = 0x07,
  		
  		/// <summary>
  		///     The text contains URL's for relevant images.
  		/// </summary>
  		ImageUrls         = 0x08
  	}
  	
  	/// <summary>
  	///    This class extends <see cref="Frame" />, implementing support for
  	///    ID3v2 Synchronised Lyrics and Text (SYLT) Frames.
  	/// </summary>
  	public class SynchronisedLyricsFrame : Frame
  	{
  #region Private Properties
  		
  		/// <summary>
  		///    Contains the text encoding to use when rendering the
  		///    current instance.
  		/// </summary>
  		private StringType encoding = Tag.DefaultEncoding;
  		
  		/// <summary>
  		///    Contains the ISO-639-2 language code.
  		/// </summary>
  		private string language = null;
  		
  		/// <summary>
  		///    Contains the description.
  		/// </summary>
  		private string description = null;
  		
  		/// <summary>
  		///    Contains the timestamp format.
  		/// </summary>
  		private TimestampFormat timestamp_format =
  			TimestampFormat.Unknown;
  		
  		/// <summary>
  		///    Contains the text type.
  		/// </summary>
  		private SynchedTextType lyrics_type = SynchedTextType.Other;
  		
  		/// <summary>
  		///    Contains the text.
  		/// </summary>
  		private SynchedText [] text = new SynchedText [0];
  		
  #endregion
  		
  		
  		
  #region Constructors
  		
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="SynchronisedLyricsFrame" /> with a specified
  		///    description, ISO-639-2 language code, text type, and text
  		///    encoding.
  		/// </summary>
  		/// <param name="description">
  		///    A <see cref="string" /> object containing the description
  		///    of the new instnace.
  		/// </param>
  		/// <param name="language">
  		///    A <see cref="string" /> object containing the ISO-639-2
  		///    language code of the new instance.
  		/// </param>
  		/// <param name="type">
  		///    A <see cref="SynchedTextType" /> containing the type of
  		///    text to be stored in the new instance.
  		/// </param>
  		/// <param name="encoding">
  		///    A <see cref="StringType" /> containing the text encoding
  		///    to use when rendering the new instance.
  		/// </param>
  		/// <remarks>
  		///    When a frame is created, it is not automatically added to
  		///    the tag. Consider using <see cref="Get" /> for more
  		///    integrated frame creation.
  		/// </remarks>
  		public SynchronisedLyricsFrame (string description,
  		                                string language,
  		                                SynchedTextType type,
  		                                StringType encoding)
  			: base (FrameType.SYLT, 4)
  		{
  			this.encoding = encoding;
  			this.language = language;
  			this.description = description;
  			this.lyrics_type = type;
  		}
  
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="SynchronisedLyricsFrame" /> with a specified
  		///    description, ISO-639-2 language code, and text type.
  		/// </summary>
  		/// <param name="description">
  		///    A <see cref="string" /> object containing the description
  		///    of the new instnace.
  		/// </param>
  		/// <param name="language">
  		///    A <see cref="string" /> object containing the ISO-639-2
  		///    language code of the new instance.
  		/// </param>
  		/// <param name="type">
  		///    A <see cref="SynchedTextType" /> containing the type of
  		///    text to be stored in the new instance.
  		/// </param>
  		/// <remarks>
  		///    When a frame is created, it is not automatically added to
  		///    the tag. Consider using <see cref="Get" /> for more
  		///    integrated frame creation.
  		/// </remarks>
  		public SynchronisedLyricsFrame (string description,
  		                                string language,
  		                                SynchedTextType type)
  			: this (description, language, type,
  				TagLib.Id3v2.Tag.DefaultEncoding)
  		{
  		}
  
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="SynchronisedLyricsFrame" /> by reading its raw data
  		///    in a specified ID3v2 version.
  		/// </summary>
  		/// <param name="data">
  		///    A <see cref="ByteVector" /> object starting with the raw
  		///    representation of the new instance.
  		/// </param>
  		/// <param name="version">
  		///    A <see cref="byte" /> indicating the ID3v2 version the
  		///    raw frame is encoded in.
  		/// </param>
  		public SynchronisedLyricsFrame (ByteVector data, byte version)
  			: base(data, version)
  		{
  			SetData (data, 0, version, true);
  		}
  
  		/// <summary>
  		///    Constructs and initializes a new instance of <see
  		///    cref="SynchronisedLyricsFrame" /> by reading its raw data
  		///    in a specified ID3v2 version.
  		/// </summary>
  		/// <param name="data">
  		///    A <see cref="ByteVector" /> object containing the raw
  		///    representation of the new instance.
  		/// </param>
  		/// <param name="offset">
  		///    A <see cref="int" /> indicating at what offset in
  		///    <paramref name="data" /> the frame actually begins.
  		/// </param>
  		/// <param name="header">
  		///    A <see cref="FrameHeader" /> containing the header of the
  		///    frame found at <paramref name="offset" /> in the data.
  		/// </param>
  		/// <param name="version">
  		///    A <see cref="byte" /> indicating the ID3v2 version the
  		///    raw frame is encoded in.
  		/// </param>
  		protected internal SynchronisedLyricsFrame (ByteVector data,
  		                                            int offset,
  		                                            FrameHeader header,
  		                                            byte version)
  			: base (header)
  		{
  			SetData (data, offset, version, false);
  		}
  		
  #endregion
  		
  		
  		
  #region Public Properties
  		
  		/// <summary>
  		///    Gets and sets the text encoding to use when storing the
  		///    current instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="string" /> containing the text encoding to
  		///    use when storing the current instance.
  		/// </value>
  		/// <remarks>
  		///    This encoding is overridden when rendering if <see
  		///    cref="Tag.ForceDefaultEncoding" /> is <see
  		///    langword="true" /> or the render version does not support
  		///    it.
  		/// </remarks>
  		public StringType TextEncoding {
  			get {return encoding;}
  			set {encoding = value;}
  		}
  
  		/// <summary>
  		///    Gets and sets the ISO-639-2 language code stored in the
  		///    current instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="string" /> containing the ISO-639-2 language
  		///    code stored in the current instance.
  		/// </value>
  		/// <remarks>
  		///    There should only be one frame with a matching
  		///    description, type, and ISO-639-2 language code per tag.
  		/// </remarks>
  		public string Language {
  			get {
  				return (language != null && language.Length > 2)
  					? language.Substring (0, 3) : "XXX";
  			}
  			set {language = value;}
  		}
  
  		/// <summary>
  		///    Gets and sets the description stored in the current
  		///    instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="string" /> containing the description
  		///    stored in the current instance.
  		/// </value>
  		/// <remarks>
  		///    There should only be one frame with a matching
  		///    description, type, and ISO-639-2 language code per tag.
  		/// </remarks>
  		public string Description {
  			get {return description;}
  			set {description = value;}
  		}
  		
  		/// <summary>
  		///    Gets and sets the timestamp format used by the current
  		///    instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="TimestampFormat" /> value describing the
  		///    timestamp format used by the current instance.
  		/// </value>
  		public TimestampFormat Format {
  			get {return timestamp_format;}
  			set {timestamp_format = value;}
  		}
  		
  		/// <summary>
  		///    Gets and sets the type of text contained in the current
  		///    instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="TimestampFormat" /> value describing the
  		///    type of text contained in the current instance.
  		/// </value>
  		public SynchedTextType Type {
  			get {return lyrics_type;}
  			set {lyrics_type = value;}
  		}
  		
  		/// <summary>
  		///    Gets and sets the text contained in the current
  		///    instance.
  		/// </summary>
  		/// <value>
  		///    A <see cref="SynchedText[]" /> containing the text
  		///    contained in the current instance.
  		/// </value>
  		public SynchedText [] Text {
  			get {return text;}
  			set {
  				text = value == null ? new SynchedText [0] :
  					value;
  			}
  		}
  		
  #endregion
  		
  		
  		
  #region Public Static Methods
  		
  		/// <summary>
  		///    Gets a specified lyrics frame from the specified tag,
  		///    optionally creating it if it does not exist.
  		/// </summary>
  		/// <param name="tag">
  		///    A <see cref="Tag" /> object to search in.
  		/// </param>
  		/// <param name="description">
  		///    A <see cref="string" /> object specifying the description
  		///    to match.
  		/// </param>
  		/// <param name="language">
  		///    A <see cref="string" /> object specifying the ISO-639-2
  		///    language code to match.
  		/// </param>
  		/// <param name="type">
  		///    A <see cref="SynchedTextType" /> value specifying the
  		///    text type to match.
  		/// </param>
  		/// <param name="create">
  		///    A <see cref="bool" /> specifying whether or not to create
  		///    and add a new frame to the tag if a match is not found.
  		/// </param>
  		/// <returns>
  		///    A <see cref="SynchronisedLyricsFrame" /> object
  		///    containing the matching frame, or <see langword="null" />
  		///    if a match wasn't found and <paramref name="create" /> is
  		///    <see langword="false" />.
  		/// </returns>
  		public static SynchronisedLyricsFrame Get (Tag tag,
  		                                           string description,
  		                                           string language,
  		                                           SynchedTextType type,
  		                                           bool create)
  		{
  			foreach (Frame f in tag) {
  				SynchronisedLyricsFrame lyr =
  					f as SynchronisedLyricsFrame;
  				
  				if (lyr == null)
  					continue;
  				
  				if (lyr.Description == description &&
  					(language == null ||
  						language == lyr.Language) &&
  					type == lyr.Type)
  					return lyr;
  			}
  
  			if (!create)
  				return null;
  
  			SynchronisedLyricsFrame frame =
  				new SynchronisedLyricsFrame (description,
  					language, type);
  			tag.AddFrame (frame);
  			return frame;
  		}
  
  		/// <summary>
  		///    Gets a specified lyrics frame from the specified tag,
  		///    trying to to match the description and language but
  		///    accepting an incomplete match.
  		/// </summary>
  		/// <param name="tag">
  		///    A <see cref="Tag" /> object to search in.
  		/// </param>
  		/// <param name="description">
  		///    A <see cref="string" /> object specifying the description
  		///    to match.
  		/// </param>
  		/// <param name="language">
  		///    A <see cref="string" /> object specifying the ISO-639-2
  		///    language code to match.
  		/// </param>
  		/// <param name="type">
  		///    A <see cref="SynchedTextType" /> value specifying the
  		///    text type to match.
  		/// </param>
  		/// <returns>
  		///    A <see cref="SynchronisedLyricsFrame" /> object
  		///    containing the matching frame, or <see langword="null" />
  		///    if a match wasn't found.
  		/// </returns>
  		/// <remarks>
  		///    <para>The method tries matching with the following order
  		///    of precidence:</para>
  		///    <list type="number">
  		///       <item><term>The first frame with a matching
  		///       description, language, and type.</term></item>
  		///       <item><term>The first frame with a matching
  		///       description and language.</term></item>
  		///       <item><term>The first frame with a matching
  		///       language.</term></item>
  		///       <item><term>The first frame with a matching
  		///       description.</term></item>
  		///       <item><term>The first frame with a matching
  		///       type.</term></item>
  		///       <item><term>The first frame.</term></item>
  		///    </list>
  		/// </remarks>
  		public static SynchronisedLyricsFrame GetPreferred (Tag tag,
  		                                                    string description,
  		                                                    string language,
  		                                                    SynchedTextType type)
  		{
  			// This is weird, so bear with me. The best thing we can
  			// have is something straightforward and in our own
  			// language. If it has a description, then it is
  			// probably used for something other than an actual
  			// comment. If that doesn't work, we'd still rather have
  			// something in our language than something in another.
  			// After that all we have left are things in other
  			// languages, so we'd rather have one with actual
  			// content, so we try to get one with no description
  			// first.
  
  			int best_value = -1;
  			SynchronisedLyricsFrame best_frame = null;
  			
  			foreach (Frame f in tag) {
  				SynchronisedLyricsFrame cf =
  					f as SynchronisedLyricsFrame;
  				
  				if (cf == null)
  					continue;
  				
  				int value = 0;
  				if (cf.Language == language)
  					value += 4;
  
gt: 				if (cf.Description == description)