/**
   * @see
   *     gatewayapps.crondroid.impl.triggers.AbstractTrigger#updateWithNewCalendar(gatewayapps.crondroid.Calendar,
   *     long)
   */
  @Override
  public void updateWithNewCalendar(Calendar calendar, long misfireThreshold) {
    nextFireTime = getFireTimeAfter(previousFireTime);

    if (nextFireTime == null || calendar == null) {
      return;
    }

    Date now = new Date();
    while (nextFireTime != null && !calendar.isTimeIncluded(nextFireTime.getTime())) {

      nextFireTime = getFireTimeAfter(nextFireTime);

      if (nextFireTime == null) break;

      // avoid infinite loop
      java.util.Calendar c = java.util.Calendar.getInstance();
      c.setTime(nextFireTime);
      if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
        nextFireTime = null;
      }

      if (nextFireTime != null && nextFireTime.before(now)) {
        long diff = now.getTime() - nextFireTime.getTime();
        if (diff >= misfireThreshold) {
          nextFireTime = getFireTimeAfter(nextFireTime);
        }
      }
    }
  }
  /**
   * Called by the scheduler at the time a <code>Trigger</code> is first added to the scheduler, in
   * order to have the <code>Trigger</code> compute its first fire time, based on any associated
   * calendar.
   *
   * <p>After this method has been called, <code>getNextFireTime()</code> should return a valid
   * answer.
   *
   * @return the first time at which the <code>Trigger</code> will be fired by the scheduler, which
   *     is also the same value <code>getNextFireTime()</code> will return (until after the first
   *     firing of the <code>Trigger</code>).
   */
  @Override
  public Date computeFirstFireTime(Calendar calendar) {
    nextFireTime = getStartTime();

    while (nextFireTime != null
        && calendar != null
        && !calendar.isTimeIncluded(nextFireTime.getTime())) {
      nextFireTime = getFireTimeAfter(nextFireTime);

      if (nextFireTime == null) break;

      // avoid infinite loop
      java.util.Calendar c = java.util.Calendar.getInstance();
      c.setTime(nextFireTime);
      if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
        return null;
      }
    }

    return nextFireTime;
  }
  /**
   * Called when the <code>{@link Scheduler}</code> has decided to 'fire' the trigger (execute the
   * associated <code>Job</code>), in order to give the <code>Trigger</code> a chance to update
   * itself for its next triggering (if any).
   *
   * @see #executionComplete(JobExecutionContext, JobExecutionException)
   */
  @Override
  public void triggered(Calendar calendar) {
    timesTriggered++;
    previousFireTime = nextFireTime;
    nextFireTime = getFireTimeAfter(nextFireTime);

    while (nextFireTime != null
        && calendar != null
        && !calendar.isTimeIncluded(nextFireTime.getTime())) {

      nextFireTime = getFireTimeAfter(nextFireTime);

      if (nextFireTime == null) break;

      // avoid infinite loop
      java.util.Calendar c = java.util.Calendar.getInstance();
      c.setTime(nextFireTime);
      if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
        nextFireTime = null;
      }
    }
  }
  /**
   * Updates the <code>SimpleTrigger</code>'s state based on the MISFIRE_INSTRUCTION_XXX that was
   * selected when the <code>SimpleTrigger</code> was created.
   *
   * <p>If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, then the following
   * scheme will be used: <br>
   *
   * <ul>
   *   <li>If the Repeat Count is <code>0</code>, then the instruction will be interpreted as <code>
   *       MISFIRE_INSTRUCTION_FIRE_NOW</code>.
   *   <li>If the Repeat Count is <code>REPEAT_INDEFINITELY</code>, then the instruction will be
   *       interpreted as <code>MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT</code>.
   *       <b>WARNING:</b> using MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT with a
   *       trigger that has a non-null end-time may cause the trigger to never fire again if the
   *       end-time arrived during the misfire time span.
   *   <li>If the Repeat Count is <code>&gt; 0</code>, then the instruction will be interpreted as
   *       <code>MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT</code>.
   * </ul>
   */
  @Override
  public void updateAfterMisfire(Calendar cal) {
    int instr = getMisfireInstruction();

    if (instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return;

    if (instr == Trigger.MISFIRE_INSTRUCTION_SMART_POLICY) {
      if (getRepeatCount() == 0) {
        instr = MISFIRE_INSTRUCTION_FIRE_NOW;
      } else if (getRepeatCount() == REPEAT_INDEFINITELY) {
        instr = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
      } else {
        // if (getRepeatCount() > 0)
        instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
      }
    } else if (instr == MISFIRE_INSTRUCTION_FIRE_NOW && getRepeatCount() != 0) {
      instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT;
    }

    if (instr == MISFIRE_INSTRUCTION_FIRE_NOW) {
      setNextFireTime(new Date());
    } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT) {
      Date newFireTime = getFireTimeAfter(new Date());
      while (newFireTime != null && cal != null && !cal.isTimeIncluded(newFireTime.getTime())) {
        newFireTime = getFireTimeAfter(newFireTime);

        if (newFireTime == null) break;

        // avoid infinite loop
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.setTime(newFireTime);
        if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
          newFireTime = null;
        }
      }
      setNextFireTime(newFireTime);
    } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT) {
      Date newFireTime = getFireTimeAfter(new Date());
      while (newFireTime != null && cal != null && !cal.isTimeIncluded(newFireTime.getTime())) {
        newFireTime = getFireTimeAfter(newFireTime);

        if (newFireTime == null) break;

        // avoid infinite loop
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.setTime(newFireTime);
        if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
          newFireTime = null;
        }
      }
      if (newFireTime != null) {
        int timesMissed = computeNumTimesFiredBetween(nextFireTime, newFireTime);
        setTimesTriggered(getTimesTriggered() + timesMissed);
      }

      setNextFireTime(newFireTime);
    } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT) {
      Date newFireTime = new Date();
      if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {
        setRepeatCount(getRepeatCount() - getTimesTriggered());
        setTimesTriggered(0);
      }

      if (getEndTime() != null && getEndTime().before(newFireTime)) {
        setNextFireTime(null); // We are past the end time
      } else {
        setStartTime(newFireTime);
        setNextFireTime(newFireTime);
      }
    } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT) {
      Date newFireTime = new Date();

      int timesMissed = computeNumTimesFiredBetween(nextFireTime, newFireTime);

      if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {
        int remainingCount = getRepeatCount() - (getTimesTriggered() + timesMissed);
        if (remainingCount <= 0) {
          remainingCount = 0;
        }
        setRepeatCount(remainingCount);
        setTimesTriggered(0);
      }

      if (getEndTime() != null && getEndTime().before(newFireTime)) {
        setNextFireTime(null); // We are past the end time
      } else {
        setStartTime(newFireTime);
        setNextFireTime(newFireTime);
      }
    }
  }