/**
   * Used by dismissed and missed states, to update parent alarm. This will either disable, delete
   * or reschedule parent alarm.
   *
   * @param context application context
   * @param instance to update parent for
   */
  private static void updateParentAlarm(Context context, AlarmInstance instance) {
    ContentResolver cr = context.getContentResolver();
    Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    if (alarm == null) {
      LogUtils.e("Parent has been deleted with instance: " + instance.toString());
      return;
    }

    if (!alarm.daysOfWeek.isRepeating()) {
      if (alarm.deleteAfterUse) {
        LogUtils.i("Deleting parent alarm: " + alarm.id);
        Alarm.deleteAlarm(cr, alarm.id);
      } else {
        LogUtils.i("Disabling parent alarm: " + alarm.id);
        alarm.enabled = false;
        Alarm.updateAlarm(cr, alarm);
      }
    } else {
      // Schedule the next repeating instance after the current time
      AlarmInstance nextRepeatedInstance = alarm.createInstanceAfter(getCurrentTime());
      LogUtils.i(
          "Creating new instance for repeating alarm "
              + alarm.id
              + " at "
              + AlarmUtils.getFormattedTime(context, nextRepeatedInstance.getAlarmTime()));
      AlarmInstance.addInstance(cr, nextRepeatedInstance);
      registerInstance(context, nextRepeatedInstance, true);
    }
  }
  /**
   * This registers the AlarmInstance to the state manager. This will look at the instance and
   * choose the most appropriate state to put it in. This is primarily used by new alarms, but it
   * can also be called when the system time changes.
   *
   * <p>Most state changes are handled by the states themselves, but during major time changes we
   * have to correct the alarm instance state. This means we have to handle special cases as
   * describe below:
   *
   * <ul>
   *   <li>Make sure all dismissed alarms are never re-activated
   *   <li>Make sure pre-dismissed alarms stay predismissed
   *   <li>Make sure firing alarms stayed fired unless they should be auto-silenced
   *   <li>Missed instance that have parents should be re-enabled if we went back in time
   *   <li>If alarm was SNOOZED, then show the notification but don't update time
   *   <li>If low priority notification was hidden, then make sure it stays hidden
   * </ul>
   *
   * If none of these special case are found, then we just check the time and see what is the proper
   * state for the instance.
   *
   * @param context application context
   * @param instance to register
   */
  public static void registerInstance(
      Context context, AlarmInstance instance, boolean updateNextAlarm) {
    final ContentResolver cr = context.getContentResolver();
    final Alarm alarm = Alarm.getAlarm(cr, instance.mAlarmId);
    final Calendar currentTime = getCurrentTime();
    final Calendar alarmTime = instance.getAlarmTime();
    final Calendar timeoutTime = instance.getTimeout(context);
    final Calendar lowNotificationTime = instance.getLowNotificationTime();
    final Calendar highNotificationTime = instance.getHighNotificationTime();
    final Calendar missedTTL = instance.getMissedTimeToLive();

    // Handle special use cases here
    if (instance.mAlarmState == AlarmInstance.DISMISSED_STATE) {
      // This should never happen, but add a quick check here
      LogUtils.e("Alarm Instance is dismissed, but never deleted");
      setDismissState(context, instance);
      return;
    } else if (instance.mAlarmState == AlarmInstance.FIRED_STATE) {
      // Keep alarm firing, unless it should be timed out
      boolean hasTimeout = timeoutTime != null && currentTime.after(timeoutTime);
      if (!hasTimeout) {
        setFiredState(context, instance);
        return;
      }
    } else if (instance.mAlarmState == AlarmInstance.MISSED_STATE) {
      if (currentTime.before(alarmTime)) {
        if (instance.mAlarmId == null) {
          // This instance parent got deleted (ie. deleteAfterUse), so
          // we should not re-activate it.-
          setDismissState(context, instance);
          return;
        }

        // TODO: This will re-activate missed snoozed alarms, but will
        // use our normal notifications. This is not ideal, but very rare use-case.
        // We should look into fixing this in the future.

        // Make sure we re-enable the parent alarm of the instance
        // because it will get activated by by the below code
        alarm.enabled = true;
        Alarm.updateAlarm(cr, alarm);
      }
    } else if (instance.mAlarmState == AlarmInstance.PREDISMISSED_STATE) {
      if (currentTime.before(alarmTime)) {
        setPreDismissState(context, instance);
      } else {
        setDismissState(context, instance);
      }
      return;
    }

    // Fix states that are time sensitive
    if (currentTime.after(missedTTL)) {
      // Alarm is so old, just dismiss it
      setDismissState(context, instance);
    } else if (currentTime.after(alarmTime)) {
      // There is a chance that the TIME_SET occurred right when the alarm should go off, so
      // we need to add a check to see if we should fire the alarm instead of marking it
      // missed.
      Calendar alarmBuffer = Calendar.getInstance();
      alarmBuffer.setTime(alarmTime.getTime());
      alarmBuffer.add(Calendar.SECOND, ALARM_FIRE_BUFFER);
      if (currentTime.before(alarmBuffer)) {
        setFiredState(context, instance);
      } else {
        setMissedState(context, instance);
      }
    } else if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
      // We only want to display snooze notification and not update the time,
      // so handle showing the notification directly
      AlarmNotifications.showSnoozeNotification(context, instance);
      scheduleInstanceStateChange(
          context, instance.getAlarmTime(), instance, AlarmInstance.FIRED_STATE);
    } else if (currentTime.after(highNotificationTime)) {
      setHighNotificationState(context, instance);
    } else if (currentTime.after(lowNotificationTime)) {
      // Only show low notification if it wasn't hidden in the past
      if (instance.mAlarmState == AlarmInstance.HIDE_NOTIFICATION_STATE) {
        setHideNotificationState(context, instance);
      } else {
        setLowNotificationState(context, instance);
      }
    } else {
      // Alarm is still active, so initialize as a silent alarm
      setSilentState(context, instance);
    }

    // The caller prefers to handle updateNextAlarm for optimization
    if (updateNextAlarm) {
      updateNextAlarm(context);
    }
  }