﻿Namespace British

    Public Class Cup(Of T)

        Private _Random As Random
        Private _Timer As Timer
        Private _ReheatingTime As TimeSpan
        Private _ReheatingStopwatch As Stopwatch
        Private _IsReheating As Boolean

        Protected Contents As Stack(Of T)

        ''' <summary>
        ''' Creates a new Cup(Of T) of the specified size.
        ''' </summary>
        ''' <param name="size">How much T your Cup(Of T) can hold without overflowing.</param>
        Public Sub New(size As Integer)
            If size <= 0 Then
                Throw New ArgumentOutOfRangeException("size", "Such a small cup does not exist!")
            End If

            _Size = size
            _Random = New Random()
            Me.Contents = New Stack(Of T)()
            Me.Temperature = 0

            _Timer = New Timer()
            _Timer.Interval = 500
            AddHandler _Timer.Tick, AddressOf Timer_Tick
        End Sub

        ''' <summary>
        ''' Creates a new Cup(Of T) of the specified size.
        ''' </summary>
        ''' <param name="size">How much T your Cup(Of T) can hold without overflowing.</param>
        ''' <param name="contents">The initial contents of your Cup(Of T)</param>
        Public Sub New(size As Integer, contents As IEnumerable(Of T))
            Me.New(size)
            For Each content As T In contents
                Me.Push(content)
            Next
            Me.Temperature = 100
        End Sub

#Region " Properties "

        Private _Temperature As Integer
        ''' <summary>
        ''' Gets the current temperature of your Cup(Of T). Be careful when drinking hot fluids!
        ''' </summary>
        ''' <returns>The current temperature of your Cup(Of T).</returns>
        Public Property Temperature() As Integer
            Get
                Return _Temperature
            End Get
            Private Set(ByVal value As Integer)
                If _Temperature <> value Then
                    _Temperature = value
                    Me.OnTemperatureChanged(EventArgs.Empty)
                End If
            End Set
        End Property

        Private _Size As Integer
        ''' <summary>
        ''' Gets the size of your cup. Be careful not to overflow it!
        ''' </summary>
        ''' <returns>The size of your cup.</returns>
        Public ReadOnly Property Size() As Integer
            Get
                Return _Size
            End Get
        End Property

#End Region

#Region " Methods "

        Private Sub Push(content As T)
            Me.Contents.Push(content)
            If Me.Contents.Count > Me.Size Then
                Throw New OverflowException("Your cup is too full and has overflown. Look at the mess you made!")
            End If
        End Sub

        Private Sub CanDrink()
            If Me.Contents.Count = 0 Then
                Throw New EmptyCupException("You cannot drink from an empty cup. Try calling Refill() first.")
            End If
            If Me.Temperature <= 0 Then
                Throw New TemperatureException("Your T has frozen. Try calling Reheat() first.")
            End If
            If Me.Temperature >= 40 Then
                Throw New TemperatureException("You burned your tongue. Try waiting some more or calling Blow() first.")
            End If
        End Sub

        ''' <summary>
        ''' If you can currently drink the T, you will take a small sip.
        ''' </summary>
        Public Sub Sip()
            Me.CanDrink()
            Me.Contents.Pop()
        End Sub

        ''' <summary>
        ''' If you can currently drink the T, you will drink about a quarter of the T.
        ''' </summary>
        Public Sub Drink()
            Me.CanDrink()

            Dim amount As Integer = _Random.Next(CInt(Me.Contents.Count * (1 / 8)), CInt(Me.Contents.Count * (3 / 8)))
            For i As Integer = 0 To amount - 1
                Me.Contents.Pop()
            Next
        End Sub

        ''' <summary>
        ''' Stir around the contents of the T.
        ''' </summary>
        Public Sub Stir()
            Dim oldContents = Me.Contents.ToList()
            Dim newContents = From c As T In oldContents Order By _Random.NextDouble() Select c

            Me.Contents.Clear()
            For Each content In newContents
                Me.Push(content)
            Next
        End Sub

        ''' <summary>
        ''' Add some extra T to your cup. Note that the temperature will change!
        ''' </summary>
        Public Sub Refill(contents As IEnumerable(Of T))
            Dim amount = Me.Contents.Count
            For Each content As T In contents
                Me.Push(content)
            Next

            Dim ratio = amount / Me.Contents.Count
            Me.Temperature = CInt(Me.Temperature * ratio + 100 * (ratio - 1))
        End Sub

        ''' <summary>
        ''' Reheats your T for the specified time.
        ''' </summary>
        ''' <param name="time">The time to reheat your T for. Be careful not to make it too hot!</param>
        Public Sub Reheat(time As TimeSpan)
            _IsReheating = True
            _ReheatingTime = time
            _ReheatingStopwatch = New Stopwatch
            _ReheatingStopwatch.Start()
        End Sub

        ''' <summary>
        ''' Blows in your Cup(Of T) to cool it down.
        ''' </summary>
        Public Sub Blow()
            Me.Temperature -= 10
        End Sub

        ''' <summary>
        ''' If the temperature is too high some T will evaporate!
        ''' </summary>
        Private Sub Evaporate()
            For i As Integer = 0 To _Random.Next(0, 5)
                If Me.Contents.Count > 0 Then Me.Contents.Pop()
            Next
        End Sub

        Private Sub Timer_Tick(sender As Object, e As EventArgs)
            If _IsReheating Then
                If _ReheatingStopwatch.Elapsed >= _ReheatingTime Then
                    _ReheatingStopwatch.Stop()
                    _IsReheating = False
                End If

                Me.Temperature += _Random.Next(10, 20)
            Else
                Me.Temperature -= _Random.Next(0, 5)
            End If

            If Me.Temperature >= 100 Then
                Me.Evaporate()
            End If
        End Sub

#End Region

#Region " Events "

        Public Event TemperatureChanged As EventHandler

        Protected Overridable Sub OnTemperatureChanged(e As EventArgs)
            RaiseEvent TemperatureChanged(Me, e)
        End Sub

#End Region

#Region " Nested classes "

        Public Class OverflowException
            Inherits System.Exception

            Public Sub New(msg As String)
                MyBase.New(msg)
            End Sub
        End Class

        Public Class EmptyCupException
            Inherits System.Exception

            Public Sub New(msg As String)
                MyBase.New(msg)
            End Sub
        End Class

        Public Class TemperatureException
            Inherits System.Exception

            Public Sub New(msg As String)
                MyBase.New(msg)
            End Sub
        End Class

#End Region

    End Class

End Namespace
