﻿''' <summary>
''' An easy-to-use socket class.
''' </summary>
''' <remarks>easySocket is the underlying socket architecture behind all easySockets.
''' easySocket is an inheritance class of TcpClient, so all core functions remain.
''' However, new functions are included for fast and easy use of Sync and Async reads and writes.
''' Use easySocket to build your own socket protocols.</remarks>
Public Class easySocket
    Inherits TcpClient

#Region "Types"

    ''' <summary>
    ''' The arguments parameter of the DataRead event.
    ''' </summary>
    ''' <remarks>Contains a ReadResult property, Result.</remarks>
    Public Class DataReadEventArgs
        Inherits EventArgs

        Private _result As ReadResult

        ''' <summary>
        ''' A ReadResult object containing the data from a read operation.
        ''' </summary>
        ''' <remarks>Contains one byte array property, Data.
        ''' <seealso cref="ReadResult">ReadResult</seealso>
        ''' </remarks>
        Public ReadOnly Property Result As ReadResult
            Get
                Return(Me._result)
            End Get
        End Property

        Public Sub New(ByVal result As ReadResult)
            _result = result
        End Sub

    End Class

    ''' <summary>
    ''' The result of a read operation.
    ''' </summary>
    ''' <remarks>Contains one byte array property, Data.</remarks>
    Public Structure ReadResult

        ''' <summary>
        ''' The data obtained from a read operation on the socket.
        ''' </summary>
        ''' <remarks>Data starts resized to the amount of data read from the socket.
        ''' Use Data.Length to determine the amount of data read.</remarks>
        Public Data As Byte()

        ''' <summary>
        ''' Create a new ReadResult.
        ''' </summary>
        ''' <param name="buffer">The data from the read operation.</param>
        ''' <param name="count">The number of bytes read from the socket.</param>
        Public Sub New(ByVal buffer As Byte(), ByVal count As Integer)
            Data = buffer
            Array.Resize(Data, count)
        End Sub

    End Structure

#End Region ' Types

#Region "Fields"

    Private myAddr As String
    Private myPort As Integer

    Private asReadBuffer() As Byte ' Buffer for our Async Reads.
    Private asContReads As Boolean ' Do we do Async Reads continuously forever?  Assume true.
    Private asReadSize As Integer  ' How big are our Async Reads?  Assume 1024 bytes.

#End Region ' Fields

#Region "Properties"

    Public Property Address As String
        Get
            Return myAddr
        End Get
        Set(ByVal value As String)
            myAddr = value
        End Set
    End Property

    Public Property Port As String
        Get
            Return myPort
        End Get
        Set(ByVal value As String)
            myPort = value
        End Set
    End Property

#End Region ' Properties

#Region "Events" ' Events

    ''' <summary>
    ''' The socket was disconnected.
    ''' </summary>
    ''' <remarks>Raised when a disconnect has been detected.</remarks>
    Public Event Disconnected As EventHandler

    ''' <summary>
    ''' Data has been read asynchronously and is available.
    ''' </summary>
    ''' <param name="e">Contains two properties: Buffer and Count.</param>
    ''' <remarks>Raised when an Asynchronous read has completed and data is available.</remarks>
    Public Event DataRead As EventHandler(Of DataReadEventArgs)

    ''' <summary>
    ''' Data has been sent asynchronously.
    ''' </summary>
    ''' <remarks>Raised when an Asynchronous send has completed.</remarks>
    Public Event DataSent As EventHandler

    ''' <summary>
    ''' A connected has been established asynchronously.
    ''' </summary>
    ''' <remarks>Raised when an Asynchronous connect has completed.</remarks>
    Public Event ConnectionEstablished As EventHandler

#End Region ' Events

#Region "Methods"

#Region "Event Raisers"

    Protected Overridable Sub OnDisconnected(ByVal e As EventArgs)
        RaiseEvent Disconnected(Me, e)
    End Sub

    Protected Overridable Sub OnDataRead(ByVal e As DataReadEventArgs)
        RaiseEvent DataRead(Me, e)
    End Sub

    Protected Overridable Sub OnDataSent(ByVal e As EventArgs)
        RaiseEvent DataSent(Me, e)
    End Sub

    Protected Overridable Sub OnConnectionEstablished(ByVal e As EventArgs)
        RaiseEvent ConnectionEstablished(Me, e)
    End Sub

#End Region ' Event Raisers

#Region "Connection Methods"

    ''' <summary>
    ''' Connect using a sycnrhonous method.
    ''' </summary>
    ''' <remarks><c>Address</c> and <c>Port</c> must have already been set.</remarks>
    Public Overloads Sub Connect()
        MyBase.Connect(myAddr, myPort)
    End Sub

    ''' <summary>
    ''' Connect using a synchronous method.
    ''' </summary>
    ''' <param name="addr">The hostname or IP to connect to.</param>
    ''' <param name="port">The port number to connect to.</param>
    Public Overloads Sub Connect(ByVal addr As String, ByVal port As Integer)
        myAddr = addr
        myPort = port
        Me.Connect()
    End Sub

    ''' <summary>
    ''' Connect using an asynchronous method.
    ''' </summary>
    ''' <param name="addr">The hostname or IP to connect to.</param>
    ''' <param name="port">The port number to connect to.</param>
    ''' <remarks></remarks>
    Public Sub AsyncConnect(ByVal addr As String, ByVal port As Integer)
        myAddr = addr
        myPort = port
        Me.AsyncConnect()
    End Sub

    ''' <summary>
    ''' Connect using an asynchronous method.
    ''' </summary>
    ''' <remarks><c>Address</c> and <c>Port</c> must have already been set.</remarks>
    Public Sub AsyncConnect()
        BeginConnect(myAddr, myPort, New AsyncCallback(AddressOf EndASConnect), Nothing)
    End Sub

    Private Sub EndASConnect(ByVal ar As IAsyncResult)
        EndConnect(ar)
        OnConnectionEstablished(EventArgs.Empty)
    End Sub

#End Region ' Connection Methods

#Region "Sending Methods"

    ''' <summary>
    ''' Send data using a synchronous operation.
    ''' </summary>
    ''' <param name="Data">The data to be sent, as a string.</param>
    Public Sub Send(ByVal data As String)
        Dim w As New IO.StreamWriter(GetStream())
        w.Write(data)
        w.Flush()
    End Sub

    ''' <summary>
    ''' Send data using a synchronous operation.
    ''' </summary>
    ''' <param name="Data">The data to be sent, as a byte array.</param>
    ''' <param name="size">The number of bytes in data to be sent.</param>
    Public Sub Send(ByVal data As Byte(), ByVal size As Integer)
        GetStream.Write(data, 0, size)
    End Sub

    ''' <summary>
    ''' Send data using an asynchronous operation.
    ''' </summary>
    ''' <param name="data">The data to be a sent, as a byte array.</param>
    ''' <param name="size">The number of bytes in data to be sent.</param>
    ''' <remarks></remarks>
    Public Sub AsyncSend(ByVal data As Byte(), ByVal size As Integer)
        GetStream.BeginWrite(data, 0, size, New AsyncCallback(AddressOf EndASSend), Nothing)
    End Sub

    Private Sub EndASSend(ByVal ar As IAsyncResult)
        GetStream.EndWrite(ar)
        OnDataSent(EventArgs.Empty)
    End Sub

#End Region ' Sending Methods

#Region "Reading Methods"

    ''' <summary>
    ''' Read data using a synchronous operation.
    ''' </summary>
    ''' <param name="size">The maximum size, in bytes, to be retrieved.  Default 1024.</param>
    ''' <remarks>If the socket is disconnected, Read returns Nothing, and the
    ''' Disconnected event will fire.</remarks>
    Public Function Read(Optional ByVal size As Integer = 1024) As ReadResult
        Dim ReadBuf As Byte() = {0}
        Dim Count As Integer

        Array.Resize(ReadBuf, size)
        Count = GetStream.Read(ReadBuf, 0, size)

        If Not Count Then
            If Not MyBase.Connected Then
                OnDisconnected(EventArgs.Empty)
                Return Nothing
            End If
        End If

        Return New ReadResult(ReadBuf, Count)
    End Function

    ''' <summary>
    ''' Read data using an asynchronous operation.
    ''' </summary>
    ''' <param name="size">The maximum size, in bytes, to be retrieved.  Default 1024.</param>
    ''' <param name="continuous">Whether Asynchronous Reads should continue on forever.  Default true.</param>
    ''' <remarks>When data comes in, the DataRead event will fire. If the socket is
    ''' disconnected, the Disconnected event will fire.</remarks>
    Public Sub AsyncRead(Optional ByVal size As Integer = 1024, Optional ByVal continuous As Boolean = True)
        asContReads = continuous
        asReadSize = size
        StartASRead()
    End Sub

    Private Sub StartASRead()
        asReadBuffer = {0}
        Array.Resize(asReadBuffer, asReadSize)
        GetStream.BeginRead(asReadBuffer, 0, asReadSize, New AsyncCallback(AddressOf EndASRead), Nothing)
    End Sub

    Private Sub EndASRead(ByVal ar As IAsyncResult)
        Dim intCount As Integer

        intCount = GetStream.EndRead(ar)
        If intCount < 1 Then
            ' The only reason to have 0 bytes in an EndRead is disconnected.
            ' We were disconnected, stop async reads and raise Disconnected event.
            OnDisconnected(EventArgs.Empty)
            Exit Sub
        End If

        'Let's put our stuff into a read result, and pass it back in the DataRead event.
        Dim result As New ReadResult(asReadBuffer, intCount)
        OnDataRead(New DataReadEventArgs(result))

        If asContReads Then ' Continuously Reading Asynchronously.
            StartASRead()
        End If
    End Sub

#End Region ' Reading Methods

#End Region ' Methods



End Class
