'*************************************************************************
'
'      Author       : Esben Laursen (hyber@hyber.dk)
'      
'      Webpage      : http://www.hyber.dk or http://netsmsgw.sf.net
'
'      Notes        :
'
'      License      : This program is free software; you can redistribute
'                     it and/or modify it under the terms of the GNU
'                     General Public License as published by the Free
'                     Software Foundation version 2 of the License.
'
'*************************************************************************


Imports System.Net.Sockets
Imports System.Text
Imports Microsoft.VisualBasic
Imports System.Threading


Friend Class smtpClientHandle
    Inherits logUtils

    Dim mtcpClient As TcpClient
    Dim mstrServerName As String = "sms.hyber.dk"
    Dim mcolNumbers As New Collection
    Dim mstrSMSText As String
    Dim mstrSender As String
    Dim msmsPriority As netSMSgw.smsUtils.smsPriority = smsUtils.smsPriority.Normal


    'Event ReturnSMSs(ByVal pcolTLF As Collection, ByVal pstrSMSText As String)
    Event ReturnSMSs(ByVal pcolSMSMsgs As Collection)
    Event Log(ByVal pstrLog As String, ByVal penuLoglevel As LogLevels)

    Public Property smtpName() As String
        Get
            Return mstrServerName
        End Get
        Set(ByVal value As String)
            mstrServerName = value
        End Set
    End Property

    Public Sub New(ByVal ptcpClient As TcpClient)
        mtcpClient = ptcpClient
    End Sub


    Public Sub GetEmail()

        Try


            Dim str As String
            Dim strRes As String

            'Send SMTP ready
            '220 mailserver SMTP
            RaiseEvent Log(gstrSMTPSend & "220 " & mstrServerName & " SMTP", LogLevels.Debug)
            str = SendAndGet("220 " & mstrServerName & " SMTP", False)
            RaiseEvent Log(gstrSMTPGot & str, LogLevels.Debug)

            'get response back should be:
            'HELO domain.tld
            If UCase(Left(str, 4)) = "HELO" Or UCase(Left(str, 4)) = "EHLO" Then
                'weeee
            Else
                Throw New Exception("expected ""HELO"" or ""EHLO""! Got: " & str)

            End If

            '250 OK
            RaiseEvent Log(gstrSMTPSend & "250 OK", LogLevels.Debug)
            str = SendAndGet("250 OK", False)
            RaiseEvent Log(gstrSMTPGot & str, LogLevels.Debug)

            'response should be like:
            'MAIL FROM: user@domian.tld
            If Not UCase(Left(str, 10)) = "MAIL FROM:" Then
                Throw New Exception("expected ""MAIL FROM:""! Got: " & str)
            End If

            'get sender address
            mstrSender = GetEmailAdr(str)



            'send
            '250 user@domain.tld OK
            strRes = GetEmailAdr(str)
            RaiseEvent Log(gstrSMTPSend & "250 " & strRes & " OK", LogLevels.Debug)
            str = SendAndGet("250 " & strRes & " OK", False)
            RaiseEvent Log(gstrSMTPGot & str, LogLevels.Debug)

            'there might be more than one RCPT TO:
            While True

                'if the command is "DATA" we know there is not more RCPT TO:
                'and we move on :-)
                If UCase(Left(str, 4)) = "DATA" Then
                    Exit While
                End If

                If Not UCase(Left(str, 8)) = "RCPT TO:" Then
                    Throw New Exception("expected ""RCPT TO""! Got: " & str)
                End If

                'response: RCPT TO: user@domain.tld
                'send: 250 user@email.tld OK
                strRes = GetEmailAdr(str)

                'add email adress, its the one we need to sms :-)
                mcolNumbers.Add(GetTlfNum(strRes))

                RaiseEvent Log(gstrSMTPSend & "250 " & strRes & " OK", LogLevels.Debug)
                str = SendAndGet("250 " & strRes & " OK", False)
                RaiseEvent Log(gstrSMTPGot & str, LogLevels.Debug)

            End While

            'response should be:
            'DATA
            If Not UCase(Left(str, 4)) = "DATA" Then
                Throw New Exception("expected DATA! Got: " & str)
            End If

            'Send back:
            '354 Go ahead
            RaiseEvent Log(gstrSMTPSend & "354 go ahead", LogLevels.Debug)
            str = SendAndGet("354 go ahead", True)
            RaiseEvent Log(gstrSMTPGot & str, LogLevels.Debug)


            'get the subject from message
            mstrSMSText = GetSubject(str)

            'get header priority
            msmsPriority = GetHeaderPriority(str)

            'after data send:
            '250 Message received OK
            RaiseEvent Log(gstrSMTPSend & "250 Message received OK", LogLevels.Debug)
            str = SendAndGet("250 Message received OK", False)
            RaiseEvent Log(gstrSMTPGot & str, LogLevels.Debug)

            If Not UCase(Left(str, 4)) = "QUIT" Then
                RaiseEvent Log("expected Quit! Got: " & str, LogLevels.Warnings)
                'Throw New Exception("expected Quit! Got: " & str)
            End If

            'After Quit, close the connection with:
            '221 server closing
            RaiseEvent Log(gstrSMTPSend & "221 " & mstrServerName & " Closing", LogLevels.Debug)
            SendData("221 " & mstrServerName & " Closing")

            mtcpClient.Close()

            'Call SendSMS(mcolSMS, mstrSMSText)


            RaiseEvent ReturnSMSs(AssembleSMS)



        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            RaiseEvent Log(gstrSMTPSend & "221 " & mstrServerName & " Closing", LogLevels.Debug)
            SendData("221 " & mstrServerName & " Closing")
        End Try

    End Sub

    Private Function AssembleSMS() As Collection

        Dim str(1) As String
        Dim strNum As String
        Dim strPrefix As String = ""
        Dim col As New Collection
        Dim msg As smsMsg

        For Each strNum In mcolNumbers
            msg = New smsMsg

            'if number starts with +, like in +45 or +44 remove the + and add it later.
            If Left(strNum, 1) = "+" Then
                strNum = strNum.Remove(0, 1)
                strPrefix = "+"
            End If

            str = Split(strNum, "+", 2)

            If str.Length > 1 Then
                Select Case LCase(str(1))
                    Case "high" : msg.Priority = smsUtils.smsPriority.High
                    Case "normal" : msg.Priority = smsUtils.smsPriority.Normal
                    Case "low" : msg.Priority = smsUtils.smsPriority.Low
                    Case Else
                        msg.Priority = smsUtils.smsPriority.Normal
                        RaiseEvent Log(gstrUnknownPrio & str(1), LogLevels.Warnings)
                End Select
            Else
                'if priority is not found in recepient address, it might be in body
                'if it is in body we already know it, and can set it.
                msg.Priority = msmsPriority
            End If
            'remember to add the prefix (empty if not used).
            msg.Number = strPrefix & str(0)
            msg.Message = mstrSMSText
            msg.MessagePrefix = mstrSender & ": "
            col.Add(msg, msg.ID)
        Next

        Return col

    End Function


    Private Function GetTlfNum(ByVal pstrEmail As String) As String
        Try
            Dim str(3) As String


            str = Split(pstrEmail, "@", 2)

            RaiseEvent Log(gstrGotTlf & str(0), LogLevels.Debug)
            Return str(0)

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Return ""
        End Try
    End Function


    Private Function GetSubject(ByVal pstr As String) As String

        Try

            Dim str(100) As String
            Dim i As Integer

            str = Split(pstr, vbCrLf, 99)

            Do While i <= 98

                If Microsoft.VisualBasic.LCase(Left(str(i), 8)) = "subject:" Then
                    RaiseEvent Log(gstrSub & Trim(str(i).Remove(0, 8)), LogLevels.Debug)
                    Return Trim(str(i).Remove(0, 8))
                End If

                i += 1
            Loop

            Return "No Subject"

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Return "No Subject"
        End Try
    End Function


    Private Function GetHeaderPriority(ByVal pstr As String) As netSMSgw.smsUtils.smsPriority

        Try

            Dim str(100) As String
            Dim i As Integer
            Dim bolFound As Boolean
            Dim str2 As String = ""
            str = Split(pstr, vbCrLf, 99)

            bolFound = False
            Do While i <= str.Length - 1


                If Microsoft.VisualBasic.LCase(Left(str(i), 9)) = "priority:" Then
                    bolFound = True
                    str2 = LCase(Trim(str(i).Remove(0, 9)))
                ElseIf Microsoft.VisualBasic.LCase(Left(str(i), 11)) = "importance:" Then
                    bolFound = True
                    str2 = LCase(Trim(str(i).Remove(0, 11)))
                ElseIf Microsoft.VisualBasic.LCase(Left(str(i), 11)) = "x-priority:" Then
                    bolFound = True
                    str2 = LCase(Trim(str(i).Remove(0, 11)))
                ElseIf Microsoft.VisualBasic.LCase(Left(str(i), 18)) = "x-msmail-priority:" Then
                    bolFound = True
                    str2 = LCase(Trim(str(i).Remove(0, 18)))
                ElseIf Microsoft.VisualBasic.LCase(Left(str(i), 11)) = "x-priority:" Then
                    bolFound = True
                    str2 = LCase(Trim(str(i).Remove(0, 11)))
                End If

                If bolFound Then


                    If InStr(str2, "high") Or Left(str2, 6) = "urgent" Or Left(str2, 1) = "1" Then
                        Return smsUtils.smsPriority.High
                    ElseIf InStr(str2, "low") Or Left(str2, 10) = "non-urgent" Or Left(str2, 1) = "0" Then
                        Return smsUtils.smsPriority.Low
                    Else
                        Return smsUtils.smsPriority.Normal
                        RaiseEvent Log(gstrFoundPrioNoParse & str2, LogLevels.Warnings)
                    End If
                End If

                i += 1
            Loop

            'nothing found..
            Return smsUtils.smsPriority.Normal

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Return smsUtils.smsPriority.Normal
        End Try
    End Function

    Private Function GetEmailAdr(ByVal pstr As String) As String

        Try

            Dim str As String
            Dim i As Integer

            'get start of email
            i = InStr(pstr, "<")
            'remove until start of email
            str = pstr.Remove(0, i)
            'get end of email
            i = InStr(str, ">")
            'get only until ">" -1

            RaiseEvent Log(gstrGotEmailAdr & str(i - 1), LogLevels.Debug)
            Return Microsoft.VisualBasic.Left(str, i - 1)

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Return ""
        End Try
    End Function

    Private Function SendAndGet(ByVal pstr As String, ByVal bolMultiLine As Boolean) As String
        Try

            Dim netStream As NetworkStream
            Dim strBuilder As New StringBuilder("")
            Dim str As String

            netStream = mtcpClient.GetStream()

            Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes(pstr & vbCrLf)
            netStream.Write(sendBytes, 0, sendBytes.Length)


            Dim reader As New IO.StreamReader(netStream)


            If bolMultiLine Then
                Do While True
                    str = reader.ReadLine
                    If str = "." Then
                        Exit Do
                    End If
                    strBuilder.AppendLine(str)
                Loop

            Else
                str = reader.ReadLine
                strBuilder.AppendLine(str)
            End If

            Return strBuilder.ToString


        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
            Return ""
        End Try
    End Function


    Private Sub SendData(ByVal pstr As String)

        Try

            Dim netStream As NetworkStream

            netStream = mtcpClient.GetStream()

            Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes(pstr & vbCrLf)
            netStream.Write(sendBytes, 0, sendBytes.Length)

        Catch ex As Exception
            RaiseEvent Log(gstrCriticalErr & ex.Message.ToString, LogLevels.Critical)
        End Try

    End Sub

End Class
