Imports System.Data
Imports System.DirectoryServices
Imports System.Globalization

Public Class MainWindow

#Region " Fields "

    Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker

#End Region

#Region " Properties "

    Public Shared ReadOnly Property TrafficTable() As DataTable
        Get
            Dim tblTraffic As New DataTable("IIsTrafficMonitorOutput")

            tblTraffic.Columns.Add(New DataColumn("Site", GetType(String)))
            tblTraffic.Columns.Add(New DataColumn("SiteId", GetType(String))) 'may not sort well
            tblTraffic.Columns.Add(New DataColumn("Status", GetType(String)))
            tblTraffic.Columns.Add(New DataColumn("Traffic", GetType(String)))
            tblTraffic.Columns.Add(New DataColumn("ExactBytesSent", GetType(Double)))
            tblTraffic.Columns.Add(New DataColumn("ExactBytesReceived", GetType(Double)))
            tblTraffic.Columns.Add(New DataColumn("ExactBytesTotal", GetType(Double)))
            tblTraffic.Columns.Add(New DataColumn("Info", GetType(String)))
            tblTraffic.DefaultView.Sort = "Site"

            Return tblTraffic
        End Get
    End Property

#End Region

#Region " Methods "

#Region " Events Handlers "

#Region " UI "

    Private Sub MainWindow_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ResetData()
        ResetUi()

        'select default traffic direction
        tsDirection.SelectedIndex = 0

        'create a background worker for threading later
        bgWorker = New System.ComponentModel.BackgroundWorker
        bgWorker.WorkerReportsProgress = True
        bgWorker.WorkerSupportsCancellation = True

        'setup dates to default with the current month
        dtPickerFrom.Value = New DateTime(Date.Now.Year, Date.Now.Month, 1)
        dtPickerTo.Value = dtPickerFrom.Value.AddMonths(1)
    End Sub

    Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click
        Me.Close()
    End Sub

    Private Sub btnTopRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTopRefresh.Click
        LoadData()
    End Sub

    Private Sub gridRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles gridRefresh.Click
        LoadData()
    End Sub

    Private Sub btnAbout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAbout.Click
        Dim myAbout As New About
        myAbout.ShowDialog()
    End Sub

    Private Sub btnSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSave.Click
        diaSave.ShowDialog()
    End Sub

    Private Sub diaSave_FileOk(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles diaSave.FileOk
        Try
            If IO.File.Exists(diaSave.FileName) Then _
                IO.File.Delete(diaSave.FileName)

            Dim targetFile As New IO.FileStream(diaSave.FileName, IO.FileMode.CreateNew)
            CType(dgDisplay.DataSource, DataTable).WriteXml(targetFile)
            targetFile.Close()
        Catch ex As Exception
            MessageBox.Show("Unable to save the file: " & ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

    'Private Sub btnAllSites_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAllSites.Click
    '    If btnAllSites.Checked Then
    '        'button just checked, prompt for confirmation
    '        If Not MessageBox.Show("Listing all sites on a large server can take a few seconds - are you sure?", "Confirm", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.OK Then _
    '            btnAllSites.Checked = False
    '    End If
    'End Sub

    Private Sub btnCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        bgWorker.CancelAsync()
    End Sub

    Private Sub tsDirection_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tsDirection.SelectedIndexChanged
        If Not IsNothing(dgDisplay.DataSource) Then
            'refresh list view with appropriate data
            UpdateTrafficColumn(dgDisplay.DataSource)
            UpdateDisplayedTrafficColumns()

            dgDisplay.Refresh()
        End If

        dgDisplay.Focus()
    End Sub

#End Region

#Region " Threading "

    Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
        Dim worker As System.ComponentModel.BackgroundWorker = CType(sender, System.ComponentModel.BackgroundWorker)

        e.Result = threadedLoadData(worker, e)
    End Sub

    Private Sub bgWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
        tsProgressBar.Value = e.ProgressPercentage
        statusLabel.Text = "Working... " & e.ProgressPercentage & "%"
    End Sub

    Private Sub bgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted

        If Not IsNothing(e.Error) Then
            MessageBox.Show(e.Error.Message)
            ResetData()
            ResetUi()
        Else
            If IsNothing(e.Result) Then
                ResetData()
                ResetUi()
            Else
                dgDisplay.DataSource = UpdateTrafficColumn(CType(e.Result, DataTable))

                dgDisplay.Columns("SiteId").Visible = False
                dgDisplay.Columns("ExactBytesSent").Visible = False
                dgDisplay.Columns("ExactBytesReceived").Visible = False
                dgDisplay.Columns("ExactBytesTotal").Visible = False

                Select Case tsDirection.SelectedItem.ToString
                    Case "Both"
                        dgDisplay.Columns("ExactBytesTotal").HeaderText = "Exact bytes"
                        dgDisplay.Columns("ExactBytesTotal").Visible = True
                    Case "Sent"
                        dgDisplay.Columns("ExactBytesSent").HeaderText = "Exact bytes"
                        dgDisplay.Columns("ExactBytesSent").Visible = True
                    Case "Received"
                        dgDisplay.Columns("ExactBytesReceived").HeaderText = "Exact bytes"
                        dgDisplay.Columns("ExactBytesReceived").Visible = True
                End Select

                doneLoading()
            End If
        End If

    End Sub

#End Region

#End Region

    Private Sub ResetData()
        dgDisplay.DataSource = Nothing 'TrafficTable()

        'dgdisplay.Columns("SiteId").Visible = False
    End Sub

    Private Sub ResetUi()
        lblWait.Visible = False
        tsProgressBar.Value = 0
        btnExit.Enabled = True
        btnAllSites.Enabled = True
        btnAbout.Enabled = True
        btnSave.Enabled = False
        btnTopRefresh.Visible = True
        btnCancel.Visible = False
        gridRefresh.Enabled = True
        Me.Cursor = Cursors.Default
        statusLabel.Text = "Done"
    End Sub

    Private Sub LoadData()
        ResetData()
        ResetUi()

        'setup the ui to show we are busy
        statusLabel.Text = "Working..."
        dgDisplay.DataSource = Nothing
        lblWait.Visible = True
        btnExit.Enabled = False
        btnAllSites.Enabled = False
        btnAbout.Enabled = False
        btnTopRefresh.Visible = False
        btnCancel.Visible = True
        gridRefresh.Enabled = False
        Me.Cursor = Cursors.AppStarting

        'run the worker thread
        bgWorker.RunWorkerAsync(dgDisplay)
    End Sub

    Private Sub doneLoading()
        'setup the ui to show we are done working
        ResetUi()
        btnSave.Enabled = True
    End Sub

    Private Function threadedLoadData(ByVal worker As System.ComponentModel.BackgroundWorker, ByVal e As System.ComponentModel.DoWorkEventArgs) As DataTable
        Dim tblTraffic As DataTable = TrafficTable()

        Dim logParser As New MSUtil.LogQueryClass
        Dim iisLog As New MSUtil.COMIISW3CInputContextClass

        Dim strFormat As String = "yyyy-MM-dd"

        Dim dateFrom As String = dtPickerFrom.Value.ToString(strFormat, DateTimeFormatInfo.InvariantInfo) & " 00:00:00"
        Dim dateTo As String = dtPickerTo.Value.ToString(strFormat, DateTimeFormatInfo.InvariantInfo) & " 23:59:59"

        Dim root As New DirectoryEntry("IIS://localhost/W3SVC")
        Dim siteName As String
        Dim siteId As String
        Dim siteQuery As String
        Dim rsLp As MSUtil.ILogRecordset
        Dim rowLp As MSUtil.ILogRecord
        Dim logRow As DataRow

        'this is a hack to work out how many sites there are
        Dim sites As New ArrayList
        Dim percentile As Decimal
        Dim sitePointer As Integer = 0

        For Each dirEntry As DirectoryEntry In root.Children
            If dirEntry.SchemaClassName.Equals("IIsWebServer") Then _
                sites.Add(dirEntry)
        Next

        percentile = 100D / Convert.ToDecimal(sites.Count)

        For Each dirEntry As DirectoryEntry In sites
            sitePointer += 1

            siteName = dirEntry.Properties("ServerComment").Value.ToString
            siteId = dirEntry.Name

            logRow = tblTraffic.NewRow
            logRow("Site") = siteName
            logRow("SiteId") = siteId
            logRow("Status") = "Stopped"

            If Convert.ToBoolean(dirEntry.Properties("ServerAutoStart").Value) Then
                logRow("Status") = "Running"
                If Not Convert.ToInt32(dirEntry.Properties("ServerState").Value).Equals(2) Then _
                    logRow("Status") = "Paused"
            End If

            If logRow("Status") = "Running" Or btnAllSites.Checked Then
                siteQuery = "SELECT SUM(TO_REAL(sc-bytes)) AS sentTotal, SUM(TO_REAL(cs-bytes)) AS rcvdTotal FROM <" & siteId & "> WHERE TO_TIMESTAMP(date, time) >= TO_TIMESTAMP('" & dateFrom & "', 'yyyy-MM-dd hh:mm:ss') AND TO_TIMESTAMP(date, time) <= TO_TIMESTAMP('" & dateTo & "', 'yyyy-MM-dd hh:mm:ss')"

                rsLp = Nothing
                rowLp = Nothing

                Try
                    rsLp = logParser.Execute(siteQuery, iisLog)
                    rowLp = rsLp.getRecord

                    logRow("ExactBytesSent") = rowLp.getValue("sentTotal")
                    logRow("ExactBytesReceived") = rowLp.getValue("rcvdTotal")

                Catch ex As Exception
                    Dim finalException As Exception = ex

                    While Not IsNothing(finalException.InnerException)
                        finalException = finalException.InnerException
                    End While

                    logRow("Info") = finalException.Message
                End Try

                tblTraffic.Rows.Add(logRow)
            End If

            worker.ReportProgress(percentile * sitePointer)

            If bgWorker.CancellationPending Then _
                Return Nothing
        Next

        Return tblTraffic
    End Function

    Private Function UpdateTrafficColumn(ByVal currentTable As DataTable) As DataTable
        Dim tmpSent, tmpRcvd As Double

        For Each myRow As DataRow In currentTable.Rows
            tmpSent = 0D
            tmpRcvd = 0D

            If Not IsDBNull(myRow("ExactBytesSent")) Then _
                tmpSent = Convert.ToDouble(myRow("ExactBytesSent"))
            If Not IsDBNull(myRow("ExactBytesReceived")) Then _
                tmpRcvd = Convert.ToDouble(myRow("ExactBytesReceived"))

            myRow("ExactBytesTotal") = tmpSent + tmpRcvd
            myRow("ExactBytesSent") = tmpSent
            myRow("ExactBytesReceived") = tmpRcvd
            myRow("Traffic") = String.Empty

            Select Case tsDirection.SelectedItem.ToString
                Case "Both"
                    If tmpSent + tmpRcvd > 0D Then _
                        myRow("Traffic") = FormatBytes(tmpSent + tmpRcvd)
                Case "Sent"
                    If tmpSent > 0D Then _
                        myRow("Traffic") = FormatBytes(tmpSent)
                Case "Received"
                    If tmpRcvd > 0D Then _
                        myRow("Traffic") = FormatBytes(tmpRcvd)
            End Select

            currentTable.AcceptChanges()
        Next

        Return currentTable
    End Function

    Private Sub UpdateDisplayedTrafficColumns()
        dgDisplay.Columns("ExactBytesSent").Visible = False
        dgDisplay.Columns("ExactBytesReceived").Visible = False
        dgDisplay.Columns("ExactBytesTotal").Visible = False

        Select Case tsDirection.SelectedItem.ToString
            Case "Both"
                dgDisplay.Columns("ExactBytesTotal").HeaderText = "Exact bytes"
                dgDisplay.Columns("ExactBytesTotal").Visible = True
            Case "Sent"
                dgDisplay.Columns("ExactBytesSent").HeaderText = "Exact bytes"
                dgDisplay.Columns("ExactBytesSent").Visible = True
            Case "Received"
                dgDisplay.Columns("ExactBytesReceived").HeaderText = "Exact bytes"
                dgDisplay.Columns("ExactBytesReceived").Visible = True
        End Select
    End Sub

    Private Function FormatBytes(ByVal num_bytes As Double) As String
        Const ONE_KB As Double = 1024
        Const ONE_MB As Double = ONE_KB * 1024
        Const ONE_GB As Double = ONE_MB * 1024
        Const ONE_TB As Double = ONE_GB * 1024
        Const ONE_PB As Double = ONE_TB * 1024
        Const ONE_EB As Double = ONE_PB * 1024
        Const ONE_ZB As Double = ONE_EB * 1024
        Const ONE_YB As Double = ONE_ZB * 1024

        ' See how big the value is.
        If num_bytes <= 999 Then
            ' Format in bytes.
            Return Format$(num_bytes, "0") & " bytes"
        ElseIf num_bytes <= ONE_KB * 999 Then
            ' Format in KB.
            Return ThreeNonZeroDigits(num_bytes / ONE_KB) & " Kb"
        ElseIf num_bytes <= ONE_MB * 999 Then
            ' Format in MB.
            Return ThreeNonZeroDigits(num_bytes / ONE_MB) & " Mb"
        ElseIf num_bytes <= ONE_GB * 999 Then
            ' Format in GB.
            Return ThreeNonZeroDigits(num_bytes / ONE_GB) & " Gb"
        ElseIf num_bytes <= ONE_TB * 999 Then
            ' Format in TB.
            Return ThreeNonZeroDigits(num_bytes / ONE_TB) & " Tb"
        ElseIf num_bytes <= ONE_PB * 999 Then
            ' Format in PB.
            Return ThreeNonZeroDigits(num_bytes / ONE_PB) & " Pb"
        ElseIf num_bytes <= ONE_EB * 999 Then
            ' Format in EB.
            Return ThreeNonZeroDigits(num_bytes / ONE_EB) & " Eb"
        ElseIf num_bytes <= ONE_ZB * 999 Then
            ' Format in ZB.
            Return ThreeNonZeroDigits(num_bytes / ONE_ZB) & " Zb"
        Else
            ' Format in YB.
            Return ThreeNonZeroDigits(num_bytes / ONE_YB) & " Yb"
        End If
    End Function

    Private Function ThreeNonZeroDigits(ByVal value As Double) As String
        If value >= 100 Then
            ' No digits after the decimal.
            Return Format$(CInt(value))
        ElseIf value >= 10 Then
            ' One digit after the decimal.
            Return Format$(value, "0.0")
        Else
            ' Two digits after the decimal.
            Return Format$(value, "0.00")
        End If
    End Function

#End Region

End Class
