
Imports System.Linq.Expressions
Imports System.Text.RegularExpressions


''' <summary>
''' Representa un número. En la clase se desglosan las distintas opciones que se puedan
''' encontrar
''' </summary>
Public Class ValidarDocumentoIdentidad
    ''' <summary>
    ''' Tipos de Códigos.
    ''' </summary>
    ''' <remarks>Aunque actualmente no se utilice el término CIF, se usa en la enumeración
    ''' por comodidad</remarks>
    Public Enum TiposDocumentosEnum
        NIF
        NIE
        CIF
    End Enum

    ' Número tal cual lo introduce el usuario
    Private m_numero As String

    Private tipo As TiposDocumentosEnum
    Public ReadOnly Property TipoDocumento As TiposDocumentosEnum
        Get
            Return tipo
        End Get
    End Property

    ''' <summary>
    ''' Parte de Nif: En caso de ser un Nif intracomunitario, permite obtener el cógido del país
    ''' </summary>
    Public Property CodigoIntracomunitario() As String
        Get
            Return m_CodigoIntracomunitario
        End Get
        Friend Set(value As String)
            m_CodigoIntracomunitario = value
        End Set
    End Property
    Private m_CodigoIntracomunitario As String
    Friend Property EsIntraComunitario() As Boolean
        Get
            Return m_EsIntraComunitario
        End Get
        Set(value As Boolean)
            m_EsIntraComunitario = value
        End Set
    End Property
    Private m_EsIntraComunitario As Boolean

    ''' <summary>
    ''' Parte de Nif: Letra inicial del Nif, en caso de tenerla
    ''' </summary>
    Public Property LetraInicial() As String
        Get
            Return m_LetraInicial
        End Get
        Friend Set(value As String)
            m_LetraInicial = value
        End Set
    End Property
    Private m_LetraInicial As String

    ''' <summary>
    ''' Parte de Nif: Bloque numérico del NIF. En el caso de un NIF de persona física,
    ''' corresponderá al DNI
    ''' </summary>
    Public Property Identificador() As Integer
        Get
            Return m_numero
        End Get
        Friend Set(value As Integer)
            m_numero = value
        End Set
    End Property
    Private m_Identificador As Integer

    ''' <summary>
    ''' Parte de Nif: Dígito de control. Puede ser número o letra
    ''' </summary>
    Public Property DigitoControl() As String
        Get
            Return m_DigitoControl
        End Get
        Friend Set(value As String)
            m_DigitoControl = value
        End Set
    End Property
    Private m_DigitoControl As String

    ''' <summary>
    ''' Valor que representa si el Nif introducido es correcto
    ''' </summary>
    Public Property EsCorrecto() As Boolean
        Get
            Return m_EsCorrecto
        End Get
        Friend Set(value As Boolean)
            m_EsCorrecto = value
        End Set
    End Property
    Private m_EsCorrecto As Boolean

    ''' <summary>
    ''' Cadena que representa el tipo de Nif comprobado:
    '''     - NIF : Número de identificación fiscal de persona física
    '''     - NIE : Número de identificación fiscal extranjería
    '''     - CIF : Código de identificación fiscal (Entidad jurídica)
    ''' </summary>
    Public ReadOnly Property TipoNif() As String
        Get
            Return tipo.ToString()
        End Get
    End Property

    ''' <summary>
    ''' Constructor. Al instanciar la clase se realizan todos los cálculos
    ''' </summary>
    ''' <param name="numero">Cadena de 9 u 11 caracteres que contiene el DNI/NIF
    ''' tal cual lo ha introducido el usuario para su verificación</param>
    Public Sub New(numero As String)
        ' Se eliminan los carácteres sobrantes
        Try
            '   numero = EliminaCaracteres(numero)


            '   numero = numero.ToUpper()

            ' Comprobación básica de la cadena introducida por el usuario
            If numero.Length <> 9 AndAlso numero.Length <> 11 Then
                Me.EsCorrecto = False
            Else

                Me.m_numero = numero
                Desglosa()

                Select Case tipo
                    Case TiposDocumentosEnum.NIF, TiposDocumentosEnum.NIE
                        Me.EsCorrecto = CompruebaNif()
                        Exit Select
                    Case TiposDocumentosEnum.CIF
                        Me.EsCorrecto = validateCif(numero)
                        ' Me.EsCorrecto = CompruebaCif()
                        Exit Select
                End Select
            End If
        Catch ex As Exception
            Me.EsCorrecto = False
        End Try
    End Sub

#Region "Preparación del número (desglose)"

    ''' <summary>
    ''' Realiza un desglose del número introducido por el usuario en las propiedades
    ''' de la clase
    ''' </summary>
    Private Sub Desglosa()
        Dim n As Int32
        If m_numero.Length = 11 Then
            ' Nif Intracomunitario
            EsIntraComunitario = True
            CodigoIntracomunitario = m_numero.Substring(0, 2)
            LetraInicial = m_numero.Substring(2, 1)
            Int32.TryParse(m_numero.Substring(3, 7), n)
            DigitoControl = m_numero.Substring(10, 1)
            tipo = GetTipoDocumento(LetraInicial(0))
        Else
            ' Nif español
            tipo = GetTipoDocumento(m_numero(0))
            EsIntraComunitario = False
            If tipo = TiposDocumentosEnum.NIF Then
                LetraInicial = String.Empty
                Int32.TryParse(m_numero.Substring(0, 8), n)
            Else
                LetraInicial = m_numero.Substring(0, 1)
                Dim listaLetrasNIE As Char() = {"X", "Y", "Z"}
                If listaLetrasNIE.Contains(LetraInicial) Then
                    '// Las letras por las que comienza el NIE deben ser reemplazadas por números antes de realizar
                    '// la operación Int32.TryParse, que además deberá incluir ese número reemplazado.
                    '// X = 0
                    '// Y = 1
                    '// Z = 2
                    Select Case LetraInicial
                        Case "X"
                            m_numero = 0 & m_numero.Substring(1, m_numero.Length - 1)
                        Case "Y"
                            m_numero = 1 & m_numero.Substring(1, m_numero.Length - 1)
                        Case "Z"
                            m_numero = 2 & m_numero.Substring(1, m_numero.Length - 1)
                    End Select
                    Int32.TryParse(m_numero.Substring(0, 8), n)
                Else
                    '// El curso normal, cuando la letra inicial no es X, Y o Z.
                    Int32.TryParse(m_numero.Substring(1, 7), n)
                End If

            End If
            DigitoControl = m_numero.Substring(8, 1)
        End If
        Identificador = n
    End Sub

    ''' <summary>
    ''' En base al primer carácter del código, se obtiene el tipo de documento que se intenta
    ''' comprobar
    ''' </summary>
    ''' <param name="letra">Primer carácter del número pasado</param>
    ''' <returns>Tipo de documento</returns>
    Private Function GetTipoDocumento(letra As Char) As TiposDocumentosEnum
        Dim regexIdentificadors As New Regex("[0-9]")
        If regexIdentificadors.IsMatch(letra.ToString()) Then
            Return TiposDocumentosEnum.NIF
        End If

        Dim regexLetrasNIE As New Regex("[LKMXYZ]")
        If regexLetrasNIE.IsMatch(letra.ToString()) Then
            Return TiposDocumentosEnum.NIE
        End If

        Dim regexLetrasCIF As New Regex("[ABCDEFGHJPQRSUVNW]")
        If regexLetrasCIF.IsMatch(letra.ToString()) Then
            Return TiposDocumentosEnum.CIF
        End If

        Throw New ApplicationException("El código no es reconocible")
    End Function

    ''' <summary>
    ''' Eliminación de todos los carácteres no numéricos o de texto de la cadena
    ''' </summary>
    ''' <param name="numero">Número tal cual lo escribe el usuario</param>
    ''' <returns>Cadena de 9 u 11 carácteres sin signos</returns>
    Private Function EliminaCaracteres(numero As String) As String
        ' Todos los carácteres que no sean números o letras
        Dim caracteres As String = "[^\w]"
        Dim regex As New Regex(caracteres)
        Return regex.Replace(numero, "")
    End Function

#End Region

#Region "Cálculos"

    Private Function CompruebaNif() As Boolean
        Return DigitoControl = GetLetraNif()
    End Function



    Public Shared Function validateCif(ByVal cif As String) As Boolean
        If String.IsNullOrEmpty(cif) Then Return False
        cif = cif.Trim().ToUpper()
        If cif.Length <> 9 Then Return False
        Dim firstChar As String = cif.Substring(0, 1)
        Dim cadena As String = "ABCDEFGHJNPQRSUVW"
        If cadena.IndexOf(firstChar) = -1 Then Return False

        Try
            Dim sumaPar As Int32 = Nothing
            Dim sumaImpar As Int32 = Nothing
            Dim cif_sinControl As String = cif.Substring(0, 8)
            Dim digits As String = cif_sinControl.Substring(1, 7)

            For n As Int32 = 0 To digits.Length - 1 Step 2

                If n < 6 Then
                    sumaPar += Convert.ToInt32(digits(n + 1).ToString())
                End If

                Dim dobleImpar As Int32 = 2 * Convert.ToInt32(digits(n).ToString())
                sumaImpar += (dobleImpar Mod 10) + (dobleImpar \ 10)
            Next

            Dim sumaTotal As Int32 = sumaPar + sumaImpar
            sumaTotal = (10 - (sumaTotal Mod 10)) Mod 10
            Dim digitoControl As String = ""

            Select Case firstChar
                Case "N", "P", "Q", "R", "S", "W"
                    Dim characters As Char() = {"J"c, "A"c, "B"c, "C"c, "D"c, "E"c, "F"c, "G"c, "H"c, "I"c}
                    digitoControl = characters(sumaTotal).ToString()
                Case Else
                    digitoControl = sumaTotal.ToString()
            End Select

            Return digitoControl.Equals(cif.Substring(8, 1))
        Catch __unusedException1__ As Exception
            Return False
        End Try
    End Function



    ''' <summary>
    ''' Cálculos para la comprobación del Cif (Entidad jurídica)
    ''' </summary>
    'Private Function CompruebaCif() As Boolean
    '    Dim letrasCodigo As String() = {"J", "A", "B", "C", "D", "E", _
    '        "F", "G", "H", "I"}

    '    Dim n As String = Identificador.ToString()
    '    Dim sumaPares As Int32 = 0
    '    Dim sumaImpares As Int32 = 0
    '    Dim sumaTotal As Int32 = 0
    '    Dim i As Int32 = 0
    '    Dim digitoCalculado As String
    '    Dim retVal As Boolean = False

    '    ' Recorrido por todos los dígitos del número
    '    For i = 0 To n.Length - 1
    '        Dim aux As Int32
    '        Int32.TryParse(n(i).ToString(), aux)

    '        If (i + 1) Mod 2 = 0 Then
    '            ' Si es una posición par, se suman los dígitos
    '            sumaPares += aux
    '        Else
    '            ' Si es una posición impar, se multiplican los dígitos por 2 
    '            aux = aux * 2

    '            ' se suman los dígitos de la suma
    '            sumaImpares += SumaDigitos(aux)
    '        End If
    '    Next
    '    ' Se suman los resultados de los números pares e impares
    '    sumaTotal += sumaPares + sumaImpares

    '    ' Se obtiene el dígito de las unidades
    '    Dim unidades As Int32 = sumaTotal Mod 10

    '    ' Si las unidades son distintas de 0, se restan de 10
    '    If unidades <> 0 Then
    '        unidades = 10 - unidades
    '    End If

    '    Select Case LetraInicial
    '        ' Sólo números
    '        Case "A", "B", "E", "H"
    '            retVal = DigitoControl = unidades.ToString()
    '            Exit Select

    '            ' Sólo letras
    '        Case "K", "P", "Q", "S"
    '            retVal = DigitoControl = letrasCodigo(unidades)
    '            Exit Select
    '        Case Else

    '            retVal = (DigitoControl = unidades.ToString()) OrElse (DigitoControl = letrasCodigo(unidades))
    '            Exit Select
    '    End Select

    '    Return retVal

    'End Function

    ''' <summary>
    ''' Obtiene la suma de todos los dígitos
    ''' </summary>
    ''' <returns>de 23, devuelve la suma de 2 + 3</returns>
    Private Function SumaDigitos(digitos As Int32) As Int32
        Dim sIdentificador As String = digitos.ToString()
        Dim suma As Int32 = 0

        For i As Int32 = 0 To sIdentificador.Length - 1
            Dim aux As Int32
            Int32.TryParse(sIdentificador(i).ToString(), aux)
            suma += aux
        Next
        Return suma
    End Function

    ''' <summary>
    ''' Obtiene la letra correspondiente al Dni
    ''' </summary>
    Private Function GetLetraNif() As String
        Dim indice As Integer = Identificador Mod 23
        Return "TRWAGMYFPDXBNJZSQVHLCKET"(indice).ToString()
    End Function

    ''' <summary>
    ''' Obtiene una cadena con el número de identificación completo
    ''' </summary>
    Public Overrides Function ToString() As String
        Dim nif As String
        nif = If(EsIntraComunitario, CodigoIntracomunitario, Convert.ToString(String.Empty + LetraInicial & Identificador) & DigitoControl)
        Return nif
    End Function

#End Region

    ''' <summary>
    ''' Comprobación de un número de identificación fiscal español
    ''' </summary>
    ''' <param name="numero">Identificador a analizar</param>
    ''' <returns>Instancia de <see cref="IdentificadorNif"/> con los datos del número.
    ''' Destacable la propiedad <seealso cref="IdentificadorNif.EsCorrecto"/>, que contiene la verificación
    ''' </returns>
    Public Shared Function CompruebaNif(numero As String) As ValidarDocumentoIdentidad

        Return New ValidarDocumentoIdentidad(numero)
    End Function

End Class