/// <summary>
        /// Handles the AddCheckinLabelClick event of the groupTypeEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void groupTypeEditor_AddCheckinLabelClick(object sender, EventArgs e)
        {
            CheckinGroupTypeEditor checkinGroupTypeEditor = sender as CheckinGroupTypeEditor;

            // set a hidden field value for the GroupType Guid so we know which GroupType to add the label to
            hfAddCheckinLabelGroupTypeGuid.Value = checkinGroupTypeEditor.GroupTypeGuid.ToString();

            Guid binaryFileTypeCheckinLabelGuid = new Guid(Rock.SystemGuid.BinaryFiletype.CHECKIN_LABEL);

            var binaryFileService = new BinaryFileService(new RockContext());

            ddlCheckinLabel.Items.Clear();
            var list = binaryFileService.Queryable().Where(a => a.BinaryFileType.Guid.Equals(binaryFileTypeCheckinLabelGuid) && a.IsTemporary == false).OrderBy(a => a.FileName).ToList();

            foreach (var item in list)
            {
                // add checkinlabels to dropdownlist if they aren't already a checkin label for this grouptype
                if (!checkinGroupTypeEditor.CheckinLabels.Select(a => a.BinaryFileId).Contains(item.Id))
                {
                    ddlCheckinLabel.Items.Add(new ListItem(item.FileName, item.Id.ToString()));
                }
            }

            // only enable the Add button if there are labels that can be added
            btnAddCheckinLabel.Enabled = ddlCheckinLabel.Items.Count > 0;

            pnlCheckinLabelPicker.Visible = true;
            pnlDetails.Visible            = false;
        }
        /// <summary>
        /// Creates the group type editor controls.
        /// </summary>
        /// <param name="groupType">Type of the group.</param>
        private void CreateGroupTypeEditorControls(GroupType groupType, Control parentControl, bool forceGroupTypeEditorVisible = false)
        {
            CheckinGroupTypeEditor groupTypeEditor = new CheckinGroupTypeEditor();

            groupTypeEditor.ID = "GroupTypeEditor_" + groupType.Guid.ToString("N");
            groupTypeEditor.SetGroupType(groupType);
            groupTypeEditor.AddGroupClick           += groupTypeEditor_AddGroupClick;
            groupTypeEditor.AddGroupTypeClick       += groupTypeEditor_AddGroupTypeClick;
            groupTypeEditor.DeleteCheckinLabelClick += groupTypeEditor_DeleteCheckinLabelClick;
            groupTypeEditor.AddCheckinLabelClick    += groupTypeEditor_AddCheckinLabelClick;
            groupTypeEditor.DeleteGroupTypeClick    += groupTypeEditor_DeleteGroupTypeClick;
            groupTypeEditor.CheckinLabels            = null;
            groupTypeEditor.ForceContentVisible      = forceGroupTypeEditorVisible;

            if (GroupTypeCheckinLabelAttributesState.ContainsKey(groupType.Guid))
            {
                groupTypeEditor.CheckinLabels = GroupTypeCheckinLabelAttributesState[groupType.Guid];
            }

            if (groupTypeEditor.CheckinLabels == null)
            {
                // load CheckInLabels from Database if they haven't been set yet
                groupTypeEditor.CheckinLabels = new List <CheckinGroupTypeEditor.CheckinLabelAttributeInfo>();

                groupType.LoadAttributes();
                List <string>     labelAttributeKeys = CheckinGroupTypeEditor.GetCheckinLabelAttributes(groupType).Select(a => a.Key).ToList();
                BinaryFileService binaryFileService  = new BinaryFileService(new RockContext());

                foreach (string key in labelAttributeKeys)
                {
                    var attributeValue = groupType.GetAttributeValue(key);
                    int binaryFileId   = attributeValue.AsInteger() ?? 0;
                    var binaryFile     = binaryFileService.Get(binaryFileId);
                    if (binaryFile != null)
                    {
                        groupTypeEditor.CheckinLabels.Add(new CheckinGroupTypeEditor.CheckinLabelAttributeInfo {
                            AttributeKey = key, BinaryFileId = binaryFileId, FileName = binaryFile.FileName
                        });
                    }
                }
            }

            parentControl.Controls.Add(groupTypeEditor);

            foreach (var childGroup in groupType.Groups.OrderBy(a => a.Order).ThenBy(a => a.Name))
            {
                // get the GroupType from the control just in case it the InheritedFrom changed
                childGroup.GroupType = groupTypeEditor.GetCheckinGroupType();

                CreateGroupEditorControls(childGroup, groupTypeEditor, false);
            }

            foreach (var childGroupType in groupType.ChildGroupTypes.OrderBy(a => a.Order).ThenBy(a => a.Name))
            {
                CreateGroupTypeEditorControls(childGroupType, groupTypeEditor);
            }
        }
        /// <summary>
        /// Handles the DeleteCheckinLabelClick event of the groupTypeEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param>
        protected void groupTypeEditor_DeleteCheckinLabelClick(object sender, RowEventArgs e)
        {
            CheckinGroupTypeEditor checkinGroupTypeEditor = sender as CheckinGroupTypeEditor;
            string attributeKey = e.RowKeyValue as string;

            var label = checkinGroupTypeEditor.CheckinLabels.FirstOrDefault(a => a.AttributeKey == attributeKey);

            checkinGroupTypeEditor.CheckinLabels.Remove(label);
            checkinGroupTypeEditor.ForceContentVisible = true;
        }
        /// <summary>
        /// Handles the DeleteGroupTypeClick event of the groupTypeEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void groupTypeEditor_DeleteGroupTypeClick(object sender, EventArgs e)
        {
            CheckinGroupTypeEditor groupTypeEditor = sender as CheckinGroupTypeEditor;
            var groupType = GroupTypeCache.Read(groupTypeEditor.GroupTypeGuid);

            if (groupType != null)
            {
                // Warn if this GroupType or any of its child grouptypes (recursive) is being used as an Inherited Group Type. Probably shouldn't happen, but just in case
                if (IsInheritedGroupTypeRecursive(groupType))
                {
                    nbDeleteWarning.Text    = "WARNING - Cannot delete. This group type or one of its child group types is assigned as an inherited group type.";
                    nbDeleteWarning.Visible = true;
                    return;
                }
            }

            groupTypeEditor.Parent.Controls.Remove(groupTypeEditor);
        }
        /// <summary>
        /// Handles the AddGroupClick event of the groupTypeEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void groupTypeEditor_AddGroupClick(object sender, EventArgs e)
        {
            CheckinGroupTypeEditor parentGroupTypeEditor = sender as CheckinGroupTypeEditor;

            parentGroupTypeEditor.ForceContentVisible = true;

            Group checkinGroup = new Group();

            checkinGroup.Guid     = Guid.NewGuid();
            checkinGroup.IsActive = true;
            checkinGroup.IsSystem = false;

            // set GroupType by Guid (just in case the parent groupType hasn't been added to the database yet)
            checkinGroup.GroupType = new GroupType {
                Guid = parentGroupTypeEditor.GroupTypeGuid
            };

            CreateGroupEditorControls(checkinGroup, parentGroupTypeEditor);
        }
        /// <summary>
        /// Handles the AddGroupTypeClick event of the groupTypeEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void groupTypeEditor_AddGroupTypeClick(object sender, EventArgs e)
        {
            CheckinGroupTypeEditor parentEditor = sender as CheckinGroupTypeEditor;

            parentEditor.ForceContentVisible = true;

            // CheckinArea is GroupType entity
            GroupType checkinArea = new GroupType();

            checkinArea.Guid              = Guid.NewGuid();
            checkinArea.IsSystem          = false;
            checkinArea.TakesAttendance   = true;
            checkinArea.AttendanceRule    = AttendanceRule.AddOnCheckIn;
            checkinArea.AttendancePrintTo = PrintTo.Default;
            checkinArea.ParentGroupTypes  = new List <GroupType>();
            checkinArea.ParentGroupTypes.Add(parentEditor.GetCheckinGroupType());
            checkinArea.LoadAttributes();

            CreateGroupTypeEditorControls(checkinArea, parentEditor);
        }
        /// <summary>
        /// Handles the Click event of the btnSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void btnSave_Click(object sender, EventArgs e)
        {
            bool hasValidationErrors = false;

            var rockContext = new RockContext();

            GroupTypeService     groupTypeService     = new GroupTypeService(rockContext);
            GroupService         groupService         = new GroupService(rockContext);
            AttributeService     attributeService     = new AttributeService(rockContext);
            GroupLocationService groupLocationService = new GroupLocationService(rockContext);

            int parentGroupTypeId = hfParentGroupTypeId.ValueAsInt();

            var groupTypeUIList = new List <GroupType>();

            foreach (var checkinGroupTypeEditor in phCheckinGroupTypes.Controls.OfType <CheckinGroupTypeEditor>().ToList())
            {
                var groupType = checkinGroupTypeEditor.GetCheckinGroupType();
                groupTypeUIList.Add(groupType);
            }

            var groupTypeDBList = new List <GroupType>();

            var groupTypesToDelete = new List <GroupType>();
            var groupsToDelete     = new List <Group>();

            var groupTypesToAddUpdate = new List <GroupType>();
            var groupsToAddUpdate     = new List <Group>();

            GroupType parentGroupTypeDB = groupTypeService.Get(parentGroupTypeId);
            GroupType parentGroupTypeUI = parentGroupTypeDB.Clone(false);

            parentGroupTypeUI.ChildGroupTypes = groupTypeUIList;

            PopulateDeleteLists(groupTypesToDelete, groupsToDelete, parentGroupTypeDB, parentGroupTypeUI);
            PopulateAddUpdateLists(groupTypesToAddUpdate, groupsToAddUpdate, parentGroupTypeUI);

            int binaryFileFieldTypeID = FieldTypeCache.Read(Rock.SystemGuid.FieldType.BINARY_FILE.AsGuid()).Id;
            int binaryFileTypeId      = new BinaryFileTypeService(rockContext).Get(new Guid(Rock.SystemGuid.BinaryFiletype.CHECKIN_LABEL)).Id;

            RockTransactionScope.WrapTransaction(() =>
            {
                // delete in reverse order to get deepest child items first
                groupsToDelete.Reverse();
                foreach (var groupToDelete in groupsToDelete)
                {
                    groupService.Delete(groupToDelete);
                }

                // delete in reverse order to get deepest child items first
                groupTypesToDelete.Reverse();
                foreach (var groupTypeToDelete in groupTypesToDelete)
                {
                    groupTypeService.Delete(groupTypeToDelete);
                }

                rockContext.SaveChanges();

                // Add/Update grouptypes and groups that are in the UI
                // Note:  We'll have to save all the groupTypes without changing the DB value of ChildGroupTypes, then come around again and save the ChildGroupTypes
                // since the ChildGroupTypes may not exist in the database yet
                foreach (GroupType groupTypeUI in groupTypesToAddUpdate)
                {
                    GroupType groupTypeDB = groupTypeService.Get(groupTypeUI.Guid);
                    if (groupTypeDB == null)
                    {
                        groupTypeDB      = new GroupType();
                        groupTypeDB.Id   = 0;
                        groupTypeDB.Guid = groupTypeUI.Guid;
                    }

                    groupTypeDB.Name  = groupTypeUI.Name;
                    groupTypeDB.Order = groupTypeUI.Order;
                    groupTypeDB.InheritedGroupTypeId = groupTypeUI.InheritedGroupTypeId;

                    groupTypeDB.Attributes      = groupTypeUI.Attributes;
                    groupTypeDB.AttributeValues = groupTypeUI.AttributeValues;

                    if (groupTypeDB.Id == 0)
                    {
                        groupTypeService.Add(groupTypeDB);
                    }

                    if (!groupTypeDB.IsValid)
                    {
                        hasValidationErrors = true;
                        CheckinGroupTypeEditor groupTypeEditor = phCheckinGroupTypes.ControlsOfTypeRecursive <CheckinGroupTypeEditor>().First(a => a.GroupTypeGuid == groupTypeDB.Guid);
                        groupTypeEditor.ForceContentVisible    = true;

                        return;
                    }

                    rockContext.SaveChanges();

                    groupTypeDB.SaveAttributeValues();

                    // get fresh from database to make sure we have Id so we can update the CheckinLabel Attributes
                    groupTypeDB = groupTypeService.Get(groupTypeDB.Guid);

                    // rebuild the CheckinLabel attributes from the UI (brute-force)
                    foreach (var labelAttributeDB in CheckinGroupTypeEditor.GetCheckinLabelAttributes(groupTypeDB))
                    {
                        var attribute = attributeService.Get(labelAttributeDB.Value.Guid);
                        Rock.Web.Cache.AttributeCache.Flush(attribute.Id);
                        attributeService.Delete(attribute);
                    }
                    rockContext.SaveChanges();

                    foreach (var checkinLabelAttributeInfo in GroupTypeCheckinLabelAttributesState[groupTypeUI.Guid])
                    {
                        var attribute = new Rock.Model.Attribute();
                        attribute.AttributeQualifiers.Add(new AttributeQualifier {
                            Key = "binaryFileType", Value = binaryFileTypeId.ToString()
                        });
                        attribute.Guid         = Guid.NewGuid();
                        attribute.FieldTypeId  = binaryFileFieldTypeID;
                        attribute.EntityTypeId = EntityTypeCache.GetId(typeof(GroupType));
                        attribute.EntityTypeQualifierColumn = "Id";
                        attribute.EntityTypeQualifierValue  = groupTypeDB.Id.ToString();
                        attribute.DefaultValue = checkinLabelAttributeInfo.BinaryFileId.ToString();
                        attribute.Key          = checkinLabelAttributeInfo.AttributeKey;
                        attribute.Name         = checkinLabelAttributeInfo.FileName;

                        if (!attribute.IsValid)
                        {
                            hasValidationErrors = true;
                            CheckinGroupTypeEditor groupTypeEditor = phCheckinGroupTypes.ControlsOfTypeRecursive <CheckinGroupTypeEditor>().First(a => a.GroupTypeGuid == groupTypeDB.Guid);
                            groupTypeEditor.ForceContentVisible    = true;

                            return;
                        }

                        attributeService.Add(attribute);
                    }
                    rockContext.SaveChanges();
                }

                // Add/Update Groups
                foreach (var groupUI in groupsToAddUpdate)
                {
                    Group groupDB = groupService.Get(groupUI.Guid);
                    if (groupDB == null)
                    {
                        groupDB      = new Group();
                        groupDB.Guid = groupUI.Guid;
                    }

                    groupDB.Name = groupUI.Name;

                    // delete any GroupLocations that were removed in the UI
                    foreach (var groupLocationDB in groupDB.GroupLocations.ToList())
                    {
                        if (!groupUI.GroupLocations.Select(a => a.LocationId).Contains(groupLocationDB.LocationId))
                        {
                            groupLocationService.Delete(groupLocationDB);
                        }
                    }

                    // add any GroupLocations that were added in the UI
                    foreach (var groupLocationUI in groupUI.GroupLocations)
                    {
                        if (!groupDB.GroupLocations.Select(a => a.LocationId).Contains(groupLocationUI.LocationId))
                        {
                            GroupLocation groupLocationDB = new GroupLocation {
                                LocationId = groupLocationUI.LocationId
                            };
                            groupDB.GroupLocations.Add(groupLocationDB);
                        }
                    }

                    groupDB.Order = groupUI.Order;

                    // get GroupTypeId from database in case the groupType is new
                    groupDB.GroupTypeId     = groupTypeService.Get(groupUI.GroupType.Guid).Id;
                    groupDB.Attributes      = groupUI.Attributes;
                    groupDB.AttributeValues = groupUI.AttributeValues;

                    if (groupDB.Id == 0)
                    {
                        groupService.Add(groupDB);
                    }

                    if (!groupDB.IsValid)
                    {
                        hasValidationErrors             = true;
                        hasValidationErrors             = true;
                        CheckinGroupEditor groupEditor  = phCheckinGroupTypes.ControlsOfTypeRecursive <CheckinGroupEditor>().First(a => a.GroupGuid == groupDB.Guid);
                        groupEditor.ForceContentVisible = true;

                        return;
                    }

                    rockContext.SaveChanges();

                    groupDB.SaveAttributeValues();
                }

                /* now that we have all the grouptypes saved, now lets go back and save them again with the current UI ChildGroupTypes */

                // save main parentGroupType with current UI ChildGroupTypes
                parentGroupTypeDB.ChildGroupTypes = new List <GroupType>();
                parentGroupTypeDB.ChildGroupTypes.Clear();
                foreach (var childGroupTypeUI in parentGroupTypeUI.ChildGroupTypes)
                {
                    var childGroupTypeDB = groupTypeService.Get(childGroupTypeUI.Guid);
                    parentGroupTypeDB.ChildGroupTypes.Add(childGroupTypeDB);
                }

                rockContext.SaveChanges();

                // loop thru all the other GroupTypes in the UI and save their childgrouptypes
                foreach (var groupTypeUI in groupTypesToAddUpdate)
                {
                    var groupTypeDB             = groupTypeService.Get(groupTypeUI.Guid);
                    groupTypeDB.ChildGroupTypes = new List <GroupType>();
                    groupTypeDB.ChildGroupTypes.Clear();
                    foreach (var childGroupTypeUI in groupTypeUI.ChildGroupTypes)
                    {
                        var childGroupTypeDB = groupTypeService.Get(childGroupTypeUI.Guid);
                        groupTypeDB.ChildGroupTypes.Add(childGroupTypeDB);
                    }
                }

                rockContext.SaveChanges();
            });

            if (!hasValidationErrors)
            {
                NavigateToParentPage();
            }
        }
        /// <summary>
        /// Sorts the group type list contents.
        /// </summary>
        /// <param name="eventParam">The event parameter.</param>
        /// <param name="values">The values.</param>
        private void SortGroupTypeListContents(string eventParam, string[] values)
        {
            if (eventParam.Equals("re-order-grouptype"))
            {
                var  allCheckinGroupTypeEditors = phCheckinGroupTypes.ControlsOfTypeRecursive <CheckinGroupTypeEditor>();
                Guid groupTypeGuid = new Guid(values[0]);
                int  newIndex      = int.Parse(values[1]);

                CheckinGroupTypeEditor sortedGroupTypeEditor = allCheckinGroupTypeEditors.FirstOrDefault(a => a.GroupTypeGuid.Equals(groupTypeGuid));
                if (sortedGroupTypeEditor != null)
                {
                    var     siblingGroupTypes = allCheckinGroupTypeEditors.Where(a => a.ParentGroupTypeEditor == sortedGroupTypeEditor.ParentGroupTypeEditor).ToList();
                    Control parentControl     = sortedGroupTypeEditor.Parent;
                    parentControl.Controls.Remove(sortedGroupTypeEditor);
                    if (newIndex >= siblingGroupTypes.Count())
                    {
                        parentControl.Controls.Add(sortedGroupTypeEditor);
                    }
                    else
                    {
                        parentControl.Controls.AddAt(newIndex, sortedGroupTypeEditor);
                    }
                }
            }
            else if (eventParam.Equals("re-order-group"))
            {
                var allCheckinGroupEditors = phCheckinGroupTypes.ControlsOfTypeRecursive <CheckinGroupEditor>();

                Guid groupGuid = new Guid(values[0]);
                int  newIndex  = int.Parse(values[1]);

                CheckinGroupEditor sortedGroupEditor = allCheckinGroupEditors.FirstOrDefault(a => a.GroupGuid.Equals(groupGuid));
                if (sortedGroupEditor != null)
                {
                    var siblingGroupEditors = allCheckinGroupEditors.Where(a => a.GroupTypeId == sortedGroupEditor.GroupTypeId).ToList();

                    Control parentControl = sortedGroupEditor.Parent;

                    // parent control has other controls, so just just remove all the checkingroupeditors, sort them, and add them back in the new order
                    foreach (var item in siblingGroupEditors)
                    {
                        parentControl.Controls.Remove(item);
                    }

                    siblingGroupEditors.Remove(sortedGroupEditor);
                    if (newIndex >= siblingGroupEditors.Count())
                    {
                        siblingGroupEditors.Add(sortedGroupEditor);
                    }
                    else
                    {
                        siblingGroupEditors.Insert(newIndex, sortedGroupEditor);
                    }

                    foreach (var item in siblingGroupEditors)
                    {
                        parentControl.Controls.Add(item);
                    }

                    (sortedGroupEditor.Parent as CheckinGroupTypeEditor).ForceContentVisible = true;
                }
            }
        }