// We have to ensure that there are enough batteries to
 // support the shift schedule. There must be at least enough batteries to
 // supply the two largest adjacent shifts, and there must also be enough
 // to power the two largest adjacent shifts, in case a single battery
 // cannot power an entire shift.
 void validateBatteries() {
   int minBatteries = 0;
   Shift s1 = null;
   Shift s2 = null;
   for (int i = 0; i < shiftSchedule.length; i++) {
     Shift s = shiftSchedule[i];
     if (null == s) {
       s1 = s2;
     } else if (s2 != s) {
       s1 = s2;
       s2 = s;
       if (null != s1) {
         int n1 = s1.getTrucks();
         int d1 = s1.getDuration();
         int n2 = s2.getTrucks();
         int d2 = s2.getDuration();
         double neededBatteries = (n1 * d1 + n2 * d2) * truckKW / getBatteryCapacity();
         minBatteries = (int) Math.max(minBatteries, (n1 + n2));
         minBatteries = (int) Math.max(minBatteries, Math.ceil(neededBatteries));
       }
     }
   }
   int neededBatteries = minBatteries - nBatteries;
   if (neededBatteries > 0) {
     log.error("Not enough batteries (" + nBatteries + ") for " + getName());
     // Add discharged batteries to fill out battery complement
     log.warn("Adding " + neededBatteries + " batteries for " + getName());
     setNBatteries(getNBatteries() + neededBatteries);
   }
 }
  // ======== per-timeslot activities ========
  @Override
  public void step() {
    // check for end-of-shift
    Shift newShift = shiftSchedule[indexOfShift(getNowInstant())];
    if (newShift != currentShift) {
      log.info(getName() + " start of shift");
      // Take all batteries out of service
      double totalEnergy = getEnergyCharging() + getEnergyInUse();
      setEnergyCharging(getEnergyCharging() + getEnergyInUse());
      setCapacityInUse(0.0);
      setEnergyInUse(0.0);

      // Put the strongest batteries in trucks for the next shift
      if (null != newShift) {
        setCapacityInUse(newShift.getTrucks() * batteryCapacity);
        setEnergyInUse(Math.min(getCapacityInUse(), totalEnergy));
        setEnergyCharging(totalEnergy - getEnergyInUse());
      }
      log.info(
          getName()
              + ": new shift cInUse "
              + capacityInUse
              + ", eInUse "
              + energyInUse
              + ", eCharging "
              + energyCharging);
      currentShift = newShift;
    }

    // discharge batteries on active trucks
    if (null != currentShift) {
      double usage = Math.max(0.0, normal.sample() * truckStd + truckKW * currentShift.getTrucks());
      double deficit = usage - getEnergyInUse();
      log.debug(getName() + ": trucks use " + usage + " kWh");
      if (deficit > 0.0) {
        log.warn(getName() + ": trucks use more energy than available by " + deficit + " kWh");
        addEnergyInUse(deficit);
        addEnergyCharging(-deficit);
      }
      addEnergyInUse(-usage);
    }

    // use energy on chargers, accounting for regulation
    double regulation = getSubscription().getRegulation();
    log.info(getName() + ": regulation " + regulation);
    double energyUsed = useEnergy(regulation);

    // Record energy used
    getSubscription().usePower(energyUsed);
    log.info(
        getName()
            + " cInUse "
            + capacityInUse
            + ", eInUse "
            + energyInUse
            + ", eCharging "
            + energyCharging);
  }
 ShiftEnergy(Instant start, int end, int duration) {
   super();
   this.start = start;
   this.endIndex = end;
   Shift next = shiftSchedule[end];
   if (null != next) {
     energyNeeded = next.getTrucks() * next.getDuration() * getTruckKW();
   }
   this.duration = duration;
 }
 // Computes constraints on future energy needs
 // Amounts are energy needed to run the chargers. Energy input to trucks
 // will be smaller due to charge efficiency.
 ShiftEnergy[] getFutureEnergyNeeds(Instant start, int horizon, double initialCharging) {
   Instant seStart = start;
   int index = indexOfShift(start);
   // current time is likely to be partway into first shift
   Shift currentShift = shiftSchedule[index]; // might be null
   int duration = 0;
   while (shiftSchedule[index] == currentShift) {
     duration += 1;
     index = nextShiftIndex(index);
   }
   Shift nextShift = shiftSchedule[index];
   // this gives us the info we need to start the sequence
   ArrayList<ShiftEnergy> data = new ArrayList<ShiftEnergy>();
   data.add(new ShiftEnergy(seStart, index, duration));
   seStart = seStart.plus(duration * TimeService.HOUR);
   int elapsed = duration;
   // add shifts until we run off the end of the horizon
   // keep in mind that a shift can be null
   while (elapsed < horizon) {
     duration = 0;
     while (nextShift == shiftSchedule[index]) {
       index = nextShiftIndex(index);
       duration += 1;
     }
     nextShift = shiftSchedule[index];
     data.add(new ShiftEnergy(seStart, index, duration));
     elapsed += duration;
     seStart = seStart.plus(duration * TimeService.HOUR);
   }
   // now we convert to array, then walk backward and fill in energy needs
   ShiftEnergy[] result = data.toArray(new ShiftEnergy[data.size()]);
   double shortage = 0.0;
   for (int i = result.length - 1; i >= 0; i--) {
     int endx = result[i].endIndex;
     int prev = previousShiftIndex(endx);
     currentShift = shiftSchedule[prev];
     Shift end = shiftSchedule[endx];
     double needed = 0.0;
     if (null != end) {
       // Assume we need, at the end of each shift, enough energy to
       // run the next shift
       needed = (end.getTrucks() * end.getDuration() * getTruckKW()) / getChargeEfficiency();
     }
     // chargers is min of charger capacity and battery availability
     int chargers = getNChargers();
     int availableBatteries = nBatteries;
     if (null != currentShift) {
       availableBatteries -= currentShift.getTrucks();
     }
     chargers = (int) Math.min(chargers, availableBatteries);
     double available =
         getMaxChargeKW() * result[i].getDuration() * chargers / getChargeEfficiency();
     double surplus = available - needed - shortage;
     shortage = Math.max(0.0, -(available - needed - shortage));
     result[i].setEnergyNeeded(needed);
     result[i].setMaxSurplus(surplus);
   }
   // finally, we need to update the first element with
   // the current battery charge.
   double finalSurplus = result[0].getMaxSurplus();
   if (finalSurplus > 0.0) {
     result[0].setMaxSurplus(finalSurplus + initialCharging);
   } else if (shortage > 0.0) {
     result[0].setMaxSurplus(initialCharging - shortage);
   }
   return result;
 }
  // make sure we have enough charging capacity to support
  // the shift schedule
  void validateChargers() {
    // ToDo -- A single charging should be able to charge a truck worth
    // of batteries in a single shift

    // The total output of the availableChargers should be at least enough
    // to power the trucks over a 24-hour period. Note that the shift schedule
    // starts at midnight, which may not be the start of the current shift.
    double maxNeeded = 0.0;
    int offset = 0;
    while (null == shiftSchedule[offset]) {
      offset += 1;
    }
    Shift currentShift = shiftSchedule[offset];
    int remainingDuration = 0;
    int hoursInShift = (HOURS_DAY - currentShift.getStart()) % HOURS_DAY;
    remainingDuration = currentShift.getDuration() - hoursInShift;

    for (int i = offset; i < (shiftSchedule.length - HOURS_DAY); i++) {
      double totalEnergy = 0.0;
      Shift thisShift = shiftSchedule[i];
      if (thisShift != currentShift) {
        currentShift = thisShift;
        if (null != currentShift) {
          // first block of energy in 24h window starting at i
          remainingDuration = currentShift.getDuration();
        }
      }
      if (null != currentShift) {
        totalEnergy += currentShift.getTrucks() * remainingDuration * truckKW;
      }
      // now run fwd 24h and add energy from future shifts
      Shift current = currentShift;
      // int shiftStart = i;
      for (int j = i + 1; j < (i + HOURS_DAY); j++) {
        Shift newShift = shiftSchedule[j];
        if (null != newShift && current != newShift) {
          int durationInWindow = (int) Math.min((i + HOURS_DAY - j), newShift.getDuration());
          totalEnergy += newShift.getTrucks() * durationInWindow * truckKW;
          current = newShift;
        }
      }
      maxNeeded = Math.max(maxNeeded, totalEnergy);
      remainingDuration -= 1;
    }

    double chargeEnergy = nChargers * maxChargeKW * HOURS_DAY;
    if (maxNeeded > chargeEnergy) {
      double need = (maxNeeded - chargeEnergy) / (maxChargeKW * HOURS_DAY);
      int add = (int) Math.ceil(need);
      log.error(
          "Insufficient charging capacity for "
              + getName()
              + ": have "
              + chargeEnergy
              + ", need "
              + maxNeeded
              + ". Adding "
              + add
              + " availableChargers.");
      setNChargers(getNChargers() + add);
    }
  }