/**
   * Converts the ObaArrivalInfo array received from the server to an ArrayList for the adapter
   *
   * @param arrivalInfo
   * @param filter routeIds to filter for
   * @param ms current time in milliseconds
   * @return ArrayList of arrival info to be used with the adapter
   */
  public static final ArrayList<ArrivalInfo> convertObaArrivalInfo(
      ObaArrivalInfo[] arrivalInfo,
      ArrayList<String> filter,
      long ms,
      boolean includeArrivalDepartureInStatusLabel) {
    final int len = arrivalInfo.length;
    ArrayList<ArrivalInfo> result = new ArrayList<ArrivalInfo>(len);
    if (filter != null && filter.size() > 0) {
      // Only add routes that haven't been filtered out
      for (int i = 0; i < len; ++i) {
        ObaArrivalInfo arrival = arrivalInfo[i];
        if (filter.contains(arrival.getRouteId())) {
          ArrivalInfo info = new ArrivalInfo(arrival, ms, includeArrivalDepartureInStatusLabel);
          result.add(info);
        }
      }
    } else {
      // Add arrivals for all routes
      for (int i = 0; i < len; ++i) {
        ArrivalInfo info =
            new ArrivalInfo(arrivalInfo[i], ms, includeArrivalDepartureInStatusLabel);
        result.add(info);
      }
    }

    // Sort by ETA
    Collections.sort(result, new InfoComparator());
    return result;
  }
  private String computeLongDescription() {
    StringBuilder sb = new StringBuilder();
    sb.append(ROUTE);
    sb.append(SPACE);
    sb.append(mInfo.getShortName());
    sb.append(SPACE);
    sb.append(mInfo.getHeadsign());
    sb.append(SPACE);

    if (mEta < 0) {
      // Route just arrived or departed
      long invertEta = -mEta;
      if (mIsArrival) {
        sb.append(LongDescription.ARRIVED);
      } else {
        sb.append(LongDescription.DEPARTED);
      }
      sb.append(SPACE);
      sb.append(invertEta);
      sb.append(SPACE);
      if (invertEta < 2) {
        sb.append(MINUTE_AGO);
      } else {
        sb.append(MINUTES_AGO);
      }
    } else if (mEta == 0) {
      // Route is now arriving/departing
      if (mIsArrival) {
        sb.append(IS_NOW_ARRIVING);
      } else {
        sb.append(IS_NOW_DEPARTING);
      }
    } else {
      // Route is arriving or departing in future
      if (mIsArrival) {
        sb.append(IS_ARRIVING_IN);
      } else {
        sb.append(IS_DEPARTING_IN);
      }
      sb.append(SPACE);
      sb.append(mEta);
      sb.append(SPACE);
      if (mEta < 2) {
        sb.append(MINUTE);
      } else {
        sb.append(MINUTES);
      }
    }

    // If its not real-time info, add statement about schedule
    if (!mPredicted) {
      sb.append(SPACE);
      sb.append(BASED_ON_SCHEDULE);
    }
    return sb.toString();
  }
  /**
   * @param includeArrivalDepartureInStatusLabel true if the arrival/departure label should be
   *     included in the status label false if it should not
   */
  public ArrivalInfo(ObaArrivalInfo info, long now, boolean includeArrivalDepartureInStatusLabel) {
    mInfo = info;
    // First, all times have to have to be converted to 'minutes'
    final long nowMins = now / ms_in_mins;
    long scheduled, predicted;
    // If this is the first stop in the sequence, show the departure time.
    if (info.getStopSequence() != 0) {
      scheduled = info.getScheduledArrivalTime();
      predicted = info.getPredictedArrivalTime();
      mIsArrival = true;
    } else {
      // Show departure time
      scheduled = info.getScheduledDepartureTime();
      predicted = info.getPredictedDepartureTime();
      mIsArrival = false;
    }

    final long scheduledMins = scheduled / ms_in_mins;
    final long predictedMins = predicted / ms_in_mins;

    if (predicted != 0) {
      mPredicted = true;
      mEta = predictedMins - nowMins;
      mDisplayTime = predicted;
    } else {
      mPredicted = false;
      mEta = scheduledMins - nowMins;
      mDisplayTime = scheduled;
    }

    mColor = computeColor(scheduledMins, predictedMins);

    mStatusText =
        computeStatusLabel(
            info,
            now,
            predicted,
            scheduledMins,
            predictedMins,
            includeArrivalDepartureInStatusLabel);

    mLongDescription = computeLongDescription();
  }
  /**
   * @param info
   * @param now
   * @param predicted
   * @param scheduledMins
   * @param predictedMins
   * @param includeArrivalDeparture true if the arrival/departure label should be included, false if
   *     it should not
   * @return
   */
  private String computeStatusLabel(
      ObaArrivalInfo info,
      final long now,
      final long predicted,
      final long scheduledMins,
      final long predictedMins,
      boolean includeArrivalDeparture) {
    ObaArrivalInfo.Frequency frequency = info.getFrequency();

    StringBuilder sb = new StringBuilder();

    if (frequency != null) {

      int headwayAsMinutes = (int) (frequency.getHeadway() / 60);
      DateFormat formatter = DateFormat.getTimeInstance(DateFormat.SHORT);

      sb.append("Every ");
      sb.append(headwayAsMinutes);
      sb.append(" minutes ");
      long time = 0;

      if (now < frequency.getStartTime()) {
        time = frequency.getStartTime();
        sb.append("from ");
      } else {
        time = frequency.getEndTime();
        sb.append("until ");
      }
      String label = formatter.format(new Date(time));
      sb.append(label);

      return sb.toString();
    }

    if (predicted != 0) {
      long delay = predictedMins - scheduledMins;

      if (mEta >= 0) {
        // Bus hasn't yet arrived/departed
        return computeArrivalLabelFromDelay(delay);
      } else {
        /** Arrival/departure time has passed */
        if (!includeArrivalDeparture) {
          // Don't include "depart" or "arrive" in label
          if (delay > 0) {
            // Delayed
            sb.append((int) delay);
            sb.append(Status.MINUTE_DELAY);
            return sb.toString();
          } else if (delay < 0) {
            // Early
            delay = -delay;
            sb.append((int) delay);
            if (delay < 2) {
              sb.append(MINUTE_EARLY);
            } else {
              sb.append(MINUTES_EARLY);
            }
            return sb.toString();
          } else {
            // On time
            return Status.ON_TIME;
          }
        }

        if (mIsArrival) {
          // Is an arrival time
          if (delay > 0) {
            // Arrived late
            sb.append(Status.ARRIVED);
            sb.append((int) delay);
            if (delay < 2) {
              sb.append(MINUTE_LATE);
            } else {
              sb.append(MINUTES_LATE);
            }
            return sb.toString();
          } else if (delay < 0) {
            // Arrived early
            delay = -delay;
            sb.append(Status.ARRIVED);
            sb.append((int) delay);
            if (delay < 2) {
              sb.append(MINUTE_EARLY);
            } else {
              sb.append(MINUTES_EARLY);
            }
            return sb.toString();
          } else {
            // Arrived on time
            sb.append(Status.ARRIVED);
            sb.append(Status.ON_TIME.toLowerCase());
            return sb.toString();
          }
        } else {
          // Is a departure time
          if (delay > 0) {
            // Departed late
            sb.append(Status.DEPARTED);
            sb.append((int) delay);
            if (delay < 2) {
              sb.append(MINUTE_LATE);
            } else {
              sb.append(MINUTES_LATE);
            }
            return sb.toString();
          } else if (delay < 0) {
            // Departed early
            delay = -delay;
            sb.append(Status.DEPARTED);
            sb.append((int) delay);
            if (delay < 2) {
              sb.append(MINUTE_EARLY);
            } else {
              sb.append(MINUTES_EARLY);
            }
            return sb.toString();
          } else {
            // Departed on time
            sb.append(Status.DEPARTED);
            sb.append(Status.ON_TIME);
            return sb.toString();
          }
        }
      }
    } else {
      // Scheduled times
      if (!includeArrivalDeparture) {
        return SCHEDULED;
      }

      if (mIsArrival) {
        return SCHEDULED_ARRIVAL;
      } else {
        return SCHEDULED_DEPARTURE;
      }
    }
  }