/**
   * Iterates through the provided list and classifies the mobility mode for each list item.
   *
   * @param sensorDataPoints the data points to classify
   * @return a list of ClassifiedPoints. The list will be in the same order as the provided list.
   * @throws IllegalArgumentException if the provided list is null
   */
  public static List<ClassifiedPoint> classify(List<SensorDataPoint> sensorDataPoints) {
    if (sensorDataPoints == null) {
      throw new IllegalArgumentException("sensorDataPoints is required");
    }
    if (sensorDataPoints.contains(null)) {
      throw new IllegalArgumentException("sensorDataPoints contains null");
    }

    List<ClassifiedPoint> classifiedPoints = new ArrayList<ClassifiedPoint>();

    // This could be an instance variable because it is stateless
    MobilityClassifier mobilityClassifier = new MobilityClassifier();

    WifiScan previousWifiScan = null;
    String previousMode = null;

    for (SensorDataPoint sensorDataPoint : sensorDataPoints) {
      Classification classification =
          mobilityClassifier.classify(
              sensorDataPoint.getAccelerometerSamples(),
              sensorDataPoint.getSpeed(),
              sensorDataPoint.getWifiScan(),
              previousWifiScan,
              previousMode);

      previousWifiScan = sensorDataPoint.getWifiScan();
      previousMode = classification.getMode();

      classifiedPoints.add(new ClassifiedPoint(sensorDataPoint.getId(), classification));
    }

    return classifiedPoints;
  }
  /**
   * Runs the classifier against all of the Mobility points in the list.
   *
   * @param mobilityPoints The Mobility points that are to be classified by the server.
   * @throws ServiceException Thrown if there is an error with the classification service.
   */
  public void classifyData(final String uploadersUsername, final List<MobilityPoint> mobilityPoints)
      throws ServiceException {

    // If the list is empty, just exit.
    if (mobilityPoints == null) {
      return;
    }

    // Create a new classifier.
    MobilityClassifier classifier = new MobilityClassifier();

    // Create place holders for the previous data.
    String previousWifiMode = null;
    List<WifiScan> previousWifiScans = new LinkedList<WifiScan>();

    // This is a bit more involved now that we are doing everything through
    // the observers.
    /*
    if(mobilityPoints.size() > 0) {
    	try {
    		MobilityPoint firstPoint = mobilityPoints.get(0);
    		DateTime startDate = new DateTime();

    		startDate =
    				new DateTime(
    					firstPoint.getTime() -
    					MAX_MILLIS_OF_PREVIOUS_WIFI_DATA);

    		List<MobilityPoint> previousPoints =
    				userMobilityQueries.getMobilityInformation(
    					uploadersUsername,
    					startDate,
    					new DateTime(firstPoint.getTime()),
    					null,
    					null,
    					null);

    		for(MobilityPoint previousPoint : previousPoints) {
    			try {
    				previousWifiScans.add(previousPoint.getWifiScan());
    			}
    			catch(DomainException e) {
    				// This point does not have a WifiScan, so we will skip
    				// it.
    			}
    		}
    	}
    	catch(DataAccessException e) {
    		throw new ServiceException(e);
    	}
    }
    */

    // For each of the Mobility points,
    for (MobilityPoint mobilityPoint : mobilityPoints) {
      // If the data point is of type error, don't attempt to classify
      // it.
      if (mobilityPoint.getMode().equals(Mode.ERROR)) {
        continue;
      }

      // If the SubType is sensor data,
      if (MobilityPoint.SubType.SENSOR_DATA.equals(mobilityPoint.getSubType())) {
        SensorData currSensorData = mobilityPoint.getSensorData();

        // Get the Samples from this new point.
        List<Sample> samples;
        try {
          samples = mobilityPoint.getSamples();
        } catch (DomainException e) {
          throw new ServiceException("There was a problem retrieving the samples.", e);
        }

        // Get the new WifiScan from this new point.
        WifiScan wifiScan;
        if (mobilityPoint.getSensorData().getWifiData() == null) {
          wifiScan = null;
        } else {
          try {
            wifiScan = mobilityPoint.getWifiScan();
          } catch (DomainException e) {
            throw new ServiceException("The Mobility point does not contain WiFi data.", e);
          }
        }

        // Prune out the old WifiScans that are more than 10 minutes
        // old.
        long minPreviousTime = mobilityPoint.getTime() - MAX_MILLIS_OF_PREVIOUS_WIFI_DATA;
        Iterator<WifiScan> previousWifiScansIter = previousWifiScans.iterator();
        while (previousWifiScansIter.hasNext()) {
          if (previousWifiScansIter.next().getTime() < minPreviousTime) {
            previousWifiScansIter.remove();
          } else {
            // Given the fact that the list is ordered, we can now
            // be assured that all of the remaining WiFi scans are
            // invalid.
            break;
          }
        }

        // Classify the data.
        Classification classification =
            classifier.classify(
                samples, currSensorData.getSpeed(), wifiScan, previousWifiScans, previousWifiMode);

        // Update the place holders for the previous data.
        if (wifiScan != null) {
          previousWifiScans.add(wifiScan);
        }
        previousWifiMode = classification.getWifiMode();

        // If the classification generated some results, pull them out
        // and store them in the Mobility point.
        if (classification.hasFeatures()) {
          try {
            mobilityPoint.setClassifierData(
                classification.getFft(),
                classification.getVariance(),
                classification.getAverage(),
                MobilityPoint.Mode.valueOf(classification.getMode().toUpperCase()));
          } catch (DomainException e) {
            throw new ServiceException(
                "There was a problem reading the classification's information.", e);
          }
        }
        // If the features don't exist, then create the classifier data
        // with only the mode.
        else {
          try {
            mobilityPoint.setClassifierModeOnly(
                MobilityPoint.Mode.valueOf(classification.getMode().toUpperCase()));
          } catch (DomainException e) {
            throw new ServiceException("There was a problem reading the classification's mode.", e);
          }
        }
      }
    }
  }