using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.IO;
using System.IO.Compression;
using System.Linq;

namespace LeafWeb.Core.Entities
{
	public class LeafInput : ILeafInput
	{
		public int Id { get; set; }

		public virtual ICollection<LeafInputFile> InputFiles { get; set; }
		public virtual ICollection<LeafInputData> LeafInputData { get; set; }
		public virtual ICollection<LeafOutputFile> OutputFiles { get; set; }

		[Required(ErrorMessage = "Name required")]
		public string Name { get; set; }

		[Required(ErrorMessage = "An email address is required")]
		public string Email { get; set; }

		[Required(ErrorMessage = "A unique identifier is required")]
		public string Identifier { get; set; }

		[Required(ErrorMessage = "Site Id required")]
		public string SiteId { get; set; }

		[Required]
		[Column(TypeName = "VARCHAR")]
		[StringLength(12)]
		[Index("IX_UniqueToken", 1, IsUnique = true)]
		public string UniqueToken { get; set; }

		// [Required(ErrorMessage = "PhotosynthesisType required")]
		// http://stackoverflow.com/questions/6038541/ef-validation-failing-on-update-when-using-lazy-loaded-required-properties
		public virtual PhotosynthesisType PhotosynthesisType { get; set; }

		[DataType(DataType.Date)]
		[Required]
		public DateTime Added { get; set; }

		public LeafInputStatusType CurrentStatus { get; set; }

		public virtual ICollection<LeafInputStatus> StatusHistory { get; set; }

		public Priority PendingPriority { get; set; }

		#region Calculated properties

		public LeafOutputFile OutputErrorMessage => OutputFiles?.FirstOrDefault(f => f.IsErrorMessage);
		public LeafOutputFile OutputWarningMessage => OutputFiles?.FirstOrDefault(f => f.IsWarningMessage);
		public bool HasOutputFiles => OutputFiles.Any();
		public bool HasLeafChart => OutputFiles.Any(f => f.IsLeafChartFile);

		public bool IsPending => CurrentStatus == LeafInputStatusType.Pending;
		public bool IsStarting => CurrentStatus == LeafInputStatusType.Starting;
		public bool IsRunning => CurrentStatus == LeafInputStatusType.Running;
		public bool IsFinishing => CurrentStatus == LeafInputStatusType.Finishing;
		public bool IsComplete => CurrentStatus == LeafInputStatusType.Complete;
		public bool IsException => CurrentStatus == LeafInputStatusType.Exception;
		public bool IsCancelPending => CurrentStatus == LeafInputStatusType.CancelPending;
		public bool IsCancelling => CurrentStatus == LeafInputStatusType.Cancelling;
		public bool IsCancelled => CurrentStatus == LeafInputStatusType.Cancelled;
	    public bool IsUnresponsive => CurrentStatus == LeafInputStatusType.Unresponsive;

		public bool IsInProgress => IsStarting
								 || IsRunning
								 || IsFinishing
								 || IsCancelPending
								 || IsCancelling
	                             || IsUnresponsive;

		public bool IsCancellable =>
								IsRunning
								|| IsPending
	                            || IsUnresponsive;

		public bool IsDeletable => !IsInProgress;
		public bool IsAtEndState => IsComplete || IsException || IsCancelled;

		public DateTime? StartTime
		{
			get
			{
				if (IsPending)
				{
					return null;
				}
				return StatusHistory.FirstOrDefault(s => s.Status == LeafInputStatusType.Starting)?.DateTime;
			}
		}

		public DateTime? EndTime
		{
			get
			{
				if (!IsAtEndState)
				{
					return null;
				}
				return StatusHistory.FirstOrDefault(
					s =>
						s.Status == LeafInputStatusType.Complete
						|| s.Status == LeafInputStatusType.Exception
						|| s.Status == LeafInputStatusType.Cancelled)?.DateTime;
			}
		}

		public DateTime LastStatusChange => StatusHistory.Last().DateTime;

		public TimeSpan TimeInProgress
		{
			get
			{
				var start = StartTime;
				if (!start.HasValue)
				{
					return TimeSpan.Zero;
				}

				var end = EndTime;
				if (!end.HasValue)
				{
					return DateTime.Now - start.Value;
				}

				return end.Value - start.Value;
			}
		}

		#endregion

		public override string ToString()
		{
			return $"{Id}_{Identifier}";
		}

		/// <summary>
		/// Contains all output files in a zip
		/// </summary>
		public byte[] GetOutputFileZip(LeafOutputFileType? fileType)
		{
			using (var compressedFileStream = new MemoryStream())
			{
				using (var archive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, true))
				{
					foreach (var outputFile in OutputFiles.Where(f => !fileType.HasValue || f.FileType == fileType.Value))
					{
						var entry = archive.CreateEntry(outputFile.Filename);
						using (var originalFileStream = new MemoryStream(outputFile.FileContents.Contents))
						using (var entryStream = entry.Open())
							originalFileStream.CopyTo(entryStream);
					}
				}
				return compressedFileStream.ToArray();
			}
		}
		
		/// <summary>
		/// Contains all input files in a zip
		/// </summary>
		public byte[] GetInputFileZip()
		{
			using (var compressedFileStream = new MemoryStream())
			{
				using (var archive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, true))
				{
					foreach (var inputFile in InputFiles)
					{
						var entry = archive.CreateEntry(inputFile.Filename);
						using (var originalFileStream = new MemoryStream(inputFile.Contents))
						using (var entryStream = entry.Open())
							originalFileStream.CopyTo(entryStream);
					}
				}
				return compressedFileStream.ToArray();
			}
		}

		public int GetOutputFileSizeSum()
		{
			return OutputFiles.Sum(o => o.FileContents.Contents.Length);
		}
	}
}