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 _2DMemoryStream : Stream
	{
		public _2DMemoryStream(byte[][] backing)
		{
			this.backing = backing;
			this.myModuleLength = backing.Select(x => x.Length).Sum();
		}

		private byte[][] backing;
		private long currentPosition;
		private long myModuleLength;

		public override void Flush()
		{
		}

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

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

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

				position -= backing[i].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 = backing[requiredBlockId].Length - offsetInBlock;
			int bytesToCopy = Math.Min(remainingInBlock, count);
			int stillNeededToCopy = count - bytesToCopy;

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

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

				Array.Copy(backing[requiredBlockId], 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;
		}

		public override void SetLength(long value)
		{
			throw new NotSupportedException("Can't modify the length of a 2DArray");
		}

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

		public override bool CanRead => true;
		public override bool CanSeek => true;
		public override bool CanWrite => false;
		public override long Length => 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;
		}
	}
}
