using skyscraper5.Dvb.DataBroadcasting.Biop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Ietf.FLUTE
{
	internal class FluteListenerStream : Stream
	{

		public FluteListenerStream(List<FluteListener.FluteBlock> blocks)
		{
			this.blocks = blocks;
			this.myModuleLength = this.blocks.Select(x => x.Payload.LongLength).Sum();
		}

		private List<FluteListener.FluteBlock> blocks;
		private long currentPosition;
		private long myModuleLength;

		public override void Flush()
		{
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private int GetRequiredBlockId(long position)
		{
			for (int i = 0; i < blocks.Count; i++)
			{
				if (blocks[i].Payload.Length > position)
				{
					return i;
				}

				position -= blocks[i].Payload.Length;
			}
			throw new ArgumentOutOfRangeException(nameof(position));
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private int GetOffsetInBlock(long position)
		{
			for (int i = 0; i < blocks.Count; i++)
			{
				if (blocks[i].Payload.Length > position)
				{
					return (int)position;
				}

				position -= blocks[i].Payload.Length;
			}
			throw new ArgumentOutOfRangeException(nameof(position));
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			if (closed)
			{
				throw new ObjectDisposedException("This module is already disposed.");
			}

			if (Position + count > Length)
			{
				count = (int)(Length - Position);
			}

			if (Position == Length)
				return 0;

			int requiredBlockId = GetRequiredBlockId(currentPosition);
			int offsetInBlock = GetOffsetInBlock(currentPosition);
			int remainingInBlock = blocks[requiredBlockId].Payload.Length - offsetInBlock;
			int bytesToCopy = Math.Min(remainingInBlock, count);
			int stillNeededToCopy = count - bytesToCopy;

			Array.Copy(blocks[requiredBlockId].Payload, offsetInBlock, buffer, offset, bytesToCopy);
			Position += bytesToCopy;
			int result = bytesToCopy;
			offset += bytesToCopy;
			count -= bytesToCopy;

			while (stillNeededToCopy > 0)
			{
				requiredBlockId++;
				offsetInBlock = 0;
				remainingInBlock = blocks[requiredBlockId].Payload.Length - offsetInBlock;
				bytesToCopy = Math.Min(remainingInBlock, count);
				stillNeededToCopy = count - bytesToCopy;

				Array.Copy(blocks[requiredBlockId].Payload, offsetInBlock, buffer, offset, bytesToCopy);
				Position += bytesToCopy;
				result += bytesToCopy;
				offset += bytesToCopy;
				count -= bytesToCopy;
			}
			return result;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			switch (origin)
			{
				case SeekOrigin.Begin:
					currentPosition = offset;
					break;
				case SeekOrigin.Current:
					currentPosition += offset;
					break;
				case SeekOrigin.End:
					currentPosition = Length + offset;
					break;
				default:
					throw new NotImplementedException(origin.ToString());
			}
			return currentPosition;
		}

		private bool _isLengthLimited;
		private long _limitedLength;
		public override void SetLength(long value)
		{
			if (value > myModuleLength)
			{
				throw new ArgumentOutOfRangeException(String.Format("Argument must be between 0 and {0}", myModuleLength));
			}

			_limitedLength = value;
			_isLengthLimited = true;
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotSupportedException("Can't write to a ModuleInfo");
		}

		public override bool CanRead => true;
		public override bool CanSeek => true;
		public override bool CanWrite => false;

		public override long Length
		{
			get
			{
				if (_isLengthLimited)
					return _limitedLength;
				else
					return myModuleLength;
			}
		}

		public override long Position
		{
			get => currentPosition;
			set => Seek(value, SeekOrigin.Begin);
		}

		public override bool CanTimeout => false;

		private bool closed;
		public override void Close()
		{
			closed = true;
		}
	}
}
