using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.IO;

namespace Sample3_CustomVirtualFile
{
    class CustomFileStream : IStream
    {
        private long _Offset;
        private long _Length;
        private string _FilePath;
        private Stream _Stream;

        public CustomFileStream(string FilePath, long Offset, long Length)
        {
            _FilePath = FilePath;
            _Stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            _Offset = Offset;
            _Length = Length;
        }

        public CustomFileStream(string FilePath, long Offset)
        {
            _FilePath = FilePath;
            _Stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            _Offset = Offset;
            _Length = _Stream.Length - _Offset;
        }

        #region IStream Members

        public void Read(byte[] pv, int cb, IntPtr pcbRead)
        {
            if (_Stream.Position > _Offset + _Length)
                cb = 0;
            else if (_Stream.Position + cb > _Offset + _Length)
                cb = (int)(_Offset + _Length - _Stream.Position);

            int nReadBytes = _Stream.Read(pv, 0, cb);

            if (IntPtr.Zero != pcbRead)
                Marshal.WriteIntPtr(pcbRead, new IntPtr(nReadBytes));
        }

        public void Write(byte[] pv, int cb, IntPtr pcbWritten)
        {
            if (_Stream.Position > _Offset + _Length)
                cb = 0;
            else if (_Stream.Position + cb > _Offset + _Length)
                cb = (int)(_Offset + _Length - _Stream.Position);

            int nWrittenBytes = _Stream.Read(pv, 0, cb);

            if (IntPtr.Zero != pcbWritten)
                Marshal.WriteIntPtr(pcbWritten, new IntPtr(nWrittenBytes));
        }

        public void Clone(out IStream ppstm)
        {
            ppstm = new CustomFileStream(_FilePath, _Offset, _Length);
        }

        public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
        {
            SeekOrigin Origin = (SeekOrigin)dwOrigin;
            long NewPosition = 0;

            switch (Origin)
            {
                case SeekOrigin.Begin:
                    {
                        NewPosition = _Stream.Seek(_Offset + dlibMove, Origin);

                        break;
                    }
                case SeekOrigin.Current:
                    {
                        NewPosition = _Stream.Seek(dlibMove, Origin);

                        break;
                    }
                case SeekOrigin.End:
                    {
                        NewPosition = _Stream.Seek(_Offset + _Length + dlibMove, SeekOrigin.Begin);

                        break;
                    }
            }

            NewPosition -= _Offset;

            if (NewPosition < 0)
                NewPosition = 0;
            else if (NewPosition > _Length)
                NewPosition = _Length;

            if (IntPtr.Zero != plibNewPosition)
                Marshal.WriteInt64(plibNewPosition, NewPosition);
        }

        public void Commit(int grfCommitFlags)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void LockRegion(long libOffset, long cb, int dwLockType)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void Revert()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void SetSize(long libNewSize)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
        {
            pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
            pstatstg.cbSize = _Length;
        }

        public void UnlockRegion(long libOffset, long cb, int dwLockType)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    }
}
