/// <summary>
        /// Job that will send scheduled group SMS messages.
        ///
        /// Called by the <see cref="IScheduler" /> when a
        /// <see cref="ITrigger" /> fires that is associated with
        /// the <see cref="IJob" />.
        /// </summary>
        public virtual void Execute(IJobExecutionContext context)
        {
            var dataMap                         = context.JobDetail.JobDataMap;
            int?commandTimeout                  = dataMap.GetString("CommandTimeout").AsIntegerOrNull();
            int?lastRunBuffer                   = dataMap.GetString("LastRunBuffer").AsIntegerOrNull();
            var enabledLavaCommands             = dataMap.GetString("EnabledLavaCommands");
            var JobStartDateTime                = RockDateTime.Now;
            var dateAttributes                  = new List <AttributeValue>();
            var dAttributeMatrixItemAndGroupIds = new Dictionary <int, int>(); // Key: AttributeMatrixItemId   Value: GroupId
            int communicationsSent              = 0;
            var smsMediumType                   = EntityTypeCache.Get("Rock.Communication.Medium.Sms");
            var dateAttributeId                 = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_SEND_DATE.AsGuid()).Id;
            var recurrenceAttributeId           = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_SEND_RECURRENCE.AsGuid()).Id;
            var fromNumberAttributeId           = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_FROM_NUMBER.AsGuid()).Id;
            var messageAttributeId              = Rock.Web.Cache.AttributeCache.Get(KFSConst.Attribute.MATRIX_ATTRIBUTE_SMS_MESSAGE.AsGuid()).Id;

            try
            {
                using (var rockContext = new RockContext())
                {
                    // get the last run date or yesterday
                    DateTime?lastStartDateTime = null;

                    // get job type id
                    int jobId = context.JobDetail.Description.AsInteger();

                    // load job
                    var job = new ServiceJobService(rockContext)
                              .GetNoTracking(jobId);

                    if (job != null && job.Guid != Rock.SystemGuid.ServiceJob.JOB_PULSE.AsGuid())
                    {
                        lastStartDateTime = job.LastRunDateTime?.AddSeconds(0.0d - ( double )(job.LastRunDurationSeconds + lastRunBuffer));
                    }
                    var beginDateTime = lastStartDateTime ?? JobStartDateTime.AddDays(-1);

                    // get the date attributes
                    dateAttributes = new AttributeValueService(rockContext)
                                     .Queryable().AsNoTracking()
                                     .Where(d => d.AttributeId == dateAttributeId &&
                                            d.EntityId.HasValue &&
                                            d.ValueAsDateTime >= beginDateTime &&
                                            d.ValueAsDateTime <= JobStartDateTime)
                                     .ToList();
                }

                foreach (var d in dateAttributes)
                {
                    // Use a new context to limit the amount of change-tracking required
                    using (var rockContext = new RockContext())
                    {
                        var attributeMatrixId = new AttributeMatrixItemService(rockContext)
                                                .GetNoTracking(d.EntityId.Value)
                                                .AttributeMatrixId;

                        var attributeMatrixGuid = new AttributeMatrixService(rockContext)
                                                  .GetNoTracking(attributeMatrixId)
                                                  .Guid
                                                  .ToString();

                        var attributeValue = new AttributeValueService(rockContext)
                                             .Queryable().AsNoTracking()
                                             .FirstOrDefault(a => a.Value.Equals(attributeMatrixGuid, StringComparison.CurrentCultureIgnoreCase));

                        if (attributeValue != null && attributeValue.EntityId.HasValue)
                        {
                            dAttributeMatrixItemAndGroupIds.Add(d.EntityId.Value, attributeValue.EntityId.Value);
                        }
                    }
                }

                foreach (var attributeMatrixItemAndGroupId in dAttributeMatrixItemAndGroupIds)
                {
                    // Use a new context to limit the amount of change-tracking required
                    using (var rockContext = new RockContext())
                    {
                        rockContext.Database.CommandTimeout = commandTimeout;

                        var fromNumberGuid = new AttributeValueService(rockContext)
                                             .GetByAttributeIdAndEntityId(fromNumberAttributeId, attributeMatrixItemAndGroupId.Key)
                                             .Value;
                        var fromNumber = DefinedValueCache.Get(fromNumberGuid.AsGuid());

                        var message = new AttributeValueService(rockContext)
                                      .GetByAttributeIdAndEntityId(messageAttributeId, attributeMatrixItemAndGroupId.Key)
                                      .Value;

                        var attachments = new List <BinaryFile>();

                        var group = new GroupService(rockContext)
                                    .GetNoTracking(attributeMatrixItemAndGroupId.Value);

                        if (!message.IsNullOrWhiteSpace() && smsMediumType != null)
                        {
                            var recipients = new GroupMemberService(rockContext)
                                             .GetByGroupId(attributeMatrixItemAndGroupId.Value).AsNoTracking()
                                             .Where(m => m.GroupMemberStatus == GroupMemberStatus.Active)
                                             .ToList();

                            if (recipients.Any())
                            {
                                var communicationService = new CommunicationService(rockContext);

                                var communication = new Rock.Model.Communication();
                                communication.Status                 = CommunicationStatus.Transient;
                                communication.ReviewedDateTime       = JobStartDateTime;
                                communication.ReviewerPersonAliasId  = group.ModifiedByPersonAliasId;
                                communication.SenderPersonAliasId    = group.ModifiedByPersonAliasId;
                                communication.CreatedByPersonAliasId = group.ModifiedByPersonAliasId;
                                communicationService.Add(communication);

                                communication.EnabledLavaCommands = enabledLavaCommands;
                                var personIdHash = new HashSet <int>();
                                foreach (var groupMember in recipients)
                                {
                                    // Use a new context to limit the amount of change-tracking required
                                    using (var groupMemberContext = new RockContext())
                                    {
                                        if (!personIdHash.Contains(groupMember.PersonId))
                                        {
                                            var person = new PersonService(groupMemberContext)
                                                         .GetNoTracking(groupMember.PersonId);

                                            if (person != null && person.PrimaryAliasId.HasValue)
                                            {
                                                personIdHash.Add(groupMember.PersonId);
                                                var communicationRecipient = new CommunicationRecipient();
                                                communicationRecipient.PersonAliasId         = person.PrimaryAliasId;
                                                communicationRecipient.AdditionalMergeValues = new Dictionary <string, object>();
                                                communicationRecipient.AdditionalMergeValues.Add("GroupMember", groupMember);
                                                //communicationRecipient.AdditionalMergeValues.Add( "Group", group );
                                                communication.Recipients.Add(communicationRecipient);
                                            }
                                        }
                                    }
                                }

                                communication.IsBulkCommunication     = false;
                                communication.CommunicationType       = CommunicationType.SMS;
                                communication.CommunicationTemplateId = null;

                                foreach (var recipient in communication.Recipients)
                                {
                                    recipient.MediumEntityTypeId = smsMediumType.Id;
                                }

                                communication.SMSMessage            = message;
                                communication.SMSFromDefinedValueId = fromNumber.Id;
                                communication.Subject = string.Empty;
                                communication.Status  = CommunicationStatus.Approved;

                                rockContext.SaveChanges();

                                Rock.Model.Communication.Send(communication);

                                communicationsSent = communicationsSent + personIdHash.Count;

                                var recurrence = new AttributeValueService(rockContext)
                                                 .GetByAttributeIdAndEntityId(recurrenceAttributeId, attributeMatrixItemAndGroupId.Key);

                                if (recurrence != null && !string.IsNullOrWhiteSpace(recurrence.Value))
                                {
                                    var sendDate = new AttributeValueService(rockContext)
                                                   .GetByAttributeIdAndEntityId(dateAttributeId, attributeMatrixItemAndGroupId.Key);

                                    switch (recurrence.Value)
                                    {
                                    case "1":
                                        sendDate.Value = sendDate.ValueAsDateTime.Value.AddDays(7).ToString();
                                        break;

                                    case "2":
                                        sendDate.Value = sendDate.ValueAsDateTime.Value.AddDays(14).ToString();
                                        break;

                                    case "3":
                                        sendDate.Value = sendDate.ValueAsDateTime.Value.AddMonths(1).ToString();
                                        break;

                                    case "4":
                                        sendDate.Value = sendDate.ValueAsDateTime.Value.AddDays(1).ToString();
                                        break;

                                    default:
                                        break;
                                    }
                                    rockContext.SaveChanges();
                                }
                            }
                        }
                    }
                }

                if (communicationsSent > 0)
                {
                    context.Result = string.Format("Sent {0} {1}", communicationsSent, "communication".PluralizeIf(communicationsSent > 1));
                }
                else
                {
                    context.Result = "No communications to send";
                }
            }
            catch (System.Exception ex)
            {
                HttpContext context2 = HttpContext.Current;
                ExceptionLogService.LogException(ex, context2);
                throw;
            }
        }