using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static Vanara.PInvoke.BITS;

namespace Vanara.IO
{
	/// <summary>Information about a file in a background copy job.</summary>
	public class BackgroundCopyFileInfo
	{
		internal BG_FILE_INFO fi;

		private IBackgroundCopyFile iFile;

		internal BackgroundCopyFileInfo(IBackgroundCopyFile ibgfile)
		{
			iFile = ibgfile;
		}

		internal BackgroundCopyFileInfo(BG_FILE_INFO bgfi)
		{
			fi = bgfi;
		}

		/// <summary>Retrieves the ranges that you want to download from the remote file.</summary>
		/// <value>The file copy ranges.</value>
		public IEnumerable<BackgroundCopyFileRange> FileRanges
		{
			get
			{
				IFile2.GetFileRanges(out var cnt, out var rng);
				return rng.ToEnumerable<BackgroundCopyFileRange>((int)cnt);
			}
		}

		/// <summary>Returns the set of file ranges that have been downloaded.</summary>
		/// <value>The file copy ranges that have been downloaded. Ranges will be merged together as much as possible. The ranges are ordered by offset.</value>
		public IEnumerable<BackgroundCopyFileRange> FilledFileRanges
		{
			get
			{
				IFile6.GetFilledFileRanges(out var cnt, out var rng);
				return rng.ToEnumerable<BackgroundCopyFileRange>((int)cnt);
			}
		}

		/// <summary>Gets or sets a value indicating whether the contents of the file are valid.</summary>
		/// <value><c>true</c> if the file content is valid; otherwise, <c>false</c>.</value>
		public bool IsFileContentValid { get => IFile3.GetValidationState(); set => IFile3.SetValidationState(value); }

		/// <summary>
		/// Size of the file in bytes. If the value is -1, the total size of the file has not been determined. BITS does not set this value if it cannot
		/// determine the size of the file. For example, if the specified file or server does not exist, BITS cannot determine the size of the file. If you are
		/// downloading ranges from a file, BytesTotal reflects the total number of bytes you want to download from the file.
		/// </summary>
		public long Length => (long)CopyProgress.BytesTotal;

		/// <summary>Retrieves the local name of the file.</summary>
		public string LocalFilePath
		{
			get => iFile == null ? fi.LocalName : iFile.GetLocalName();
			set
			{
				if (iFile != null)
					throw new InvalidOperationException("You cannot change the LocalFilePath property on CurrentFileSet results.");
				fi.LocalName = value;
			}
		}

		/// <summary>Gets the amount of file data downloaded from the originating server.</summary>
		public ulong OriginServerDownloadBytes { get { IFile4.GetPeerDownloadStats(out var o, out var _); return o; } }

		/// <summary>Gets the amount of file data downloaded from a peer-to-peer source.</summary>
		public ulong PeerDownloadBytes { get { IFile4.GetPeerDownloadStats(out var _, out var p); return p; } }

		/// <summary>Gets the percentage of the transfer that has completed.</summary>
		public float PercentComplete
		{
			get
			{
				var p = CopyProgress;
				return p.Completed || p.BytesTotal == 0 ? 100.0F : p.BytesTransferred * 100.0F / p.BytesTotal;
			}
		}

		/// <summary>Retrieves the remote name of the file.</summary>
		public string RemoteFilePath
		{
			get => iFile == null ? fi.RemoteName : iFile.GetRemoteName();
			set
			{
				fi.RemoteName = value;
				(iFile as IBackgroundCopyFile2)?.SetRemoteName(value);
			}
		}

		/// <summary>Gets or sets the full set of HTTP response headers from the server's last HTTP response packet.</summary>
		/// <value>The response headers.</value>
		public string ResponseHeaders
		{
			get
			{
				var hdr = IFile5.GetProperty(BITS_FILE_PROPERTY_ID.BITS_FILE_PROPERTY_ID_HTTP_RESPONSE_HEADERS);
				return hdr.String;
			}
			set
			{
				var hdr = new BITS_FILE_PROPERTY_VALUE { String = value };
				IFile5.SetProperty(BITS_FILE_PROPERTY_ID.BITS_FILE_PROPERTY_ID_HTTP_RESPONSE_HEADERS, hdr);
			}
		}

		/// <summary>Gets the full path of the temporary file that contains the content of the download.</summary>
		public string TemporaryName => IFile3.GetTemporaryName();

		/// <summary>
		/// For downloads, the value is TRUE if the file is available to the user; otherwise, the value is FALSE. Files are available to the user after calling
		/// the <see cref="BackgroundCopyJob.Complete"/> method. If the <see cref="BackgroundCopyJob.Complete"/> method generates a transient error, those files
		/// processed before the error occurred are available to the user; the others are not. Use the Completed property to determine if the file is available
		/// to the user when Complete fails. For uploads, the value is TRUE when the file upload is complete; otherwise, the value is FALSE.
		/// </summary>
		public bool TransferCompleted => CopyProgress.Completed;

		/// <summary>Gets the number of bytes transferred.</summary>
		public long BytesTransferred => (long)CopyProgress.BytesTransferred;

		/// <summary>Gets a value that determines if any part of the file was downloaded from a peer.</summary>
		/// <value>Is TRUE if any part of the file was downloaded from a peer; otherwise, FALSE.</value>
		public bool IsDownloadedFromPeer => IFile3.IsDownloadedFromPeer();

		/// <summary>Retrieves the progress of the file transfer.</summary>
		internal BG_FILE_PROGRESS CopyProgress => iFile?.GetProgress() ?? throw new InvalidOperationException("You can only get the CopyProgress on CurrentFileSet results.");

		private IBackgroundCopyFile2 IFile2 => GetDerived<IBackgroundCopyFile2>();

		private IBackgroundCopyFile3 IFile3 => GetDerived<IBackgroundCopyFile3>();

		private IBackgroundCopyFile4 IFile4 => GetDerived<IBackgroundCopyFile4>();

		private IBackgroundCopyFile5 IFile5 => GetDerived<IBackgroundCopyFile5>();

		private IBackgroundCopyFile6 IFile6 => GetDerived<IBackgroundCopyFile6>();

		/// <summary>Adds a new set of file ranges to be prioritized for download.</summary>
		/// <param name="ranges">
		/// An array of file ranges to be downloaded. Requested ranges are allowed to overlap previously downloaded (or pending) ranges. Ranges are automatically
		/// split into non-overlapping ranges.
		/// </param>
		public void RequestFileRanges(BackgroundCopyFileRange[] ranges)
		{
			IFile6.RequestFileRanges((uint)ranges.Length, Array.ConvertAll(ranges, r => r.fr));
		}

		/// <summary>Returns a <see cref="System.String" /> that represents this instance.</summary>
		/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
		public override string ToString() => $"{LocalFilePath} => {RemoteFilePath}";

		/// <summary>Specifies a position to prioritize downloading missing data from.</summary>
		/// <param name="offset">Specifies the new position to prioritize downloading missing data from.</param>
		public void UpdateDownloadPosition(ulong offset) => IFile6.UpdateDownloadPosition(offset);

		private T GetDerived<T>() where T : class
		{
			T ret = iFile as T;
			return ret ?? throw new PlatformNotSupportedException();
		}
	}

	/// <summary>Identifies a range of bytes to download from a file.</summary>
	[StructLayout(LayoutKind.Sequential)]
	public class BackgroundCopyFileRange
	{
		internal BG_FILE_RANGE fr;

		/// <summary>Zero-based offset to the beginning of the range of bytes to download from a file.</summary>
		public ulong InitialOffset { get => fr.InitialOffset; set => fr.InitialOffset = value; }

		/// <summary>
		/// The length of the range, in bytes. Do not specify a zero byte length. To indicate that the range extends to the end of the file, specify <c>BG_LENGTH_TO_EOF</c>.
		/// </summary>
		public ulong Length { get => fr.Length; set => fr.Length = value; }

		/// <summary>Performs an implicit conversion from <see cref="BG_FILE_RANGE"/> to <see cref="BackgroundCopyFileRange"/>.</summary>
		/// <param name="p">The BG_FILE_RANGE instance.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator BackgroundCopyFileRange(BG_FILE_RANGE p) => new BackgroundCopyFileRange { fr = p };
	}
}