//
// Picture.cs: Provides IPicture and Picture.
//
// Author:
//   Aaron Bockover (abockover@novell.com)
//   Brian Nickel (brian.nickel@gmail.com)
//
// Original Source:
//   attachedpictureframe.cpp from TagLib
//
// Copyright (C) 2006 Novell, Inc.
// Copyright (C) 2007 Brian Nickel
// Copyright (C) 2004 Scott Wheeler (Original Implementation)
//
// 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
{
	/// <summary>
	///    Specifies the type of content appearing in the picture.
	/// </summary>
	public enum PictureType
	{
		/// <summary>
		///    The picture is of a type other than those specified.
		/// </summary>
		Other = 0x00,

		/// <summary>
		///    The picture is a 32x32 PNG image that should be used when
		///    displaying the file in a browser.
		/// </summary>
		FileIcon = 0x01,

		/// <summary>
		///    The picture is of an icon different from <see
		///    cref="FileIcon" />.
		/// </summary>
		OtherFileIcon = 0x02,

		/// <summary>
		///    The picture is of the front cover of the album.
		/// </summary>
		FrontCover = 0x03,

		/// <summary>
		///    The picture is of the back cover of the album.
		/// </summary>
		BackCover = 0x04,

		/// <summary>
		///    The picture is of a leaflet page including with the
		///    album.
		/// </summary>
		LeafletPage = 0x05,

		/// <summary>
		///    The picture is of the album or disc itself.
		/// </summary>
		Media = 0x06,
		// Image from the album itself

		/// <summary>
		///    The picture is of the lead artist or soloist.
		/// </summary>
		LeadArtist = 0x07,

		/// <summary>
		///    The picture is of the artist or performer.
		/// </summary>
		Artist = 0x08,

		/// <summary>
		///    The picture is of the conductor.
		/// </summary>
		Conductor = 0x09,

		/// <summary>
		///    The picture is of the band or orchestra.
		/// </summary>
		Band = 0x0A,

		/// <summary>
		///    The picture is of the composer.
		/// </summary>
		Composer = 0x0B,

		/// <summary>
		///    The picture is of the lyricist or text writer.
		/// </summary>
		Lyricist = 0x0C,

		/// <summary>
		///    The picture is of the recording location or studio.
		/// </summary>
		RecordingLocation = 0x0D,

		/// <summary>
		///    The picture is one taken during the track's recording.
		/// </summary>
		DuringRecording = 0x0E,

		/// <summary>
		///    The picture is one taken during the track's performance.
		/// </summary>
		DuringPerformance = 0x0F,

		/// <summary>
		///    The picture is a capture from a movie screen.
		/// </summary>
		MovieScreenCapture = 0x10,

		/// <summary>
		///    The picture is of a large, colored fish.
		/// </summary>
		ColoredFish = 0x11,

		/// <summary>
		///    The picture is an illustration related to the track.
		/// </summary>
		Illustration = 0x12,

		/// <summary>
		///    The picture contains the logo of the band or performer.
		/// </summary>
		BandLogo = 0x13,

		/// <summary>
		///    The picture is the logo of the publisher or record
		///    company.
		/// </summary>
		PublisherLogo = 0x14,


		/// <summary>
		///    In fact, this is not a Picture, but another file-type.
		/// </summary>
		NotAPicture = 0xff

	}

	/// <summary>
	///    This interface provides generic information about a picture,
	///    including its contents, as used by various formats.
	/// </summary>
	public interface IPicture
	{
		/// <summary>
		///    Gets and sets the mime-type of the picture data
		///    stored in the current instance.
		/// </summary>
		/// <value>
		///    A <see cref="string" /> object containing the mime-type
		///    of the picture data stored in the current instance.
		/// </value>
		string MimeType { get; set; }

		/// <summary>
		///    Gets and sets the type of content visible in the picture
		///    stored in the current instance.
		/// </summary>
		/// <value>
		///    A <see cref="PictureType" /> containing the type of
		///    content visible in the picture stored in the current
		///    instance.
		/// </value>
		PictureType Type { get; set; }



		/// <summary>
		///    Gets and sets a filename of the picture stored in the
		///    current instance.
		/// </summary>
		/// <value>
		///    A <see cref="string" /> object containing the filename,
		///    with its extension, of the picture stored in the current 
		///    instance.
		/// </value>
		string Filename { get; set; }


		/// <summary>
		///    Gets and sets a description of the picture stored in the
		///    current instance.
		/// </summary>
		/// <value>
		///    A <see cref="string" /> object containing a description
		///    of the picture stored in the current instance.
		/// </value>
		string Description { get; set; }

		/// <summary>
		///    Gets and sets the picture data stored in the current
		///    instance.
		/// </summary>
		/// <value>
		///    A <see cref="ByteVector" /> object containing the picture
		///    data stored in the current instance.
		/// </value>
		ByteVector Data { get; set; }
	}

	/// <summary>
	///    This class implements <see cref="IPicture" /> and provides
	///    mechanisms for loading pictures from files.
	/// </summary>
	public class Picture : IPicture
	{

		#region Constants

		/// <summary>
		///    Look-Up-Table associating a file-extension to 
		///    a Mime-Type 
		/// </summary>
		static readonly string[] lutExtensionMime = new[] {
			"aac", "audio/aac", // AAC audio file
			"abw", "application/x-abiword", // AbiWord document
			"arc", "application/octet-stream", // Archive document (multiple files embedded)
			"avi", "video/x-msvideo", // AVI: Audio Video Interleave
			"azw", "application/vnd.amazon.ebook", // Amazon Kindle eBook format
			"bin", "application/octet-stream", // Any kind of binary data
			"bmp", "image/bmp", // BMP image data
			"bmp", "image/x-windows-bmp", // BMP image data
			"bm", "image/bmp", // BMP image data
			"bz", "application/x-bzip", // BZip archive
			"bz2", "application/x-bzip2", // BZip2 archive
			"csh", "application/x-csh", // C-Shell script
			"css", "text/css", // Cascading Style Sheets (CSS)
			"csv", "text/csv", // Comma-separated values (CSV)
			"doc", "application/msword", // Microsoft Word
			"eot", "application/vnd.ms-fontobject", // MS Embedded OpenType fonts
			"epub", "application/epub+zip", // Electronic publication (EPUB)
			"gif", "image/gif", // Graphics Interchange Format (GIF)
			"htm", "text/html", // HyperText Markup Language (HTML)text / html
			"html", "text/html", // HyperText Markup Language (HTML)text / html
			"ico", "image/x-icon", // Icon format
			"ics", "text/calendar", // iCalendar format
			"jar", "application/java-archive", // Java Archive (JAR)
			"jpg", "image/jpeg", // JPEG images
			"jpeg", "image/jpeg", // JPEG images
			"js", "application/javascript", // JavaScript (ECMAScript)
			"json", "application/json", // JSON format
			"mid", "audio/midi", // Musical Instrument Digital Interface (MIDI)
			"midi", "audio/midi", // Musical Instrument Digital Interface (MIDI)
			"mp3", "audio/mpeg",
			"mp1", "audio/mpeg",
			"mp2", "audio/mpeg",
			"mpg", "video/mpeg",
			"mpeg", "video/mpeg", // MPEG Video
			"m4a", "audio/mp4",
			"mp4", "video/mp4",
			"m4v", "video/mp4",
			"mpkg", "application/vnd.apple.installer+xml", // Apple Installer Package
			"odp", "application/vnd.oasis.opendocument.presentation", // OpenDocuemnt presentation document
			"ods", "application/vnd.oasis.opendocument.spreadsheet", // OpenDocuemnt spreadsheet document
			"odt", "application/vnd.oasis.opendocument.text", // OpenDocument text document
			"oga", "audio/ogg", // OGG audio
			"ogg", "audio/ogg",
			"ogx", "application/ogg", // OGG
			"ogv", "video/ogg",
			"otf", "font/otf", // OpenType font
			"png", "image/png", // Portable Network Graphics
			"pdf", "application/pdf", // Adobe Portable Document Format (PDF)
			"ppt", "application/vnd.ms-powerpoint", // Microsoft PowerPoint
			"rar", "application/x-rar-compressed", // RAR archive
			"rtf", "application/rtf", // Rich Text Format (RTF)
			"sh", "application/x-sh", // Bourne shell script
			"svg", "image/svg+xml", // Scalable Vector Graphics (SVG)
			"swf", "application/x-shockwave-flash", // Small web format (SWF) or Adobe Flash document
			"tar", "application/x-tar", // Tape Archive (TAR)
			"tif", "image/tiff", //  Tagged Image File Format(TIFF)
			"tiff", "image/tiff", //  Tagged Image File Format(TIFF)
			"ts", "video/vnd.dlna.mpeg-tts", // Typescript file
			"ttf", "font/ttf", // TrueType Font
			"vsd", "application/vnd.visio", // Microsoft Visio
			"wav", "audio/x-wav", // Waveform Audio Format
			"weba", "audio/webm", // WEBM audio
			"webm", "video/webm", // WEBM video
			"webp", "image/webp", // WEBP image
			"woff", "font/woff", // Web Open Font Format (WOFF)
			"woff2", "font/woff2", // Web Open Font Format (WOFF)
			"xhtml", "application/xhtml+xml", // XHTML
			"xls", "application/vnd.ms", // excel application
			"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // excel 2007 application
			"xml", "application/xml", // XML
			"xul", "application/vnd.mozilla.xul+xml", // XUL
			"zip", "application/zip", // ZIP archive
			"3gp", "video/3gpp", // 3GPP audio/video container
			"3g2", "video/3gpp2", // 3GPP2 audio/video container
			"7z", "application/x-7z-compressed", // 7-zip archive
		};

		#endregion

		#region Constructors

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="Picture" /> with no data or values.
		/// </summary>
		public Picture ()
		{
		}

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="Picture" /> by reading in the contents of a
		///    specified file.
		/// </summary>
		/// <param name="path">
		///    A <see cref="string"/> object containing the path of the
		///    file to read.
		/// </param>
		/// <exception cref="ArgumentNullException">
		///    <paramref name="path" /> is <see langword="null" />.
		/// </exception>
		public Picture (string path)
		{
			if (path == null)
				throw new ArgumentNullException (nameof (path));

			Data = ByteVector.FromPath (path);
			Filename = System.IO.Path.GetFileName (path);
			Description = Filename;
			MimeType = GetMimeFromExtension (Filename);
			Type = MimeType.StartsWith ("image/") ? PictureType.FrontCover : PictureType.NotAPicture;
		}

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="Picture" /> by reading in the contents of a
		///    specified file abstraction.
		/// </summary>
		/// <param name="abstraction">
		///    A <see cref="File.IFileAbstraction"/> object containing
		///    abstraction of the file to read.
		/// </param>
		/// <exception cref="ArgumentNullException">
		///    <paramref name="abstraction" /> is <see langword="null"
		///    />.
		/// </exception>
		public Picture (File.IFileAbstraction abstraction)
		{
			if (abstraction == null)
				throw new ArgumentNullException (nameof (abstraction));

			Data = ByteVector.FromFile (abstraction);
			Filename = abstraction.Name;
			Description = abstraction.Name;

			if (!string.IsNullOrEmpty (Filename) && Filename.Contains (".")) {
				MimeType = GetMimeFromExtension (Filename);
				Type = MimeType.StartsWith ("image/") ? PictureType.FrontCover : PictureType.NotAPicture;
			} else {
				string ext = GetExtensionFromData (Data);
				MimeType = GetMimeFromExtension (ext);
				if (ext != null) {
					Type = PictureType.FrontCover;
					Filename = Description = "cover" + ext;
				} else {
					Type = PictureType.NotAPicture;
					Filename = "UnknownType";
				}
			}
		}

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="Picture" /> by using the contents of a <see
		///    cref="ByteVector" /> object.
		/// </summary>
		/// <param name="data">
		///    A <see cref="ByteVector"/> object containing picture data
		///    to use.
		/// </param>
		/// <exception cref="ArgumentNullException">
		///    <paramref name="data" /> is <see langword="null" />.
		/// </exception>
		public Picture (ByteVector data)
		{
			if (data == null)
				throw new ArgumentNullException (nameof (data));

			Data = new ByteVector (data);
			string ext = GetExtensionFromData (data);
			MimeType = GetMimeFromExtension (ext);
			if (ext != null) {
				Type = PictureType.FrontCover;
				Filename = Description = "cover" + ext;
			} else {
				Type = PictureType.NotAPicture;
				Filename = "UnknownType";
			}
		}


		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="Picture" /> by doing a shallow copy of <see 
		///    cref="IPicture" />.
		/// </summary>
		/// <param name="picture">
		///    A <see cref="IPicture"/> object containing picture data
		///    to convert to an Picture.
		/// </param>
		public Picture (IPicture picture)
		{
			MimeType = picture.MimeType;
			Type = picture.Type;
			Filename = picture.Filename;
			Description = picture.Description;
			Data = picture.Data;
		}

		#endregion


		#region Legacy Factory methods

		/// <summary>
		///    Creates a new <see cref="Picture" />, populating it with
		///    the contents of a file.
		/// </summary>
		/// <param name="filename">
		///    A <see cref="string" /> object containing the path to a
		///    file to read the picture from.
		/// </param>
		/// <returns>
		///    A new <see cref="Picture" /> object containing the
		///    contents of the file and with a mime-type guessed from
		///    the file's contents.
		/// </returns>
		[Obsolete ("Use Picture(string filename) constructor instead.")]
		public static Picture CreateFromPath (string filename)
		{
			return new Picture (filename);
		}

		/// <summary>
		///    Creates a new <see cref="Picture" />, populating it with
		///    the contents of a file.
		/// </summary>
		/// <param name="abstraction">
		///    A <see cref="File.IFileAbstraction" /> object containing
		///    the file abstraction to read the picture from.
		/// </param>
		/// <returns>
		///    A new <see cref="Picture" /> object containing the
		///    contents of the file and with a mime-type guessed from
		///    the file's contents.
		/// </returns>
		[Obsolete ("Use Picture(File.IFileAbstraction abstraction) constructor instead.")]
		public static Picture CreateFromFile (File.IFileAbstraction abstraction)
		{
			return new Picture (abstraction);
		}

		#endregion



		#region Public Properties

		/// <summary>
		///    Gets and sets the mime-type of the picture data
		///    stored in the current instance.
		/// </summary>
		/// <value>
		///    A <see cref="string" /> object containing the mime-type
		///    of the picture data stored in the current instance.
		/// </value>
		public string MimeType { get; set; }

		/// <summary>
		///    Gets and sets the type of content visible in the picture
		///    stored in the current instance.
		/// </summary>
		/// <value>
		///    A <see cref="PictureType" /> containing the type of
		///    content visible in the picture stored in the current
		///    instance.
		/// </value>
		public PictureType Type { get; set; }

		/// <summary>
		///    Gets and sets a filename of the picture stored in the
		///    current instance.
		/// </summary>
		/// <value>
		///    A <see cref="string" /> object containing a fielname, with
		///    extension, of the picture stored in the current instance.
		/// </value>
		public string Filename { get; set; }

		/// <summary>
		///    Gets and sets a description of the picture stored in the
		///    current instance.
		/// </summary>
		/// <value>
		///    A <see cref="string" /> object containing a description
		///    of the picture stored in the current instance.
		/// </value>
		public string Description { get; set; }

		/// <summary>
		///    Gets and sets the picture data stored in the current
		///    instance.
		/// </summary>
		/// <value>
		///    A <see cref="ByteVector" /> object containing the picture
		///    data stored in the current instance.
		/// </value>
		public ByteVector Data { get; set; }

		#endregion



		#region Public static Methods (class functions)

		/// <summary>
		///    Retrieve a mime type from raw file data by reading
		///    the first few bytes of the file. 
		///    Less accurate than <see cref="GetExtensionFromMime"/>.
		/// </summary>
		/// <param name="data">
		///    file name with extension, or just extension of a file
		/// </param>
		/// <returns>File-extension as <see cref="string"/>, or null if 
		///    not identified</returns>
		public static string GetExtensionFromData (ByteVector data)
		{
			string ext = null;

			// No picture, unless it is corrupted, can fit in a file of less than 4 bytes
			if (data.Count >= 4) {
				if (data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
					ext = ".png";
				} else if (data[0] == 'G' && data[1] == 'I' && data[2] == 'F') {
					ext = ".gif";
				} else if (data[0] == 'B' && data[1] == 'M') {
					ext = ".bmp";
				} else if (data[0] == 0xFF && data[1] == 0xD8 && data[data.Count - 2] == 0xFF && data[data.Count - 1] == 0xD9) {
					ext = ".jpg";
				}

			}

			return ext;
		}

		/// <summary>
		///    Gets the file-extension that fits a mime-type. 
		///    More accurate than <see cref="GetExtensionFromData"/>.
		/// </summary>
		/// <param name="mime">
		///    Mime-type as <see cref="string"/>.
		/// </param>
		/// <returns>File-extension as <see cref="string"/>, or null if 
		///    not identified</returns>
		public static string GetExtensionFromMime (string mime)
		{
			// Default
			string ext = null;

			for (int i = 1; i < lutExtensionMime.Length; i += 2) {
				if (lutExtensionMime[i] == mime) {
					ext = lutExtensionMime[i - 1];
					break;
				}
			}

			return ext;
		}


		/// <summary>
		///    Gets the mime type of from a file-name (it's extensions). 
		///    If the format cannot be identified, it assumed to be a Binary file.
		/// </summary>
		/// <param name="name">
		///    file name with extension, or just extension of a file
		/// </param>
		/// <returns>Mime-type as <see cref="string"/></returns>
		public static string GetMimeFromExtension (string name)
		{
			// Default
			string mime_type = "application/octet-stream";

			// Get extension from Filename
			if (string.IsNullOrEmpty (name)) return mime_type;
			var ext = System.IO.Path.GetExtension (name);
			if (string.IsNullOrEmpty (ext))
				ext = name;
			else
				ext = ext.Substring (1);

			ext = ext.ToLower ();

			for (int i = 0; i < lutExtensionMime.Length; i += 2) {
				if (lutExtensionMime[i] == ext) {
					mime_type = lutExtensionMime[i + 1];
					break;
				}
			}

			return mime_type;
		}

		#endregion
	}
}
