    Imports System
    Imports System.Web
    Imports System.Web.UI.WebControls
    Imports ASPNET.StarterKit.TimeTracker.BusinessLogicLayer

    Namespace ASPNET.StarterKit.TimeTracker.Web

        '*********************************************************************
        '
        ' ProjectDetails.aspx
        '
        ' The ProjectDetails.aspx page is used to add new projects or make changes to
        ' existing projects.  Project membership are associated using this page.
        ' In addition, project categories are added and changed on this page.
        ' Only members of the Admin or Manager Role can access this page.
        ' Members of the Admin group can modify all projects, while members of the
        ' Managers group can only modify the projects they are managing.
        '
        '*********************************************************************

        Public Class ProjectDetails
            Inherits System.Web.UI.Page
            Protected ProjectName As System.Web.UI.WebControls.TextBox
            Protected Description As System.Web.UI.WebControls.TextBox
            Protected CompletionDate As System.Web.UI.WebControls.TextBox
            Protected Duration As System.Web.UI.WebControls.TextBox
            Protected Members As System.Web.UI.WebControls.ListBox
            Protected WithEvents CopyButton As System.Web.UI.WebControls.Button
            Protected WithEvents AddButton As System.Web.UI.WebControls.Button
            Protected WithEvents SaveButton As System.Web.UI.WebControls.Button
            Protected Managers As System.Web.UI.WebControls.DropDownList
            Protected Projects As System.Web.UI.WebControls.DropDownList
            Protected CategoryName As System.Web.UI.WebControls.TextBox
            Protected Abbrev As System.Web.UI.WebControls.TextBox
            Protected CatDuration As System.Web.UI.WebControls.TextBox
            Protected WithEvents CategoriesGrid As System.Web.UI.WebControls.DataGrid
            Protected ProjectNameRequiredfieldvalidator As System.Web.UI.WebControls.RequiredFieldValidator
            Protected ManagerRequiredFieldValidator As System.Web.UI.WebControls.RequiredFieldValidator
            Protected CompletionDateCompareValidator As System.Web.UI.WebControls.CompareValidator
            Protected CompletionDateRequiredFieldValidator As System.Web.UI.WebControls.RequiredFieldValidator
            Protected DurationCompareValidator As System.Web.UI.WebControls.CompareValidator
            Protected DurationRequiredFieldValidator As System.Web.UI.WebControls.RequiredFieldValidator
            Protected WithEvents ProjectsGridCustomValidator As System.Web.UI.WebControls.CustomValidator
            Protected RegularExpressionValidatorAbbrev As System.Web.UI.WebControls.RegularExpressionValidator
            Protected CatDurationValidator As System.Web.UI.WebControls.CompareValidator
            Protected WithEvents AbbrevCustomValidator As System.Web.UI.WebControls.CustomValidator
            Protected ErrorMessage As System.Web.UI.WebControls.Label
            Protected WithEvents CancelButton As System.Web.UI.WebControls.Button
            Protected WithEvents CancelButton2 As System.Web.UI.WebControls.Button
            Protected WithEvents DeleteButton As System.Web.UI.WebControls.Button
            Protected WithEvents DeleteButton2 As System.Web.UI.WebControls.Button
            Protected WithEvents SaveButton2 As System.Web.UI.WebControls.Button
            Protected RangeValidator1 As System.Web.UI.WebControls.RangeValidator
            Protected RangeValidator2 As System.Web.UI.WebControls.RangeValidator
            Protected WithEvents CategoryNameValidator As System.Web.UI.WebControls.RegularExpressionValidator
            Protected WithEvents RegularExpressionValidator1 As System.Web.UI.WebControls.RegularExpressionValidator
            Protected WithEvents CategoryErrorMessage As System.Web.UI.WebControls.Label

            ' Contains the id of current project
            Private _projID As Integer

            Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
                ' Ensure that the visiting user has access to view the current page
                If TTSecurity.IsInRole(TTUser.UserRoleAdminPMgr) = False Then
                    Response.Redirect("AccessDenied.aspx?Index=-1", True)
                End If

                ' Obtain project id from the QueryString.
                If Request.QueryString("id") Is Nothing Then
                    _projID = 0
                Else
                    _projID = Convert.ToInt32(Request.QueryString("id"))
                End If
                If Not IsPostBack Then
                    ' The categories collection is stored in session
                    ' Also the categories are not saved in the database until the user clicks Save for the whole Project
                    Session("catArray") = New CategoriesCollection()

                    BindManagers()
                    BindMembers()
                    BindOtherProjects()

                    ' Load project with _projID when project id exists in the QueryString
                    If _projID <> 0 Then
                        BindProject()
                    End If
                End If
                ' Add warning and confirmation when user tries to delete a project
                DeleteButton.Attributes.Add("onclick", "return confirm('Deleting a project will also delete all the time entries and categories associated with the project. This deletion cannot be undone. Are you sure you want to delete this project?')")
                DeleteButton2.Attributes.Add("onclick", "return confirm('Deleting a project will also delete all the time entries and categories associated with the project. This deletion cannot be undone. Are you sure you want to delete this project?')")
            End Sub 'Page_Load

            '*********************************************************************
            '
            ' The BindManagers method retrieves the list of users that are in the Managers Role
            ' and then databinds them to the Managers dropdownlist.
            '
            '*********************************************************************

            Private Sub BindManagers()
                Managers.DataSource = TTUser.ListManagers()
                Managers.DataValueField = "UserID"
                Managers.DataTextField = "Name"
                Managers.DataBind()

                ' insert blank choice on top of the managers dropdownlist.
                Managers.Items.Insert(0, New ListItem("Select Manager...", [String].Empty))
            End Sub 'BindManagers

            '*********************************************************************
            '
            ' The BindMembers method retrieves the list of Users the current user can view and
            ' then databinds them to the Members listbox.
            '
            '*********************************************************************

            Private Sub BindMembers()
                Members.DataSource = TTUser.GetAllUsers(TTSecurity.GetUserID())
                Members.DataValueField = "UserID"
                Members.DataTextField = "Name"
                Members.DataBind()
            End Sub 'BindMembers

            '*********************************************************************
            '
            ' The BindOtherProjects method retrieves the list of all projects, databinds to
            ' the Projects dropdownlist, and then remove the current project from that list.
            '
            '*********************************************************************

            Private Sub BindOtherProjects()
                Projects.DataSource = Project.GetProjects()
                Projects.DataTextField = "Name"
                Projects.DataValueField = "ProjectID"
                Projects.DataBind()

                ' remove current project from list of projects
                Projects.Items.Remove(Projects.Items.FindByValue(_projID.ToString()))

                ' Disable "Copy" functionality if there are no projects in the list
                If Projects.Items.Count = 0 Then
                    CopyButton.Enabled = False
                End If
            End Sub 'BindOtherProjects

            '*********************************************************************
            '
            ' The BindProject method retrieves the project with _projID and populates
            ' the web controls with the project's info.
            '
            '*********************************************************************

            Private Sub BindProject()
                Dim project As New project(_projID)
                project.Load()

                ' Populate web controls with the project's info.
                ProjectName.Text = project.Name
                Description.Text = project.Description
                CompletionDate.Text = project.EstCompletionDate.ToShortDateString()
                Duration.Text = project.EstDuration.ToString()
                Managers.Items.FindByValue(project.ManagerUserID.ToString()).Selected = True

                ' Sets the selected members of the project.
                Dim user As TTUser
                For Each user In project.Members
                    Members.Items.FindByValue(user.UserID.ToString()).Selected = True
                Next user

                BindCategoriesGrid(project.Categories)
            End Sub 'BindProject

            '*********************************************************************
            '
            ' The BindCategoriesGrid method is a helper take takes in a collection of categories
            ' and databinds the Categories datagrid with that collection.
            '
            ' The categories are also then stored in session.
            '
            '*********************************************************************

            Private Sub BindCategoriesGrid(ByVal cats As CategoriesCollection)
                ' Call method to sort the data before databinding
                SortGridData(cats, SortField, SortAscending)

                CategoriesGrid.DataSource = cats
                CategoriesGrid.DataBind()

                ' Store the list of categories in session
                Session("catArray") = cats
            End Sub 'BindCategoriesGrid

            '*********************************************************************
            '
            ' The ReturnToProjectList method redirects to the ProjectList.aspx page.
            '
            '*********************************************************************

            Private Sub ReturnToProjectList()
                ' Clear the current project categories that are stored in session.
                Session("catArray") = Nothing

                ' Return user to Project List Page.
                Dim mainIndex As Integer
                If Request("index") Is Nothing Then
                    mainIndex = 0
                Else
                    mainIndex = Convert.ToInt32(Request("index"))
                End If
                Dim adminIndex As Integer
                If Request("adminindex") Is Nothing Then
                    adminIndex = 0
                Else
                    adminIndex = Convert.ToInt32(Request("adminindex"))
                End If
                Response.Redirect([String].Format("ProjectList.aspx?index={0}&adminindex={1}", mainIndex, adminIndex), False)
            End Sub 'ReturnToProjectList

            '*********************************************************************
            '
            ' The SortGridData method is a helper method called when databinding the Categories grid
            ' to sort the columns of the grid.
            '
            '*********************************************************************

            Private Sub SortGridData(ByVal list As CategoriesCollection, ByVal sortField As String, ByVal asc As Boolean)
                Dim sortCol As CategoriesCollection.CategoryFields = CategoriesCollection.CategoryFields.InitValue

                Select Case sortField
                    Case "Name"
                        sortCol = CategoriesCollection.CategoryFields.Name
                    Case "Abbrev"
                        sortCol = CategoriesCollection.CategoryFields.Abbreviation
                    Case "Duration"
                        sortCol = CategoriesCollection.CategoryFields.Duration
                End Select

                list.Sort(sortCol, asc)
            End Sub 'SortGridData

            Private Sub CancelButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CancelButton.Click, CancelButton2.Click
                ReturnToProjectList()
            End Sub 'CancelButton_Click

            '*********************************************************************
            '
            ' The DeleteButton_Click event handler calls a method that deletes the project,
            ' all of its categories, projectmembers and their time entries.
            '
            '*********************************************************************

            Private Sub DeleteButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DeleteButton.Click, DeleteButton2.Click
                Project.Remove(_projID)
                ReturnToProjectList()
            End Sub 'DeleteButton_Click

            '*********************************************************************
            '
            ' The SaveButton_Click event handler saves the configuration of the project.
            ' It updates if the current project id is not 0 and inserts if it is 0.
            '
            '*********************************************************************

            Private Sub SaveButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SaveButton.Click, SaveButton2.Click
                ' Add the project manager as member of the project.
                Members.Items.FindByValue(Managers.SelectedItem.Value).Selected = True

                ' Make sure that there is at least one category.
                ProjectsGridCustomValidator.Validate()
                If Not ProjectsGridCustomValidator.IsValid Then
                    Return
                End If
                ' Make sure that there are no duplicate categories.
                AbbrevCustomValidator.Validate()
                If Not AbbrevCustomValidator.IsValid Then
                    Return
                End If
                ' Retrieve the current list of categories from session.
                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)

                ' Get list of members the user selected.
                Dim selectedMembers As New UsersCollection()
                Dim li As ListItem
                For Each li In Members.Items
                    If li.Selected Then
                        Dim user As New TTUser()
                        user.UserID = Convert.ToInt32(li.Value)
                        selectedMembers.Add(user)
                    End If
                Next li

                Dim prj As New Project(_projID, TTSecurity.CleanStringRegex(ProjectName.Text), TTSecurity.CleanStringRegex(Description.Text), Convert.ToInt32(Managers.SelectedItem.Value), Convert.ToDateTime(CompletionDate.Text), Convert.ToDecimal(Duration.Text))
                prj.Categories = catArray
                prj.Members = selectedMembers

                If prj.Save() Then
                    ReturnToProjectList()
                Else
                    ErrorMessage.Text = "There was an error.  You cannot remove a member with time entries from this project."
                End If
            End Sub 'SaveButton_Click

            '*********************************************************************
            '
            ' The AddButton_Click event handler adds a category to the categories collection
            ' in session and displays it in the datagrid.
            '
            '*********************************************************************

            Private Sub AddButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AddButton.Click
                ' check to make sure duplicated category abbreviations are avoided.
                If Not AbbrevCustomValidator.IsValid Then
                    Return
                End If
                ' a required field validator is not used for this case because only the add button click should
                ' check for this and not the save button.
                If CategoryName.Text = "" Then
                    CategoryErrorMessage.Text = "Category name is required."
                    Return
                End If

                ' a required field validator is not used for this case because only the add button click should
                ' check for this and not the save button.
                If Abbrev.Text = "" Then
                    CategoryErrorMessage.Text = "Category abbreviation is required."
                    Return
                End If

                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)
                If catArray Is Nothing Then
                    catArray = New CategoriesCollection()
                End If
                ' categories need a unique identifier when added, since they are not yet saved to
                ' the database but in session, a counter is used to populate their id.
                Dim catID As Integer
                If Not (Session("catID") Is Nothing) Then
                    catID = Convert.ToInt32(Session("catID")) - 1
                Else
                    catID = -1
                End If
                Session("catID") = catID

                Dim cat As New Category()
                cat.CategoryID = catID
                cat.Name = TTSecurity.CleanStringRegex(CategoryName.Text)
                cat.Abbreviation = TTSecurity.CleanStringRegex(Abbrev.Text)

                If CatDuration.Text.Length = 0 Then
                    cat.EstDuration = 0
                Else
                    cat.EstDuration = Convert.ToDecimal(CatDuration.Text)
                End If
                catArray.Add(cat)

                ' Re-Validate now that users have added a new category.
                ProjectsGridCustomValidator.Validate()

                BindCategoriesGrid(catArray)

                CategoryName.Text = String.Empty
                Abbrev.Text = String.Empty
                CatDuration.Text = String.Empty
            End Sub 'AddButton_Click

            '*********************************************************************
            '
            ' The CopyButton_Click event handler copies the list of categories from the
            ' selected project in the Projects dropdownlist.
            '
            '*********************************************************************

            Private Sub CopyButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CopyButton.Click
                Dim catID As Integer
                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)
                If catArray Is Nothing Then
                    catArray = New CategoriesCollection()
                End If
                Dim projcatArray As CategoriesCollection = Project.GetCategories(Convert.ToInt32(Projects.SelectedItem.Value))
                Dim cat As Category
                For Each cat In projcatArray
                    ' categories need a unique identifier when added, since they are not yet saved to
                    ' the database but in session, a counter is used to populate their id.
                    If Not (Session("catID") Is Nothing) Then
                        catID = Convert.ToInt32(Session("catID")) + 1
                    Else
                        catID = 1
                    End If
                    Session("catID") = catID
                    cat.CategoryID = catID
                    catArray.Add(cat)
                Next cat

                ' Re-Validate to make sure duplicate categories are not copied.
                ProjectsGridCustomValidator.Validate()

                Session("catArray") = catArray

                BindCategoriesGrid(catArray)
            End Sub 'CopyButton_Click

            '*********************************************************************
            '
            ' The CategoriesGrid_OnDelete event handler deletes a category from the categories grid.
            '
            '*********************************************************************

            Protected Sub CategoriesGrid_OnDelete(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles CategoriesGrid.DeleteCommand
                Dim catID As Integer = Convert.ToInt32(CategoriesGrid.DataKeys(CInt(e.Item.ItemIndex)))
                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)

                Dim i As Integer
                For i = 0 To (catArray.Count - 1)
                    If CType(catArray(i), Category).CategoryID = catID Then
                        catArray.RemoveAt(i)
                        Exit For
                    End If
                Next i

                BindCategoriesGrid(catArray)
            End Sub 'CategoriesGrid_OnDelete

            '*********************************************************************
            '
            ' The CategoriesGrid_OnCancel event handler cancels an edit on the categories grid.
            '
            '*********************************************************************

            Protected Sub CategoriesGrid_OnCancel(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles CategoriesGrid.CancelCommand
                CategoriesGrid.EditItemIndex = -1

                BindCategoriesGrid(CType(Session("catArray"), CategoriesCollection))
            End Sub 'CategoriesGrid_OnCancel

            '*********************************************************************
            '
            ' The CategoriesGrid_OnUpdate event handler updates changes on an edit of the categories grid.
            '
            '*********************************************************************

            Protected Sub CategoriesGrid_OnUpdate(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles CategoriesGrid.UpdateCommand
                ' set up the editted category
                Dim editCat As New Category()
                editCat.CategoryID = Convert.ToInt32(CategoriesGrid.DataKeys(CInt(e.Item.ItemIndex)))
                editCat.Name = TTSecurity.CleanStringRegex(CType(e.Item.FindControl("EditName"), TextBox).Text)
                editCat.Abbreviation = TTSecurity.CleanStringRegex(CType(e.Item.FindControl("EditAbbreviation"), TextBox).Text)
                editCat.EstDuration = Convert.ToDecimal(CType(e.Item.FindControl("EditDuration"), TextBox).Text)

                ' check to make sure that category abbreviations are not being duplicated.
                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)
                If Not (catArray Is Nothing) Then
                    Dim editAbbrev As String = CType(e.Item.FindControl("EditAbbreviation"), TextBox).Text
                    Dim thisCat As Category
                    For Each thisCat In catArray
                        If thisCat.Abbreviation = editCat.Abbreviation And thisCat.CategoryID <> editCat.CategoryID Then
                            CategoryErrorMessage.Text = "Duplicate categories abbreviations are not allowed"
                            Return
                        End If
                    Next thisCat
                End If

                ' container for new list of categories
                Dim catNew As New CategoriesCollection()

                ' Add the editted category to our a new arraylist
                catNew.Add(editCat)

                If catArray Is Nothing Then
                    catArray = New CategoriesCollection()
                End If
                Dim cat As Category
                For Each cat In catArray
                    ' category id does not exist when users are creating new projects so we cannot use it as comparer here
                    ' since categories abbreviation must be unique per project we can use it as a comparer
                    If cat.CategoryID <> editCat.CategoryID Then
                        catNew.Add(cat)
                    End If
                Next cat

                CategoriesGrid.EditItemIndex = -1
                BindCategoriesGrid(catNew)
            End Sub 'CategoriesGrid_OnUpdate

            '*********************************************************************
            '
            ' The CategoriesGrid_OnEdit event handler sets up editing for the categories grid
            '
            '*********************************************************************

            Protected Sub CategoriesGrid_OnEdit(ByVal sender As [Object], ByVal e As DataGridCommandEventArgs) Handles CategoriesGrid.EditCommand
                CategoriesGrid.EditItemIndex = e.Item.ItemIndex

                BindCategoriesGrid(CType(Session("catArray"), CategoriesCollection))
            End Sub 'CategoriesGrid_OnEdit

            '*********************************************************************
            '
            ' CategoriesGrid_Sort handles sorting of the categories grid.
            '
            '*********************************************************************

            Private Sub CategoriesGrid_Sort(ByVal [source] As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles CategoriesGrid.SortCommand
                SortField = e.SortExpression

                BindCategoriesGrid(CType(Session("catArray"), CategoriesCollection))
            End Sub 'CategoriesGrid_Sort

            '*********************************************************************
            '
            ' Custom Validator event handler to make sure that there are categories in the categories grid.
            '
            '*********************************************************************

            Private Sub ValidateCategories(ByVal [source] As Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs) Handles ProjectsGridCustomValidator.ServerValidate
                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)
                If Not (catArray Is Nothing) Then
                    If catArray.Count > 0 Then
                        args.IsValid = True
                        Return
                    End If
                End If
                args.IsValid = False
            End Sub 'ValidateCategories

            '*********************************************************************
            '
            ' Custom Validator event handler to make sure that category abbreviations are not duplicated.
            '
            '*********************************************************************

            Private Sub AbbrevCustomValidator_ServerValidate(ByVal [source] As Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs) Handles AbbrevCustomValidator.ServerValidate
                Dim catArray As CategoriesCollection = CType(Session("catArray"), CategoriesCollection)
                If catArray.Count <> 0 Then
                    catArray.Sort(CategoriesCollection.CategoryFields.Abbreviation, True)
                    Dim i As Integer
                    For i = 0 To catArray.Count - 1
                        If CType(catArray(i), Category).Abbreviation = Abbrev.Text Then
                            args.IsValid = False
                            Return
                        End If
                        If i <> 0 Then
                            If CType(catArray(i), Category).Abbreviation = CType(catArray((i - 1)), Category).Abbreviation Then
                                args.IsValid = False
                                Return
                            End If
                        End If
                    Next i
                End If
                args.IsValid = True
            End Sub 'AbbrevCustomValidator_ServerValidate

            Private Sub CategoriesGrid_ItemCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles CategoriesGrid.ItemCreated
                ' add confirmation on deletion of categories
                If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then
                    CType(e.Item.FindControl("CatDeleteButton"), ImageButton).Attributes.Add("onclick", "return confirm('Deleting an existing category will also delete all the time entries and categories associated with the category. Are you sure you want to delete this category?')")
                End If
            End Sub 'CategoriesGrid_ItemCreated

            Property SortField() As String
                Get
                    Dim o As Object = ViewState("SortField")
                    If o Is Nothing Then
                        Return [String].Empty
                    End If
                    Return CStr(o)
                End Get

                Set(ByVal Value As String)
                    If Value = SortField Then
                        ' same as current sort file, toggle sort direction
                        SortAscending = Not SortAscending
                    End If
                    ViewState("SortField") = Value
                End Set
            End Property

            '*********************************************************************
            '
            ' SortAscending property is tracked in ViewState
            '
            '*********************************************************************

            Property SortAscending() As Boolean
                Get
                    Dim o As Object = ViewState("SortAscending")
                    If o Is Nothing Then
                        Return True
                    End If
                    Return CBool(o)
                End Get

                Set(ByVal Value As Boolean)
                    ViewState("SortAscending") = Value
                End Set
            End Property

            Private Sub InitializeComponent()

            End Sub
        End Class 'ProjectDetails
    End Namespace 'ASPNET.StarterKit.TimeTracker.Web