Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
Imports System.Security.Cryptography
Imports System.IO
Imports System.Diagnostics

Public Class VirtualFileStream
    Implements IStream

    Private _EncryptedVideoFilePath As String
    Private _EncryptedVideoFile As Stream
    Private _EncryptedChunkLength() As Integer
    Private _EncryptedChunkPosition() As Long
    Private _SourceChunkLength() As Integer
    Private _ChunkCount As Integer
    Private _CurrentChunk(Globals.ChunkSize - 1) As Byte
    Private _CurrentChunkIndex As Long = -1
    Private _Position As Long = 0
    Private _Length As Long
    Private _Lock As Object = New Object()

    Public Sub New(ByVal EncryptedVideoFilePath As String)
        _EncryptedVideoFilePath = EncryptedVideoFilePath
        _EncryptedVideoFile = File.Open(EncryptedVideoFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)

        ' Read chunk information
        Using EncryptedVideoFileStream As Stream = File.Open(EncryptedVideoFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)
            Using Reader As BinaryReader = New BinaryReader(EncryptedVideoFileStream)
                _Length = 0

                Dim SizeOfInt As Integer = 4

                EncryptedVideoFileStream.Position = _EncryptedVideoFile.Length - SizeOfInt
                _ChunkCount = Reader.ReadInt32()

                EncryptedVideoFileStream.Position = _EncryptedVideoFile.Length - SizeOfInt - _ChunkCount * SizeOfInt - _ChunkCount * SizeOfInt

                _EncryptedChunkLength = New Integer(_ChunkCount - 1) {}
                _SourceChunkLength = New Integer(_ChunkCount - 1) {}
                _EncryptedChunkPosition = New Long(_ChunkCount - 1) {}

                Dim i As Integer = 0
                For i = 0 To _ChunkCount - 1
                    _SourceChunkLength(i) = Reader.ReadInt32()
                    _Length = _Length + _SourceChunkLength(i)
                Next

                Dim Offset As Long = 0

                For i = 0 To _ChunkCount - 1
                    _EncryptedChunkLength(i) = Reader.ReadInt32()
                    _EncryptedChunkPosition(i) = Offset

                    Offset = Offset + _EncryptedChunkLength(i)
                Next
            End Using
        End Using
    End Sub

    Sub Read(ByVal pv() As Byte, ByVal cb As Integer, ByVal pcbRead As System.IntPtr) Implements IStream.Read
        Dim ReadBytes As Integer

        If _Position < 0 Or _Position > _Length Then
            ReadBytes = 0
        Else
            ' Let's protect _Position: _Position might be changed by another Read() or Seek()
            SyncLock (_Lock)
                Dim TotalReadBytes As Integer = 0
                Dim RestBytesToCopy As Integer = cb

                Dim OffsetInOutput As Integer = 0

                ' Let's move chunk by chunk until all requested data is read or end of file reached
                While RestBytesToCopy > 0 And _Position < _Length
                    ' Original data is splitted into chunks, so let's find a chunk number that corresponds
                    ' to current position
                    Dim RequiredChunkIndex As Long = _Position \ Globals.ChunkSize

                    ' We do cache decrypted data, so let's update the cache if either it's not initialized
                    ' or cached chunk has another index
                    If -1 = _CurrentChunkIndex Or _CurrentChunkIndex <> RequiredChunkIndex Then
                        _CurrentChunkIndex = RequiredChunkIndex

                        _EncryptedVideoFile.Position = _EncryptedChunkPosition(_CurrentChunkIndex)

                        Dim data(_EncryptedChunkLength(_CurrentChunkIndex) - 1) As Byte
                        _EncryptedVideoFile.Read(data, 0, data.Length)

                        _CurrentChunk = Globals.Decrypt(data, data.Length)
                    End If

                    ' So for now uncrypted data is available, now let's get starting point within the chunk
                    ' and how many bytes we are able to read from the chunk (chunks might have different lengths)
                    Dim OffsetInChunk As Integer = _Position - (_CurrentChunkIndex * Globals.ChunkSize)
                    Dim RestInChunk As Integer = _SourceChunkLength(_CurrentChunkIndex) - OffsetInChunk

                    Dim BytesToCopy As Integer
                    If RestInChunk < RestBytesToCopy Then
                        BytesToCopy = RestInChunk
                    Else
                        BytesToCopy = RestBytesToCopy
                    End If

                    ' Copy the data...
                    Array.Copy(_CurrentChunk, OffsetInChunk, pv, OffsetInOutput, BytesToCopy)

                    ' ...and move forward
                    RestBytesToCopy = RestBytesToCopy - BytesToCopy
                    TotalReadBytes = TotalReadBytes + BytesToCopy
                    OffsetInOutput = OffsetInOutput + BytesToCopy
                    _Position = _Position + BytesToCopy
                End While

                ReadBytes = TotalReadBytes
            End SyncLock
        End If

        If IntPtr.Zero <> pcbRead Then Marshal.WriteIntPtr(pcbRead, New IntPtr(ReadBytes))
    End Sub

    Sub Write(ByVal pv() As Byte, ByVal cb As Integer, ByVal pcbWritten As System.IntPtr) Implements IStream.Write
        Throw New Exception("The method or operation is not implemented.")
    End Sub

    Sub Clone(ByRef ppstm As System.Runtime.InteropServices.ComTypes.IStream) Implements IStream.Clone
        ppstm = New VirtualFileStream(_EncryptedVideoFilePath)
    End Sub

    Sub Seek(ByVal dlibMove As Long, ByVal dwOrigin As Integer, ByVal plibNewPosition As System.IntPtr) Implements IStream.Seek
        Dim Origin As SeekOrigin = CType(dwOrigin, SeekOrigin)

        ' Let's protect _Position: _Position might be changed by Read()
        SyncLock (_Lock)
            Select Case Origin
                Case SeekOrigin.Begin
                    _Position = dlibMove
                Case SeekOrigin.Current
                    _Position = _Position + dlibMove
                Case SeekOrigin.End
                    _Position = _Length + dlibMove
            End Select
        End SyncLock

        If IntPtr.Zero <> plibNewPosition Then Marshal.WriteInt64(plibNewPosition, _Position)
    End Sub

    Sub Stat(ByRef pstatstg As System.Runtime.InteropServices.ComTypes.STATSTG, ByVal grfStatFlag As Integer) Implements IStream.Stat
        pstatstg = New System.Runtime.InteropServices.ComTypes.STATSTG
        pstatstg.cbSize = _Length
    End Sub

    Sub Commit(ByVal grfCommitFlags As Integer) Implements IStream.Commit
        Throw New Exception("The method or operation is not implemented.")
    End Sub

    Sub CopyTo(ByVal pstm As System.Runtime.InteropServices.ComTypes.IStream, ByVal cb As Long, ByVal pcbRead As System.IntPtr, ByVal pcbWritten As System.IntPtr) Implements IStream.CopyTo
        Throw New Exception("The method or operation is not implemented.")
    End Sub

    Sub LockRegion(ByVal libOffset As Long, ByVal cb As Long, ByVal dwLockType As Integer) Implements IStream.LockRegion
        Throw New Exception("The method or operation is not implemented.")
    End Sub

    Sub Revert() Implements IStream.Revert
        Throw New Exception("The method or operation is not implemented.")
    End Sub

    Sub SetSize(ByVal libNewSize As Long) Implements IStream.SetSize
        Throw New Exception("The method or operation is not implemented.")
    End Sub

    Sub UnlockRegion(ByVal libOffset As Long, ByVal cb As Long, ByVal dwLockType As Integer) Implements IStream.UnlockRegion
        Throw New Exception("The method or operation is not implemented.")
    End Sub
End Class
