﻿namespace MPFX.OpenCL.IO
{
    using System;
    using System.IO;

    public class StreamSegment : Stream
    {
        private Int64 length;
        private Int64 position;
        private Stream BaseStream;
        private Int64Segment[] Segments;

        private int IterateOverStream(byte[] buffer, int offset, int count, bool read)
        {
            if (this.Position >= this.Length) return 0;
            if (!read && (this.Length - this.Position < count)) throw new IOException("Data cannon fit the stream");

            long LocalPosition = this.Position;
            int CurrentSegmentIndex = 0;
            while (LocalPosition >= this.Segments[CurrentSegmentIndex].Length)
            {
                CurrentSegmentIndex++;
                LocalPosition -= this.Segments[CurrentSegmentIndex].Length;
            }

            int CurrentBufferOffset = offset;
            int BytesLeft = count;
            int BytesProcessed = 0;


            while ((CurrentSegmentIndex < this.Segments.Length) && (BytesLeft > 0))
            {
                this.BaseStream.Position = this.Segments[CurrentSegmentIndex].A + LocalPosition;
                int BytesToRequest = (int)(this.Segments[CurrentSegmentIndex].Length - LocalPosition);
                int BytesJustProcessed;
                if (read)
                    BytesJustProcessed = this.BaseStream.Read(buffer, CurrentBufferOffset, BytesToRequest);
                else
                {
                    this.BaseStream.Write(buffer, CurrentBufferOffset, BytesToRequest);
                    BytesJustProcessed = BytesToRequest;
                }
                BytesLeft -= BytesJustProcessed;
                BytesProcessed += BytesJustProcessed;
                CurrentBufferOffset += BytesJustProcessed;
                LocalPosition = 0;
                CurrentSegmentIndex++;
            }

            this.Position += BytesProcessed;

            return BytesProcessed;
        }

        public StreamSegment(Stream stream, params Int64Segment[] segments)
        {
            this.Segments = segments;
            this.BaseStream = stream;

            this.length = 0;
            this.position = 0;

            foreach (Int64Segment segment in this.Segments)
            {
                this.length += segment.Length;
            }
        }

        public override bool CanRead
        {
            get { return this.BaseStream.CanRead; }
        }
        public override bool CanSeek
        {
            get { return this.BaseStream.CanSeek; }
        }
        public override bool CanWrite
        {
            get { return this.BaseStream.CanWrite; }
        }
        public override void Flush()
        {
            this.BaseStream.Flush();
        }
        public override long Length
        {
            get { return this.length; }
        }
        public override long Position
        {
            get
            {
                return this.position;
            }
            set
            {
                this.Seek(value, SeekOrigin.Begin);
            }
        }
        public override long Seek(long offset, SeekOrigin origin)
        {
            switch (origin)
            {
                case SeekOrigin.Begin: this.position = offset;
                    break;
                case SeekOrigin.Current: this.position += offset;
                    break;
                case SeekOrigin.End: this.position = this.Length + offset;
                    break;
            }

            return this.Position;
        }
        public override void SetLength(long value)
        {
            throw new NotSupportedException(
                "This class should not be used to modify the size of the stream");
        }
        public override int Read(byte[] buffer, int offset, int count)
        {
            return this.IterateOverStream(buffer, offset, count, true);
        }
        public override void Write(byte[] buffer, int offset, int count)
        {
            this.IterateOverStream(buffer, offset, count, false);
        }
    }
}
