using log4net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Skyscraper.IO
{
	public class M3U8Stream : Stream
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		public M3U8Stream(string source) 
		{
			this.segmentIndex = 0;
			this.segmentNames = new string[0];
			this.source = source;
			this.finishedSegments = new List<string>();
			this.innerStream = new EmptyStream();
			this.sourceRoot = GetRoot(source);

			if (source.StartsWith("http://") || source.StartsWith("https://"))
			{
				webClient = new HttpClient();
				webClient.BaseAddress = new Uri(sourceRoot);
			}
			else if (File.Exists(source))
			{
				this.rootDirectory = new DirectoryInfo(sourceRoot);
			}
			else
			{
				throw new NotImplementedException(source);
			}
		}

		private int segmentIndex;
		private string[] segmentNames;
		private string source;
		private List<string> finishedSegments;
		private Stream innerStream;
		private string sourceRoot;

		private HttpClient webClient;
		private DirectoryInfo rootDirectory;

		private string GetRoot(string source)
		{
			if (source.StartsWith("http://") || source.StartsWith("https://"))
			{
				while (!source.EndsWith("/"))
				{
					source = source.Substring(0, source.Length - 1);
				}
				return source;
			}
			else if (File.Exists(source))
			{
				FileInfo fi = new FileInfo(source);
				DirectoryInfo di = fi.Directory;
				return di.FullName;
			}
			else
			{
				throw new NotImplementedException(source);
			}
		}

		private DateTime lastSegmentSwitch;
		private Stream GetSegmentStream(string segmentName)
		{
			if (lastSegmentSwitch == DateTime.MinValue)
			{
				lastSegmentSwitch = DateTime.Now;
				logger.DebugFormat("open segment {0}", segmentName);
			}
			else
			{
				TimeSpan timeTaken = DateTime.Now - lastSegmentSwitch;
				lastSegmentSwitch = DateTime.Now;
				logger.DebugFormat("open segment {0}, processing took {1}", segmentName, timeTaken.ToString());
			} 
			//Debug.WriteLine(String.Format("open segment {0}", segmentName));
			if (source.StartsWith("http://") || source.StartsWith("https://"))
			{
				return webClient.GetStreamAsync(segmentName).Result;
			}
			else if (File.Exists(source))
			{
				string partFileName = Path.Combine(rootDirectory.FullName, segmentName);
				FileInfo fi = new FileInfo(partFileName);
				return fi.OpenRead();
			}
			else
			{
				throw new NotImplementedException(source);
			}
		}

		private bool ReloadSegmentList()
		{
			string[] newLines;
			if (source.StartsWith("http://") || source.StartsWith("https://"))
			{
				byte[] result = webClient.GetByteArrayAsync(source).Result;
				MemoryStream ms = new MemoryStream(result, false);
				StreamReader streamReader = new StreamReader(ms);
				List<string> lines = new List<string>();
				while (!streamReader.EndOfStream)
				{
					lines.Add(streamReader.ReadLine());
				}
				streamReader.Close();
				ms.Close();
				newLines = lines.ToArray();
			}
			else if (File.Exists(source))
			{
				newLines = File.ReadAllLines(source);
			}
			else
			{
				throw new NotImplementedException(source);
			}

			if (newLines.Length != segmentNames.Length)
			{
				segmentNames = newLines;
				return true;
			}
			for (int i = 0; i < System.Math.Min(newLines.Length, segmentNames.Length); i++)
			{
				if (!segmentNames[i].Equals(newLines[i]))
				{
					segmentNames = newLines;
					return true;
				}
			}

			return false;
		}

		private bool GetNextSegment()
		{
			for (int i = segmentIndex; i < segmentNames.Length; i++)
			{
				if (segmentNames[i].StartsWith("#"))
					continue;
				if (finishedSegments.Contains(segmentNames[i]))
					continue;
				if (segmentNames[i].EndsWith(".m3u8"))
				{
					this.source = String.Format("{0}{1}",GetRoot(source), segmentNames[i]);
					ReloadSegmentList();
					return GetNextSegment();
				}
				if (string.IsNullOrEmpty(segmentNames[i]))
					continue;

				this.segmentIndex = i;
				this.finishedSegments.Add(segmentNames[i]);
				this.innerStream = GetSegmentStream(segmentNames[i]);
				return true;
			}
			if (ReloadSegmentList())
			{
				return GetNextSegment();
			}
			return false;
		}

		public override bool CanRead => throw new NotImplementedException();

		public override bool CanSeek => false;

		public override bool CanWrite => false;

		public override long Length => throw new NotSupportedException();

		public override long Position { get => actuallyRead; set => throw new NotSupportedException(); }

		public override void Flush()
		{
			throw new NotImplementedException();
		}

		private long actuallyRead;
		public override int Read(byte[] buffer, int offset, int count)
		{
			int result = 0;
			while (count > 0)
			{
				int readResult = innerStream.Read(buffer, offset, count);
				if (readResult == 0)
				{
					if (!GetNextSegment())
					{
						actuallyRead += result;
						return result;
					}
					continue;
				}
				offset += readResult;
				count -= readResult;
				result += readResult;
			}
			actuallyRead += result;
			return result;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotImplementedException();
		}

		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotImplementedException();
		}
	}
}
