using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.DsmCc.Message;
using skyscraper5.Dvb.DataBroadcasting.Biop;

namespace skyscraper5.Dvb.DataBroadcasting
{
    internal class ModuleInfoStream : Stream
    {
        public ModuleInfoStream(ModuleInfo mi, bool allowDispose)
        {
            this.myModule = mi;
            this.myModuleLength = mi.Blocks.Select(x => x.BlockData.Length).Sum();
            this.allowDispose = allowDispose;
        }

        private bool allowDispose;
        private ModuleInfo myModule;
        private long currentPosition;
        private long myModuleLength;

        public override void Flush()
        {
        }

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

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

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

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

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

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

                Array.Copy(myModule.Blocks[requiredBlockId].BlockData, 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 ModuleInfo");
        }

        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 => 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;

            for (int i = 0; i < myModule.Blocks.Length; i++)
            {
                if (myModule.Blocks[i] != null)
                {
                    myModule.Blocks[i].ForgetBlock();
                    myModule.Blocks[i] = null;
                }
            }
        }
    }
}
