Imports System.Collections.Concurrent
Imports GPSConnector.Common
Imports System.Net.Sockets
Imports System.Net
Imports System.Threading

Public Class GPSDeviceTypeComponent
    Private _tcpServer As TcpListener
    Private ReadOnly _gpsProcessor As IGPSProcessor
    Private ReadOnly _database As IDatabase
    Private ReadOnly _dataPersistence As IGPSDeviceDataPersistence
    Private ReadOnly _ip As String
    Private ReadOnly _port As Integer
    Private ReadOnly _logger As IGPSLogger
    Private _connections As ConcurrentDictionary(Of String, ProcessClientRequest)
    Private _tmrDeadConnectionCheck As Timer
    Private ReadOnly _lockObjectTmr As New Object

    Public Sub New(gpsProcessor As IGPSProcessor, database As IDatabase, dataPersistence As IGPSDeviceDataPersistence, logger As IGPSLogger, ip As String, port As Integer)
        _gpsProcessor = gpsProcessor
        _database = database
        _dataPersistence = dataPersistence
        _ip = ip
        _port = port
        _logger = logger
        _connections = New ConcurrentDictionary(Of String, ProcessClientRequest)
    End Sub
    Public Event GPSDataArrival(e As GPSDeviceTypeComponentEventArg, sender As Object)
    ''' <summary>
    ''' Friend name of the type of the device
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property DeviceTypeName As String
    ''' <summary>
    ''' Minimal acceptable valid Data. To avoid processing heartbeat data. set zero to process all
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property MinimalDataLength As Integer
    ''' <summary>
    ''' How do you recognize full valid data.?
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ValidDataFlagCharacter As EnValidDataFlagCharacter
    ''' <summary>
    ''' How often we can check if the tcpclient is still alive? in Second.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ReadDataInterval As Integer

    Public Property ByteData As Boolean

    Public ReadOnly Property Ip As String
        Get
            Return _ip
        End Get
    End Property

    Public ReadOnly Property Port As Integer
        Get
            Return _port
        End Get
    End Property
    Friend ReadOnly Property GPSProcessor As IGPSProcessor
        Get
            Return _gpsProcessor
        End Get
    End Property

    Friend ReadOnly Property GPSDeviceListDAO As IDatabase
        Get
            Return _database
        End Get
    End Property

    Friend ReadOnly Property DataPersistence As IGPSDeviceDataPersistence
        Get
            Return _dataPersistence
        End Get
    End Property

    Friend ReadOnly Property Logger As IGPSLogger
        Get
            Return _logger
        End Get
    End Property

    Public ReadOnly Property ActiveConnectionCount As Integer
        Get
            Return 0 ' having issue with synclock.
        End Get
    End Property

    Public Sub Start()
        _logger.LogInformation("Starting Component...", "DeviceTypeComponent : " + _DeviceTypeName, False, False)
        _connections = New ConcurrentDictionary(Of String, ProcessClientRequest)
        If _tcpServer Is Nothing Then
            _tcpServer = New TcpListener(If(String.IsNullOrEmpty(_ip), IPAddress.Any, IPAddress.Parse(_ip)), _port)
        End If
        _tcpServer.Start()
        _tmrDeadConnectionCheck = New Timer(AddressOf DeadConnectionCheck, Nothing, 30000, 30000)
        _logger.LogInformation(String.Format("Starting component {0} , Listen on IP : {1}, Port {2}", DeviceTypeName, If(String.IsNullOrEmpty(_ip), "Any", _ip), _port), "GPSDeviceTypeComponent", False, False)
        'asyncronously wait for connection requests
        _tcpServer.BeginAcceptTcpClient(New AsyncCallback(AddressOf DoAcceptTcpClientCallback), _tcpServer)
    End Sub

    Private Sub DoAcceptTcpClientCallback(ByVal ar As IAsyncResult)
        Dim listener As TcpListener = CType(ar.AsyncState, TcpListener)
        Try
            Dim client As TcpClient = listener.EndAcceptTcpClient(ar)
            Dim cp As New ProcessClientRequest(client, Me)
            Dim clientip As String = GetClientIP(client)

            If clientip <> _ip Then
                _logger.LogInformation(String.Format("New connection established with device type {0}, IP: {1}", DeviceTypeName, clientip), "GPSDeviceTypeComponent", False, False)
            End If

            cp.Thread = New Thread(New ThreadStart(AddressOf cp.Process))
            cp.Thread.IsBackground = True
            cp.Thread.Start()
            listener.BeginAcceptTcpClient(New AsyncCallback(AddressOf DoAcceptTcpClientCallback), listener)

            _connections.AddOrUpdate(cp.Id, cp, Function(k, v) v)
          
        Catch ex As Exception
            ' _logger.LogException(ex, "DeviceTypeComponent : " + _DeviceTypeName, False, False)
        End Try

    End Sub

    Public Sub [Stop]()
        Try
            _logger.LogInformation("Stopping component...", "DeviceTypeComponent : " + _DeviceTypeName, False, False)
            _tmrDeadConnectionCheck.Dispose()

            For Each conn In _connections.Values
                Try
                    conn.Thread.Abort()
                    conn.Thread = Nothing
                    conn.ShutDown()
                Catch ex As Exception
                    '_logger.LogException(ex, "DeviceTypeComponent : " + _DeviceTypeName, False, False)
                End Try
            Next
            _connections.Clear()

            If Not _tcpServer Is Nothing Then _tcpServer.Stop()
            _tcpServer = Nothing
        Catch ex As Exception
            '_logger.LogException(ex, "DeviceTypeComponent : " + _DeviceTypeName, False, False)
        Finally
            If Not _tcpServer Is Nothing Then _tcpServer.Stop()
            _tcpServer = Nothing
        End Try

    End Sub

    Friend Sub RaiseDataArrivalEvent(gpsData As GPSDeviceData)
        RaiseEvent GPSDataArrival(New GPSDeviceTypeComponentEventArg(gpsData), Me)
    End Sub

   Friend Function GetClientIP(client As TcpClient) As String
        Dim ipl As String = String.Empty
        Try
            Dim ipend As IPEndPoint = client.Client.RemoteEndPoint
            If Not ipend Is Nothing Then
                ipl = ipend.Address.ToString
            End If
        Catch ex As Exception
        End Try
        Return If(String.IsNullOrEmpty(ipl), "Unknown", ipl)
    End Function

    Private Sub DeadConnectionCheck(sender As Object)
        If Monitor.TryEnter(_lockObjectTmr, 10000) Then
            Try
                KillDeadConnections()
            Catch ex As Exception
                _logger.LogException(ex, "DeviceTypeComponent : " + _DeviceTypeName, False, False)
            Finally
                Monitor.Exit(_lockObjectTmr)
            End Try
        End If
    End Sub

    Private Sub KillDeadConnections()
        Try
            Dim keys As List(Of String) = Me._connections.Keys.ToList()
            Dim req As ProcessClientRequest = Nothing
            For Each key As String In keys
                Try
                    Dim request As ProcessClientRequest = Nothing
                    If _connections.ContainsKey(key) Then
                        request = _connections(key)
                        If request Is Nothing Then _connections.TryRemove(key, req)

                        If Not IsClientRequestActive(request) Then
                            If Not String.IsNullOrEmpty(request.ClientIP) Then
                                _logger.LogInformation(String.Format("Device with IMEI {0}, IP {1} has disconnected", request.ClientImei, request.ClientIP), "DeviceTypeComponent : " + Me.DeviceTypeName, False, False)
                            End If
                            request.ShutDown()
                            request.Thread.Abort()
                            _connections.TryRemove(key, req)
                        End If
                    End If

                Catch ex As Exception
                    ' _logger.LogException(ex, "DeviceTypeComponent : " + _DeviceTypeName, False, False)
                    If Not _connections Is Nothing AndAlso _connections.ContainsKey(key) Then _connections.TryRemove(key, req)
                End Try
            Next
        Catch ex As Exception
            '  _logger.LogException(ex, "DeviceTypeComponent : " + _DeviceTypeName, False, False)
        End Try

    End Sub

    Private Function IsClientRequestActive(request As ProcessClientRequest) As Boolean

        Try
            If request Is Nothing Then
                Return False
            End If

            If Not request.Thread.IsAlive Then
                Return False
            End If

          If Not request.TcpClient.Connected Then
                Return False
            End If

            Dim test As Integer = request.TcpClient.Available ' TEST

            If request.TcpClient.Client.Poll(0, SelectMode.SelectError) OrElse Not request.TcpClient.Client.Poll(0, SelectMode.SelectRead) Then
                Return False
            Else
                Dim buff As Byte() = {1}
                If request.TcpClient.Client.Receive(buff, SocketFlags.Peek) = 0 Then
                    Return False
                End If
            End If
            If Math.Abs(DateDiff(DateInterval.Second, DateTime.Now, request.LastAccess)) >= 30 Then
                Return False
            End If
        Catch ex As Exception
            Return False
        End Try

        Return True
    End Function

  End Class
