Imports System.IO
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Encryption
Imports ProtoBuf

''' <module>FileContainer</module>
''' <version>0.0.0.2</version>
''' <date>21.11.2018</date>
''' <summary>
''' File Container for securely saving files
''' </summary>
''' <dependencies>
''' NLog, >= 4.5.8
''' </dependencies>
''' <params>
''' LogConfig, DigitalData.Module.Logging.LogConfig
'''     A LogConfig object
''' Password, String
'''     The Password to Encrypt
''' Path, String
'''     The Path to save/load the container
''' </params>
''' <example>
''' dim oContainer = Container.Create(logConfig, "pass", "E:\some.container")
''' dim oContainer = Container.Load(logConfig, "pass", "E:\some.container")
''' 
''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
''' oContainer.Save()
''' 
''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
''' oContainer.Contents = oSomeData
''' oContainer.Save()
''' 
''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
''' oContainer.Load()
''' dim oContents = oContainer.Contents
''' 
''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
''' oContainer.Load()
''' oContainer.Contents = oSomeOtherData
''' oContainer.Save()
''' oContainer.SaveAs("E:\some2.container")
''' </example>
Public Class FileContainer
    Private _crypto As Encryption.Encryption
    Private _compression As Compression
    Private _inner As FileContainerInner
    Private _logger As Logger
    Private _logConfig As LogConfig
    Private _path As String

    Public Property Contents As Byte()
        Get
            Return _inner.Contents
        End Get
        Set(value As Byte())
            _inner.Contents = value
        End Set
    End Property
    Public ReadOnly Property ContainerId As String
        Get
            Return _inner.FileId
        End Get
    End Property
    Public ReadOnly Property CreatedAt As String
        Get
            Return _inner.CreatedAt
        End Get
    End Property
    Public ReadOnly Property UpdatedAt As String
        Get
            Return _inner.UpdatedAt
        End Get
    End Property

    Public Shared Function Create(LogConfig As LogConfig, Password As String) As FileContainer
        Dim oContainer = New FileContainer(LogConfig, Password)
        Return oContainer
    End Function

    Public Shared Function Load(LogConfig As LogConfig, Password As String, Path As String) As FileContainer
        Dim oContainer = New FileContainer(LogConfig, Password, Path)
        oContainer.Load()
        Return oContainer
    End Function

    Public Sub New(LogConfig As LogConfig, Password As String)
        _logger = LogConfig.GetLogger()
        _crypto = New Encryption.Encryption(LogConfig, Password)
        _compression = New Compression(LogConfig)
        _inner = New FileContainerInner()
    End Sub

    Public Sub New(LogConfig As LogConfig, Password As String, Path As String)
        MyClass.New(LogConfig, Password)
        _path = Path
    End Sub

    Public Sub SetFile(Contents As Byte(), FileName As String)
        _inner.Contents = Contents
        _inner.UpdatedAt = Date.Now
        _inner.FileName = FileName
    End Sub

    Public Function GetFile() As FileContainerInner
        Return _inner
    End Function

    Public Sub Save()
        If IsNothing(_path) Then
            Throw New ArgumentException("Path not set")
        End If

        SaveAs(_path)
    End Sub

    Public Sub SaveAs(Path As String)
        Try
            WriteBytesToFile(TransformToBytes(_inner), Path)
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Sub

    Public Sub Load()
        If IsNothing(_path) Then
            Throw New ArgumentException("Path not set")
        End If

        LoadFrom(_path)
    End Sub

    Public Sub LoadFrom(Path As String)
        Try
            _inner = TransformToObject(ReadBytesFromFile(_path))
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Sub

    Private Function TransformToBytes([Object] As FileContainerInner) As Byte()
        Dim oBytes = Serialize([Object])
        Dim oCompressed = _compression.Compress(oBytes)
        Dim oEncrypted = _crypto.Encrypt(oCompressed)
        Return oEncrypted
    End Function

    Private Function TransformToObject(Bytes As Byte()) As FileContainerInner
        Dim oDecrypted = _crypto.Decrypt(Bytes)
        Dim oDecompressed = _compression.Decompress(oDecrypted)
        Dim oObject = Deserialize(oDecompressed)
        Return oObject
    End Function

    Private Function Serialize(InnerData As FileContainerInner) As Byte()
        Dim oBinaryData As Byte()

        Using oStream As New MemoryStream
            Serializer.Serialize(oStream, InnerData)
            oBinaryData = oStream.ToArray()
        End Using

        Return oBinaryData
    End Function

    Private Function Deserialize(InnerData As Byte()) As FileContainerInner
        Dim oObject As FileContainerInner

        Using oStream As New MemoryStream(InnerData)
            oObject = Serializer.Deserialize(Of FileContainerInner)(oStream)
        End Using

        Return oObject
    End Function

    Private Sub WriteBytesToFile(Data As Byte(), FilePath As String)
        Using oSourceStream As New FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)
            oSourceStream.Write(Data, 0, Data.Length)
            oSourceStream.Flush()
        End Using
    End Sub

    Private Function ReadBytesFromFile(FilePath As String) As Byte()
        Using oFileStream = New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096)
            Dim oBuffer As Byte() = New Byte(oFileStream.Length - 1) {}
            oFileStream.Read(oBuffer, 0, oFileStream.Length)
            oFileStream.Close()
            Return oBuffer
        End Using
    End Function
End Class
