//
// PictureLazy.cs:
//
// Author:
//   Sebastien Mouy <starwer@laposte.net>
//
// Copyright (C) 2018 Starwer
//
// 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;

namespace TagLib
{

	/// <summary>
	///    This class implements <see cref="IPicture" /> and provides
	///    mechanisms for loading pictures from files.
	///    Contrary to <see cref="Picture" />, a reference to a file
	///    where the picture is located can be given and the picture 
	///    is lazily loaded from the file, meaning that it will be 
	///    read from the file only when needed. This saves time and 
	///    memory if the picture loading is not required.
	/// </summary>
	public class PictureLazy : IPicture, ILazy
	{
		#region Private Fields

		/// <summary>
		///    Contains the mime-type.
		/// </summary>
		string mime_type;

		/// <summary>
		///    Contains the content type.
		/// </summary>
		PictureType type;

		/// <summary>
		///    Contains the filename.
		/// </summary>
		string filename;

		/// <summary>
		///    Contains the picture data.
		/// </summary>
		ByteVector data;


		/// <summary>
		/// Stream where the picture is located
		/// </summary>
		File.IFileAbstraction file;

		/// <summary>
		/// Offset from where the picture start in the <see cref="file"/>
		/// </summary>
		readonly long stream_offset;

		/// <summary>
		/// Size of the picture in the <see cref="file"/> (-1 = until end of Stream)
		/// </summary>
		readonly long stream_size = -1;

		#endregion


		#region Constructors

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

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="PictureLazy" /> from a file.
		///    The content will be lazily loaded.
		/// </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 PictureLazy (string path)
		{
			if (path == null)
				throw new ArgumentNullException (nameof (path));

			file = new File.LocalFileAbstraction (path);

			filename = Path.GetFileName (path);
			Description = filename;
			mime_type = Picture.GetMimeFromExtension (filename);
			type = mime_type.StartsWith ("image/") ? PictureType.FrontCover : PictureType.NotAPicture;
		}

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="PictureLazy" /> from a file abstraction.
		///    The content will be lazily loaded.
		/// </summary>
		/// <param name="abstraction">
		///    A <see cref="File.IFileAbstraction"/> object containing
		///    abstraction of the file to read.
		/// </param>
		/// <param name="offset">
		///    The position in bytes where the picture is located in the
		///    <see cref="File.IFileAbstraction"/>.
		/// </param>
		/// <param name="size">
		///    The size in bytes of the picture in the
		///    <see cref="File.IFileAbstraction"/> (default: read all).
		/// </param>
		/// <exception cref="ArgumentNullException">
		///    <paramref name="abstraction" /> is <see langword="null"
		///    />.
		/// </exception>
		public PictureLazy (File.IFileAbstraction abstraction, long offset = 0, long size = -1)
		{
			if (abstraction == null)
				throw new ArgumentNullException (nameof (abstraction));


			file = abstraction;
			stream_offset = offset;
			stream_size = size;

			filename = abstraction.Name;
			Description = abstraction.Name;

			if (!string.IsNullOrEmpty (filename) && filename.Contains (".")) {
				mime_type = Picture.GetMimeFromExtension (filename);
				type = mime_type.StartsWith ("image/") ? PictureType.FrontCover : PictureType.NotAPicture;
			}
		}

		/// <summary>
		///    Constructs and initializes a new instance of <see
		///    cref="PictureLazy" /> by using the contents of a <see
		///    cref="ByteVector" /> object.
		///    The content will not be lazily loaded.
		/// </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 PictureLazy (ByteVector data)
		{
			if (data == null)
				throw new ArgumentNullException (nameof (data));

			Data = new ByteVector (data);
			string ext = Picture.GetExtensionFromData (data);
			MimeType = Picture.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="PictureLazy" /> by doing a shallow copy of <see 
		///    cref="IPicture" />.
		///    The content will not be lazily loaded.
		/// </summary>
		/// <param name="picture">
		///    A <see cref="IPicture"/> object containing picture data
		///    to convert to an Picture.
		/// </param>
		public PictureLazy (IPicture picture)
		{
			mime_type = picture.MimeType;
			type = picture.Type;
			filename = picture.Filename;
			Description = picture.Description;
			data = picture.Data;
		}



		#endregion


		#region Public Methods

		/// <summary>
		/// Load the picture data from the file,
		/// if not done yet.
		/// </summary>
		public void Load ()
		{
			// Already loaded ?
			if (data != null) return;


			// Load the picture from the stream

			Stream stream = null;

			try {
				if (stream_size == 0) {
					data = new ByteVector ();
				} else if (stream_size > 0) {
					stream = file.ReadStream;
					stream.Seek (stream_offset, SeekOrigin.Begin);

					int count = 0, read = 0, needed = (int)stream_size;
					byte[] buffer = new byte[needed];

					do {
						count = stream.Read (buffer, read, needed);

						read += count;
						needed -= count;
					} while (needed > 0 && count != 0);

					data = new ByteVector (buffer, read);
				} else {
					stream = file.ReadStream;
					stream.Seek (stream_offset, SeekOrigin.Begin);

					data = ByteVector.FromStream (stream);
				}

			} finally {
				// Free the resources
				if (stream != null && file != null) {
					file.CloseStream (stream);
				}

				file = null;
			}

			// Retrieve remaining properties from data (if required)

			if (mime_type == null) {
				string ext = Picture.GetExtensionFromData (data);
				MimeType = Picture.GetMimeFromExtension (ext);
				if (ext != null) {
					type = PictureType.FrontCover;
					if (filename == null)
						filename = Description = "cover" + ext;
				} else {
					type = PictureType.NotAPicture;
					if (filename == null)
						filename = "UnknownType";
				}
			}
		}

		#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 {
				if (mime_type == null)
					Load ();
				return mime_type;
			}
			set { mime_type = value; }
		}

		/// <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 {
				if (type == PictureType.Other && mime_type == null)
					Load ();
				return type;
			}
			set { type = value; }
		}

		/// <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 {
				if (filename == null)
					Load ();
				return filename;
			}
			set { filename = value; }
		}

		/// <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 {
				if (data == null)
					Load ();
				return data;
			}
			set { data = value; }
		}


		/// <summary>
		///    Gets an indication whether the picture is loaded.
		/// </summary>
		public bool IsLoaded {
			get {
				return data != null;
			}
		}

		#endregion

	}
}
