Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports MethodBase = System.Reflection.MethodBase

Friend Class UdpSocket
    Private Class SocketState
        Public RemoteIPEndPoint As IPEndPoint
        Public clientSocket As UdpClient
        Public ReceivedBytes(2048) As Byte
    End Class

    Public Event ErrorOccurred(ByVal remoteIP As String, _
                               ByVal remotePort As Integer, _
                               ByVal errorMessage As String)
    Public Event DataReceived(ByVal remoteIP As String, _
                              ByVal remotePort As Integer, _
                              ByRef dataByteArray() As Byte)
    Public Event SendComplete(ByVal remoteIP As String, _
                              ByVal remotePort As Integer, _
                              ByVal bytesSent As Integer)

    Dim _socketState As SocketState
    Dim _remoteIP As String
    Dim _remotePort As Integer

    Dim _localPort As Integer

    Dim _socketBound As Boolean = False

    Dim _endReceiveCallback As New AsyncCallback(AddressOf EndReceiveCallback)
    Dim _endSendCallback As New AsyncCallback(AddressOf EndSendCallback)

    Property RemoteIP() As String
        Get
            Return _remoteIP
        End Get

        Set(ByVal value As String)
            _remoteIP = value
        End Set
    End Property

    Property RemotePort() As Integer
        Get
            Return _remotePort
        End Get

        Set(ByVal value As Integer)
            _remotePort = value
        End Set
    End Property

    Property LocalPort() As Integer
        Get
            Return _localPort
        End Get
        Set(ByVal value As Integer)
            _localPort = value
        End Set
    End Property


    Public Sub GetLocalIP(ByRef localIPs() As String)
        'Dim ipCount As Integer
        'Dim i As Integer

        'ipCount = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName).AddressList.Length

        'localIPs = New String(ipCount - 1) {}

        'i = 0
        'For Each ip As Net.IPAddress In System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName).AddressList
        '    localIPs(i) = ip.ToString
        '    i += 1
        'Next

        Dim ipCount As Integer

        ipCount = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName).AddressList.Length

        ipCount = 0
        For Each ip As Net.IPAddress In System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName).AddressList
            If InStr(ip.ToString, ":") = 0 Then
                'only get the IPV4 IP address format
                ReDim Preserve localIPs(ipCount)
                localIPs(ipCount) = ip.ToString
                ipCount += 1
            End If

        Next
    End Sub

    Shared Function GetEndPointIP(ByVal endPointx As EndPoint) As String
        Return CType(endPointx, IPEndPoint).Address.ToString
    End Function

    Shared Function GetEndPointPort(ByVal endPointx As EndPoint) As Integer
        Return CType(endPointx, IPEndPoint).Port
    End Function

    Public Sub CloseConnection()
        _socketBound = False

        Try
            '_socketState.clientSocket.Client.Disconnect(False)
            _socketState.clientSocket.Client.Close()
            _socketState.clientSocket.Close()
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

    Private Sub EndReceiveCallback(ByVal ar As IAsyncResult)
        If _socketBound Then
            Dim procedureName As String = MethodBase.GetCurrentMethod.Name
            Dim remoteIP As String = ""
            Dim remotePort As Integer

            Dim ss As SocketState = CType(ar.AsyncState, SocketState)

            'Parse receiced data
            Try
                Dim buf() As Byte
                buf = ss.clientSocket.EndReceive(ar, ss.RemoteIPEndPoint)

                remoteIP = GetEndPointIP(ss.RemoteIPEndPoint)
                remotePort = GetEndPointPort(ss.RemoteIPEndPoint)

                Try
                    'Notify host that data received
                    RaiseEvent DataReceived(remoteIP, remotePort, buf)
                Catch ex As Exception
                    RaiseEvent ErrorOccurred(remoteIP, remotePort, procedureName & ": " & ex.Message)
                End Try
            Catch ex As SocketException
                RaiseEvent ErrorOccurred(remoteIP, remotePort, procedureName & ": (Error/Message) " & ex.ErrorCode & " / " & ex.Message)
            Catch ex As Exception
                RaiseEvent ErrorOccurred(remoteIP, remotePort, procedureName & ": " & ex.Message)
            End Try



            'Start reading data process
            Try
                ss.clientSocket.BeginReceive(_endReceiveCallback, _socketState)
            Catch ex As SocketException
                RaiseEvent ErrorOccurred(remoteIP, remotePort, procedureName & ": (Error/Message) " & ex.ErrorCode & " / " & ex.Message)
            Catch ex As Exception
                RaiseEvent ErrorOccurred(remoteIP, remotePort, procedureName & ": " & ex.Message)
            End Try
        Else
            ' Socket is disconnected
            ' Do nothing here
        End If


    End Sub

    Public Sub NewSocketState(ByVal ep As EndPoint)
        _socketState = New SocketState
        _socketState.clientSocket = New UdpClient(ep)
    End Sub

    Public Sub Bind(ByVal localIP As String, _
                    ByVal localPort As Integer)
        Try
            'Get the end point for binding
            Dim ep As IPEndPoint

            If localIP = "" Then
                ep = New IPEndPoint(IPAddress.Any, localPort)
            Else
                ep = New IPEndPoint(IPAddress.Parse(localIP), localPort)
            End If

            'Initialize the udp socket
            If _socketState Is Nothing Then
                _socketState = New SocketState
                _socketState.clientSocket = New UdpClient
            Else
                _socketState.clientSocket.Close()
                _socketState.clientSocket = New UdpClient()
            End If

            'Bind the port
            _socketState.clientSocket.Client.Bind(ep)

            'Get the local port be bound
            _localPort = GetEndPointPort(_socketState.clientSocket.Client.LocalEndPoint)

            'Start reading data process
            _socketState.clientSocket.BeginReceive(_endReceiveCallback, _socketState)
            _socketBound = True
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

    Private Sub EndSendCallback(ByVal ar As IAsyncResult)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name
        Dim remotePort As Integer
        Dim remoteIP As String

        Dim bytesSent As Integer

        Try
            Dim ss As SocketState = CType(ar.AsyncState, SocketState)

            bytesSent = ss.clientSocket.Client.EndSend(ar)

            remotePort = GetEndPointPort(ss.RemoteIPEndPoint)
            remoteIP = GetEndPointIP(ss.RemoteIPEndPoint)

            'RaiseEvent SendComplete(_remoteIP, _remotePort, bytesSent)
            RaiseEvent SendComplete(remoteIP, remotePort, bytesSent)
        Catch ex As Exception
            'connection may be closed
            RaiseEvent ErrorOccurred(_remoteIP, _remotePort, procedureName & ": " & ex.Message)
        End Try
    End Sub

    Public Sub Send(ByVal dataBytes() As Byte)
        Try
            'Initialize the udp socket
            If _socketState Is Nothing Then
                _socketState = New SocketState
                _socketState.clientSocket = New UdpClient
            End If

            _socketState.clientSocket.BeginSend(dataBytes, _
                                                dataBytes.Length, _
                                                _socketState.RemoteIPEndPoint, _
                                                _endSendCallback, _
                                                _socketState)

            _socketBound = True
            _socketState.clientSocket.BeginReceive(_endReceiveCallback, _socketState)
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

    Public Sub Send(ByVal remoteIP As String, _
                    ByVal remotePort As Integer, _
                    ByRef dataBytes() As Byte)
        Dim procedureName As String = MethodBase.GetCurrentMethod.Name

        _remoteIP = remoteIP
        _remotePort = remotePort
        Dim ep As New IPEndPoint(IPAddress.Parse(remoteIP), remotePort)

        Try
            'Initialize the udp socket
            If _socketState Is Nothing Then
                _socketState = New SocketState
                _socketState.clientSocket = New UdpClient
            End If

            _socketState.RemoteIPEndPoint = ep
            _socketState.clientSocket.BeginSend(dataBytes, _
                                                dataBytes.Length, _
                                                ep, _
                                                _endSendCallback, _
                                                _socketState)
            _socketBound = True
            _socketState.clientSocket.BeginReceive(_endReceiveCallback, _socketState)
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

End Class
