Module modCifragem
    ''' <summary>
    ''' Classe mãe de todas as classes de cifras neste módulo
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsCifras
        'Public Event AlterandoChave(ByRef novachave As Object)
        Private _chave As Object, _espacos As Boolean, _tipo As clsCifras.enuTipos
        Public ReadOnly AMAIUSCULO As Integer = AscW("A"c)
        Public ReadOnly AMINUSCULO As Integer = AscW("a"c)
#Region "Tipos"
        Public Enum enuCifras As Integer
            Caesar = 0
            Blocos = 1
            Permutas = 2
            FraseChave = 3
            Viginere = 4
            Multiplicativa = 5
            Afim = 6
        End Enum
        Public Enum enuTipos As Integer
            Inteiro = 0
            Texto = 1
        End Enum
        Public Function NomeDaCifra(ByVal cifra As enuCifras) As String
            Select Case cifra
                Case enuCifras.Caesar : Return "Cifra de César (aditiva)"
                Case enuCifras.Blocos : Return "Blocos Verticais"
                Case enuCifras.Permutas : Return "Permutação"
                Case enuCifras.FraseChave : Return "Frase Chave"
                Case enuCifras.Viginere : Return "Cifra de Vigenère"
                Case enuCifras.Multiplicativa : Return "Cifra Multiplicativa"
                Case enuCifras.Afim : Return "Cifra Afim"
            End Select
            Return ""
        End Function
        Public Function TipoDeChave(ByVal cifra As enuCifras) As enuTipos
            Select Case cifra
                Case enuCifras.Caesar : Return enuTipos.Inteiro
                Case enuCifras.Blocos : Return enuTipos.Inteiro
                Case enuCifras.Permutas : Return enuTipos.Texto
                Case enuCifras.FraseChave : Return enuTipos.Texto
                Case enuCifras.Viginere : Return enuTipos.Texto
                Case enuCifras.Multiplicativa : Return enuTipos.Inteiro
                Case enuCifras.Afim : Return enuTipos.Inteiro
            End Select
            Return enuTipos.Texto
        End Function
#End Region
#Region "Construtores"
        Public Sub New()
            Me._espacos = True
        End Sub
        Public Sub New(ByVal chave As Integer, ByVal espacos As Boolean)
            Me._tipo = enuTipos.Inteiro
            If chave >= 0 Then Me._chave = chave Else Throw New Exception("A chave não pode ser negativa!")
            Me._espacos = espacos
        End Sub
        Public Sub New(ByVal chave As String, ByVal espacos As Boolean)
            Me._tipo = enuTipos.Texto
            Me._chave = chave
            Me._espacos = espacos
        End Sub
#End Region
        Protected Overridable Function TrataChave(ByVal chave As Object) As Object
            Select Case Me._tipo
                Case enuTipos.Inteiro : If CInt(chave) >= 0 Then Return chave Else Throw New Exception("O valor não pode ser negativo!")
                Case enuTipos.Texto : If CStr(chave) <> String.Empty Then Return chave Else Throw New Exception("A Chave não pode estar vazia")
                Case Else : Return 0
            End Select
        End Function
        Public Property Chave() As Object
            Get
                Select Case Me._tipo
                    Case enuTipos.Inteiro : Return CInt(Me._chave)
                    Case enuTipos.Texto : Return CStr(Me._chave)
                End Select
                Return Nothing
            End Get
            Set(ByVal value As Object)
                'RaiseEvent AlterandoChave(value)
                value = TrataChave(value)
                Try
                    Select Case Me._tipo
                        Case enuTipos.Inteiro : Me._chave = CInt(value)
                        Case enuTipos.Texto : Me._chave = CStr(value)
                    End Select
                Catch ex As Exception
                    Throw ex
                End Try

            End Set
        End Property
        Public Property Espacos() As Boolean
            Get
                Return Me._espacos
            End Get
            Set(ByVal value As Boolean)
                Me._espacos = value
            End Set
        End Property
    End Class

    ''' <summary>
    ''' Classe para trabalhar com a Cifra de César (tendo uma chave mutável)
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsCaesar : Inherits clsCifras

        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(ByVal chave As Integer, ByVal espacos As Boolean)
            MyBase.New(chave, espacos)
            Me.Chave = chave
        End Sub
        Public Function Cifra(ByVal texto As String) As String
            Dim cifrado As String = String.Empty
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            For Each c As Char In texto
                If c >= "a"c And c <= "z"c Then
                    cifrado &= ChrW(((AscW(c) - Me.AMINUSCULO + MyBase.Chave + 26) Mod 26) + Me.AMINUSCULO)
                ElseIf c >= "A"c And c <= "Z"c Then
                    cifrado &= ChrW(((AscW(c) - Me.AMAIUSCULO + MyBase.Chave + 26) Mod 26) + Me.AMAIUSCULO)
                Else
                    cifrado &= c
                End If
            Next
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            Dim texto As String = String.Empty
            For Each c As Char In cifrado
                If c >= "a"c And c <= "z"c Then
                    texto &= ChrW(((AscW(c) - Me.AMINUSCULO - MyBase.Chave + 26) Mod 26) + Me.AMINUSCULO)
                ElseIf c >= "A"c And c <= "Z"c Then
                    texto &= ChrW(((AscW(c) - Me.AMAIUSCULO - MyBase.Chave + 26) Mod 26) + Me.AMAIUSCULO)
                Else
                    texto &= c
                End If
            Next
            Return texto
        End Function
    End Class
    ''' <summary>
    ''' Classe para trabalhar com a Transpoisção por Blocos Verticais (nº de linhas mutável)
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsBlocos : Inherits clsCifras
        Private Const SEPARADOR As String = ","
        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(ByVal chave As Integer, ByVal espacos As Boolean)
            MyBase.New(chave, espacos)
            Me.Chave = chave
        End Sub
        Public Function Cifra(ByVal texto As String) As String
            Dim I As Integer, M As Integer, cifrado As String = String.Empty
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            For I = 0 To MyBase.Chave - 1
                For M = I To texto.Length - 1 Step MyBase.Chave
                    cifrado &= texto(M)
                Next
                cifrado &= clsBlocos.SEPARADOR
            Next
            cifrado = cifrado.Remove(cifrado.Length - 1, 1)
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            Dim Linhas() As String
            If cifrado.Contains(vbNewLine) Then
                cifrado = cifrado.Replace(ChrW(10), "")
                Linhas = cifrado.Split(ChrW("13"))
            Else
                Linhas = cifrado.Split(clsBlocos.SEPARADOR)
            End If
            Dim texto As String = String.Empty
            For I As Integer = 0 To Linhas(0).Length - 1
                For M As Integer = 0 To Linhas.Length - 1
                    If I <= Linhas(M).Length - 1 Then texto &= Linhas(M).Chars(I)
                Next
            Next
            Return texto
        End Function
        'Private Sub AlterarChave(ByRef novachave As Object) Handles MyBase.AlterandoChave
        Protected Overrides Function TrataChave(ByVal chave As Object) As Object
            If CInt(chave) <= 0 Then Throw New Exception("O valor da chave tem que ser maior que zero!") Else Return chave
        End Function
    End Class
    ''' <summary>
    ''' Classe para trabalhar com a cifra de Permutação
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsPermuta : Inherits clsCifras
        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(ByVal chave As String, ByVal espacos As Boolean)
            MyBase.New(chave, espacos)
            Try
                Me.Chave2Array(chave)
            Catch ex As Exception
                Me.Chave = chave
                Throw ex
            End Try
            Me.Chave = chave
        End Sub
        Public Function Cifra(ByVal texto As String) As String
            Dim tam As Integer = Me.Chave2Array(MyBase.Chave).Length - 1, chave(tam) As Integer, I As Integer
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            Dim cifrado As String = String.Empty
            If texto.Length Mod tam <> 0 Then Throw New Exception("O tamanho da palavra tem que ser um múltiplo do tamanho da chave")
            chave = Me.Chave2Array(MyBase.Chave)

            Do While texto <> String.Empty
                For I = 0 To tam - 1
                    cifrado &= texto.Substring(chave(I) - 1, 1)
                Next
                texto = texto.Remove(0, tam)
            Loop
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            Dim tam As Integer = Me.Chave2Array(MyBase.Chave).Length - 1, chave(tam - 1) As Integer, I As Integer

            If cifrado.Length Mod (tam) <> 0 Then Throw New Exception("O tamanho da palavra tem que ser um múltiplo do tamanho da chave")
            Dim texto(cifrado.Length() - 1) As Char
            chave = Me.Chave2Array(MyBase.Chave)
            Dim local As Integer = -1, max As Integer = -1
            For I = 0 To cifrado.Length - 1
                If (max = tam - 1) Then
                    max = 0
                    local += tam
                Else
                    max += 1
                End If
                texto(I) = cifrado.Substring(local + chave(max), 1)
            Next
            Dim aux As String = ""
            For Each c As Char In texto : aux &= c : Next
            Return aux
        End Function
        Private Function Chave2Array(ByRef chave As String) As Integer()
            Dim letras() As String, nums() As Integer, I As Integer
            Try
                If chave.Contains(" ") Then
                    letras = chave.Split(" ")
                    ReDim nums(letras.Length)

                    For I = 0 To letras.Length - 1 : nums(I) = CInt(letras(I)) : Next
                Else
                    ReDim nums(chave.Length)
                    For I = 0 To chave.Length - 1
                        nums(I) = chave.Substring(I, 1)
                    Next
                End If
            Catch
                Throw New Exception("O valor na posição " & (I + 1).ToString & " da chave não é posição válida")
            End Try
            For I = 1 To nums.Length() - 1
                If Not (nums.Contains(I)) Then
                    chave = "1"
                    Throw New Exception("A chave não é válida pois não possui todos as posições entre 1 e o tamanho da chave!")
                End If
            Next
            Return nums
        End Function
    End Class
    ''' <summary>
    ''' Classe para trabalhar com a cifra que utiliza uma Frase Chave e uma letra.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsFraseChave : Inherits clsCifras
        Private _ocupado(25) As Boolean
        Private _letra As Char
        Public Sub New()
            MyBase.New()
            Me.LimpaOcupado()
        End Sub
        Public Sub New(ByVal frasechave As String, ByVal letrachave As String, ByVal espacos As Boolean)
            MyBase.New(frasechave, espacos)
            Me.Chave = frasechave
            Me.LetraChave = letrachave
        End Sub
        Public Property LetraChave() As Char
            Get
                Return Me._letra
            End Get
            Set(ByVal value As Char)
                value = Char.ToUpper(value, System.Globalization.CultureInfo.CurrentCulture)
                If (value >= "A"c AndAlso value <= "Z"c) Then Me._letra = value Else Throw New Exception("A letra Chave não é válida!")
            End Set
        End Property
        Public Function Cifra(ByVal texto As String) As String
            If MyBase.Espacos Then texto = texto.Replace(" ", "")

            Dim cifrado As String = String.Empty, alfabeto As String = Me.GeraAlfabeto()
            For Each c As Char In texto
                cifrado &= ChrW(alfabeto.IndexOf(Char.ToUpper(c, Globalization.CultureInfo.CurrentCulture)) + Me.AMAIUSCULO)
            Next
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            Dim texto As String = String.Empty
            If MyBase.Espacos Then cifrado = cifrado.Replace(" ", "")
            Dim alfabeto As String = Me.GeraAlfabeto()
            For Each c As Char In cifrado
                texto &= alfabeto(AscW(c) - Me.AMAIUSCULO)
            Next
            Return texto
        End Function
        Private Function GeraAlfabeto() As String
            Dim letra As Integer = AscW(Me._letra) - Me.AMAIUSCULO, alfabetofim As String = String.Empty, alfabetoinicio As String = String.Empty, I As Integer = 0
            For Each c In MyBase.Chave
                alfabetofim &= c
            Next
            Do While alfabetofim.Length < 26 - letra
                If Not (Me._ocupado(I)) Then alfabetofim &= ChrW(I + Me.AMAIUSCULO)
                I += 1
            Loop
            Do While alfabetofim.Length + alfabetoinicio.Length < 26
                If Not (Me._ocupado(I)) Then alfabetoinicio &= ChrW(I + Me.AMAIUSCULO)
                I += 1
            Loop
            Return alfabetoinicio & alfabetofim
        End Function
        Private Sub LimpaOcupado()
            Dim I As Integer
            For I = 0 To 25
                Me._ocupado(I) = False
            Next
        End Sub
        Private Function TrataFraseChave(ByVal chave As String) As String
            Dim alfabeto As New Collections.Specialized.StringCollection, posi As Integer
            Dim novachave As String = String.Empty
            For Each c As Char In chave
                c = Char.ToUpper(c, Globalization.CultureInfo.CurrentCulture)
                posi = AscW(c) - Me.AMAIUSCULO
                If Not (Me._ocupado(posi)) Then
                    Me._ocupado(posi) = True
                    alfabeto.Add(c)
                End If
            Next
            For Each c As String In alfabeto : novachave &= c : Next
            Return novachave
        End Function
        'Private Sub AlterandoFraseChave(ByRef chave As Object) Handles MyBase.AlterandoChave
        Protected Overrides Function TrataChave(ByVal chave As Object) As Object
            Me.LimpaOcupado()
            Return TrataFraseChave(chave)
        End Function
    End Class
    ''' <summary>
    ''' Classe para trabalhar com a Cifra de Vigènere
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsVigenere : Inherits clsCifras
        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(ByVal chave As String, ByVal espacos As Boolean)
            MyBase.New(chave, espacos)
            Me.Chave = chave
        End Sub
        'Private Sub AlterandoFraseChave(ByRef chave As Object) Handles MyBase.AlterandoChave
        Protected Overrides Function TrataChave(ByVal chave As Object) As Object
            Dim aux As String = String.Empty
            For Each c As Char In chave
                aux &= Char.ToUpper(c, Globalization.CultureInfo.CurrentCulture)
            Next
            Return aux
        End Function
        Public Function Cifra(ByVal texto As String) As String
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            Dim cifrado As String = String.Empty, frasechave As String = Me.CriaFraseChave(texto), I As Integer
            For I = 0 To texto.Length - 1
                If Char.IsWhiteSpace(texto(I)) Then
                    cifrado &= " "
                Else
                    cifrado &= ChrW(((AscW(texto(I)) - Me.AMAIUSCULO) + ((AscW(frasechave(I)) - Me.AMAIUSCULO))) Mod 26 + Me.AMAIUSCULO)
                End If
            Next
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            If MyBase.Espacos Then cifrado = cifrado.Replace(" ", "")
            Dim texto As String = String.Empty, frasechave As String = Me.CriaFraseChave(cifrado), I As Integer
            For I = 0 To cifrado.Length - 1
                If Char.IsWhiteSpace(cifrado(I)) Then
                    texto &= " "
                Else
                    texto &= ChrW(((AscW(cifrado(I)) - Me.AMAIUSCULO) - ((AscW(frasechave(I)) - Me.AMAIUSCULO)) + 26) Mod 26 + Me.AMAIUSCULO)
                End If
            Next
            Return texto
        End Function
        Private Function CriaFraseChave(ByVal texto As String) As String
            Dim chave As String = String.Empty, chaveactual As String = MyBase.Chave, I As Integer = 0
            Select Case chaveactual.Length
                Case texto.Length : Return chaveactual
                Case Is > texto.Length
                    chave = chaveactual.Substring(0, texto.Length)
                Case Is < texto.Length
                    For Each c As Char In texto
                        chave &= chaveactual(I)
                        If I >= chaveactual.Length - 1 Then I = 0 Else I += 1
                    Next
            End Select
            If Not (Me.Espacos) Then
                For I = 0 To texto.Length - 1
                    If texto(I) = " "c Then chave = chave.Insert(I, " ")
                Next
            End If
            Return chave


        End Function
    End Class
    ''' <summary>
    ''' Classe para trabalhar com a cifra multiplicativa
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsMultiplicativa : Inherits clsCifras
        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(ByVal chave As Integer, ByVal espacos As Boolean)
            MyBase.New(chave, espacos)
            Me.Chave = chave
        End Sub
        Public Function Cifra(ByVal texto As String) As String
            Dim cifrado As String = String.Empty
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            For Each c As Char In texto
                If c >= "a"c And c <= "z"c Then
                    cifrado &= ChrW((((AscW(c) - Me.AMINUSCULO) * MyBase.Chave) Mod 26) + Me.AMINUSCULO)
                ElseIf c >= "A"c And c <= "Z"c Then
                    cifrado &= ChrW((((AscW(c) - Me.AMAIUSCULO) * MyBase.Chave) Mod 26) + Me.AMAIUSCULO)
                Else
                    cifrado &= c
                End If
            Next
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            Dim texto As String = String.Empty, multinverse As Integer = 0
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            For I = 1 To 25 Step 2
                If ((MyBase.Chave * I) Mod 26 = 1) Then multinverse = I
            Next
            For Each c As Char In cifrado
                If c >= "a"c And c <= "z"c Then
                    texto &= ChrW((((AscW(c) - Me.AMINUSCULO) * multinverse + 26) Mod 26) + Me.AMINUSCULO)
                ElseIf c >= "A"c And c <= "Z"c Then
                    texto &= ChrW((((AscW(c) - Me.AMAIUSCULO) * multinverse + 26) Mod 26) + Me.AMAIUSCULO)
                Else
                    texto &= c
                End If
            Next
            Return texto
        End Function
        'Private Sub AlteraChave(ByRef novachave As Object) Handles MyBase.AlterandoChave
        Protected Overrides Function TrataChave(ByVal chave As Object) As Object
            If Integer.TryParse(chave, chave) Then
                If CInt(chave) > 26 OrElse Me.mdc(chave, 26) <> 1 Then
                    Throw New Exception("A chave tem que ser co-prima de 26 e inferior a este.")
                End If
            Else
                Throw New Exception("Erro ao converter a chave!")
            End If
            Return chave
        End Function
        Private Function mdc(ByVal a As Integer, ByVal b As Integer) As Integer
            If b = 0 Then Return a Else Return Me.mdc(b, a Mod b)
        End Function
    End Class
    ''' <summary>
    ''' Classe para trabalhar com a cifra Afim
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsAfim : Inherits clsCifras
        Private _multchave As Integer
        Public Sub New()
            MyBase.New()
            Me.ChaveMultiplicativa = 0
        End Sub
        Public Sub New(ByVal chaveS As Integer, ByVal chaveM As Integer, ByVal espacos As Boolean)
            MyBase.New(chaveS, espacos)
            Me.Chave = chaveS
            Me.ChaveMultiplicativa = chaveM
        End Sub
        Public Property ChaveMultiplicativa() As Integer
            Get
                Return Me._multchave
            End Get
            Set(ByVal value As Integer)
                Me.AlteraChave(value)
                Me._multchave = value
            End Set
        End Property
        Public Function Cifra(ByVal texto As String) As String
            Dim cifrado As String = String.Empty
            If MyBase.Espacos Then texto = texto.Replace(" ", "")
            For Each c As Char In texto
                If c >= "a"c And c <= "z"c Then
                    cifrado &= ChrW(((((AscW(c) - Me.AMINUSCULO) * Me._multchave) + MyBase.Chave + 26) Mod 26) + Me.AMINUSCULO)
                ElseIf c >= "A"c And c <= "Z"c Then
                    cifrado &= ChrW(((((AscW(c) - Me.AMAIUSCULO) * Me._multchave) + MyBase.Chave + 26) Mod 26) + Me.AMAIUSCULO)
                Else
                    cifrado &= c
                End If
            Next
            Return cifrado
        End Function
        Public Function Decifra(ByVal cifrado As String) As String
            Dim texto As String = String.Empty, multinverse As Integer = 1
            For I = 1 To 25 Step 2
                If ((Me._multchave * I) Mod 26 = 1) Then multinverse = I
            Next

            For Each c As Char In cifrado
                If c >= "a"c And c <= "z"c Then
                    texto &= ChrW((multinverse * (AscW(c) - Me.AMINUSCULO + 26 - MyBase.Chave) Mod 26) + Me.AMINUSCULO)
                ElseIf c >= "A"c And c <= "Z"c Then
                    texto &= ChrW((multinverse * (AscW(c) - Me.AMAIUSCULO + 26 - MyBase.Chave) Mod 26) + Me.AMAIUSCULO)
                Else
                    texto &= c
                End If
            Next
            Return texto
        End Function
        Private Sub AlteraChave(ByRef novachave As Object)
            If Integer.TryParse(novachave, novachave) Then
                If CInt(novachave) > 26 OrElse Me.mdc(novachave, 26) <> 1 Then
                    Throw New Exception("A chave Multiplicativa tem que ser co-prima de 26 e inferior a este.")
                End If
            Else
                Throw New Exception("Erro ao converter a chave!")
            End If
        End Sub
        Private Function mdc(ByVal a As Integer, ByVal b As Integer) As Integer
            If b = 0 Then Return a Else Return Me.mdc(b, a Mod b)
        End Function

    End Class
End Module
