@Override
  public List<GeoTimeSerie> filter(Map<String, String> labels, List<GeoTimeSerie>... series)
      throws WarpScriptException {
    List<GeoTimeSerie> retained = new ArrayList<GeoTimeSerie>();

    for (List<GeoTimeSerie> gtsinstances : series) {
      for (GeoTimeSerie serie : gtsinstances) {
        Object last = GTSHelper.getLastValue(serie);

        if (null == last) {
          continue;
        }

        switch (type) {
          case LONG:
            if (((Long) threshold).compareTo(((Number) last).longValue()) >= 0) {
              retained.add(serie);
            }
            break;
          case DOUBLE:
            if (((Double) threshold).compareTo(((Number) last).doubleValue()) >= 0) {
              retained.add(serie);
            }
            break;
          case STRING:
            if (((String) threshold).compareTo(last.toString()) >= 0) {
              retained.add(serie);
            }
            break;
        }
      }
    }

    return retained;
  }
  public List<GeoTimeSerie> filterOLD(Map<String, String> labels, List<GeoTimeSerie>... series)
      throws WarpScriptException {
    //
    // Sort GTS in ascending chronological order
    //

    List<GeoTimeSerie> allseries = new ArrayList<GeoTimeSerie>();

    for (List<GeoTimeSerie> lserie : series) {
      allseries.addAll(lserie);
    }

    for (GeoTimeSerie gts : allseries) {
      GTSHelper.sort(gts, false);
    }

    //
    // Assume the input packets are in the first GTS
    //

    int[] indices = new int[allseries.size()];

    //
    // We assume the packets exit the network in the same order they entered it
    //

    int n = allseries.get(0).size();

    GeoTimeSerie inGTS = allseries.get(0);

    //
    // Create output GTS
    //

    List<GeoTimeSerie> outGTS = new ArrayList<GeoTimeSerie>();

    for (int i = 0; i < n; i++) {
      outGTS.add(allseries.get(i).cloneEmpty());
    }

    //
    // Duplicate the hashes so we can sort them
    //

    long[][] sortedHashes = new long[allseries.size()][];

    for (int i = 1; i < allseries.size(); i++) {
      sortedHashes[i] = GTSHelper.longValues(allseries.get(i));
      Arrays.sort(sortedHashes[i]);
    }

    // Maximum latency, this is to enforce a stop condition when looking for a
    // matching out packet

    long maxlatency = 1000L;

    while (indices[0] < n) {
      long ints = GTSHelper.tickAtIndex(inGTS, indices[0]);
      long maxoutts = ints + maxLatency;
      long payloadHash = GTSHelper.tickAtIndex(inGTS, indices[0]);

      //
      // determine which outGTS has the packet
      //

      long minlatency = Long.MAX_VALUE;

      for (int i = 1; i < allseries.size(); i++) {
        GeoTimeSerie gts = allseries.get(i);
        if (payloadHash == (long) GTSHelper.valueAtIndex(gts, indices[i])
            || -1 != Arrays.binarySearch(sortedHashes[i], payloadHash)) {
          int k = indices[i];
          int nn = GTSHelper.nvalues(gts);

          while (k < nn
              && payloadHash != (long) GTSHelper.valueAtIndex(gts, k)
              && GTSHelper.tickAtIndex(gts, k) <= maxoutts) {
            k++;
          }

          if (k >= nn) {
            continue;
          }

          long outts = GTSHelper.tickAtIndex(gts, k);

          if (outts > maxoutts) {
            continue;
          }

          //
          // Ok, we've found a match
          //

          //
          // If the match was at 'indices[i]', advance 'indices[i]'
          //

          if (k == indices[i]) {
            indices[i]++;
          }

          long latency = outts - ints;

          GTSHelper.setValue(outGTS.get(i), outts, latency);

          if (latency < minlatency) {
            minlatency = latency;
          }
        }
      }

      //
      // Record the latency for the input GTS
      //

      if (Long.MAX_VALUE == minlatency) {
        GTSHelper.setValue(outGTS.get(0), ints, -1L);
      } else {
        GTSHelper.setValue(outGTS.get(0), ints, minlatency);
      }

      indices[0]++;
    }

    return outGTS;
  }
  @Override
  public java.util.List<GeoTimeSerie> filter(
      java.util.Map<String, String> labels, java.util.List<GeoTimeSerie>[] series)
      throws WarpScriptException {
    //
    // Sort GTS by (LONG) value
    //

    List<GeoTimeSerie> allseries = new ArrayList<GeoTimeSerie>();

    if (0 == series[0].size() || series[0].size() > 1) {
      return null;
    }

    for (List<GeoTimeSerie> lserie : series) {
      allseries.addAll(lserie);
    }

    for (GeoTimeSerie gts : allseries) {
      GTSHelper.valueSort(gts, false);
    }

    //
    // Allocate an array for keeping track of indices
    //

    int[] indices = new int[allseries.size()];

    //
    // Create output GTS
    //

    List<GeoTimeSerie> result = new ArrayList<GeoTimeSerie>();

    //
    // Uplink packets are in the first GTS
    // All other GTS are downlink packets
    //

    GeoTimeSerie inGTS = allseries.get(0);

    GeoTimeSerie uplinkLatencyMinGTS = inGTS.cloneEmpty();
    uplinkLatencyMinGTS.getMetadata().setName(inGTS.getName() + ":uplink.latency.min");

    if (this.uplinkLatencyMin) {
      result.add(uplinkLatencyMinGTS);
    }

    GeoTimeSerie uplinkLatencyMax = inGTS.cloneEmpty();
    uplinkLatencyMax.getMetadata().setName(inGTS.getName() + ":uplink.latency.max");

    if (this.uplinkLatencyMax) {
      result.add(uplinkLatencyMax);
    }

    List<GeoTimeSerie> downlinkLatencyMinGTS = new ArrayList<GeoTimeSerie>();
    List<GeoTimeSerie> downlinkLatencyMaxGTS = new ArrayList<GeoTimeSerie>();
    List<GeoTimeSerie> downlinkMatchesGTS = new ArrayList<GeoTimeSerie>();

    int n = allseries.size();

    for (int i = 1; i < n; i++) {
      if (this.downlinkLatencyMin) {
        GeoTimeSerie gts = allseries.get(i).cloneEmpty();
        gts.setName(gts.getName() + ":downlink.latency.min");
        downlinkLatencyMinGTS.add(gts);
      }
      if (this.downlinkLatencyMax) {
        GeoTimeSerie gts = allseries.get(i).cloneEmpty();
        gts.setName(gts.getName() + ":downlink.latency.max");
        downlinkLatencyMaxGTS.add(gts);
      }
      if (this.downlinkMatches) {
        GeoTimeSerie gts = allseries.get(i).cloneEmpty();
        gts.setName(gts.getName() + ":downlink.matches");
        downlinkMatchesGTS.add(gts);
      }
    }

    if (this.downlinkLatencyMin) {
      result.addAll(downlinkLatencyMinGTS);
    }

    if (this.downlinkLatencyMax) {
      result.addAll(downlinkLatencyMaxGTS);
    }

    if (this.downlinkMatches) {
      result.addAll(downlinkMatchesGTS);
    }

    GeoTimeSerie downlinksBitsetGTS = inGTS.cloneEmpty();
    downlinksBitsetGTS.setName(inGTS.getName() + ":downlinks.bitset");

    if (this.downlinksBitset) {
      result.add(downlinksBitsetGTS);
    }

    GeoTimeSerie downlinksTotalMatchesGTS = inGTS.cloneEmpty();
    downlinksTotalMatchesGTS.setName(inGTS.getName() + ":downlinks.totalmatches");

    if (this.downlinksTotalMatches) {
      result.add(downlinksTotalMatchesGTS);
    }

    GeoTimeSerie downlinksWithMatchesGTS = inGTS.cloneEmpty();
    downlinksWithMatchesGTS.setName(inGTS.getName() + ":downlinks.withmatches");

    if (this.downlinksWithMatches) {
      result.add(downlinksWithMatchesGTS);
    }

    //
    // Any packet on a downlink whose ts is more than 'maxlatency' from the same packet on the
    // 'uplink' will not be considered
    // as a received packet but as a duplicate.
    //

    //
    // Loop on input packets
    //

    long maxinidx = inGTS.size();

    while (indices[0] < maxinidx) {
      long uplinkHash = (long) GTSHelper.valueAtIndex(inGTS, indices[0]);
      long uplinkTimestamp = GTSHelper.tickAtIndex(inGTS, indices[0]);

      long minLatency = Long.MAX_VALUE;
      long maxLatency = Long.MIN_VALUE;
      long totalLatency = 0L;
      int matchingDownlinkHashes = 0;
      int downlinksWithMatches = 0;

      //
      // Mask of downlinks having received the packet
      //

      long downlinksMask = 0L;

      //
      // Loop on downlinks
      //

      for (int i = 1; i < allseries.size(); i++) {
        GeoTimeSerie gts = allseries.get(i);

        int startidx = indices[i];

        long downlink_minLatency = Long.MAX_VALUE;
        long downlink_maxLatency = Long.MIN_VALUE;
        int downlinkMatches = 0;

        do {
          if (indices[i] >= gts.size()) {
            break;
          }

          long downlinkHash = (long) GTSHelper.valueAtIndex(gts, indices[i]);

          //
          // Skip downlinkHash which are too low
          //

          if (downlinkHash < uplinkHash) {
            indices[i]++;
            startidx = indices[i];
            continue;
          }

          //
          // Exit the loop if the current hash on the dowlink is > than the hash on the uplink,
          //

          if (downlinkHash > uplinkHash) {
            break;
          }

          long downlinkTimestamp = GTSHelper.tickAtIndex(gts, indices[i]);

          //
          // Skip timestamps which less than 'minLatency' after the uplink one
          //

          if (downlinkTimestamp < uplinkTimestamp + this.minLatency) {
            indices[i]++;
            startidx = indices[i];
            continue;
          }

          //
          // Compare the timestamp of the uplinkHash and that of the downlinkHash
          // FIXME(hbs): since packets may arrive out of order, we might encounter the case when
          // identical packets were sent close to each other,
          //             in which case we cannot know for sure if the downlinkHash is to be matched
          // with the current uplinkHash or another one
          //

          long latency = downlinkTimestamp - uplinkTimestamp;

          //
          // Stop if the hash matches but the latency is over the threshold we've set
          //

          if (latency > this.maxLatency) {
            break;
          }

          if (latency > maxLatency) {
            maxLatency = latency;
          }

          if (latency < minLatency) {
            minLatency = latency;
          }

          if (latency > downlink_maxLatency) {
            downlink_maxLatency = latency;
          }

          if (latency < downlink_minLatency) {
            downlink_minLatency = latency;
          }

          downlinkMatches++;

          totalLatency += latency;

          matchingDownlinkHashes++;

          if (startidx == indices[i]) {
            downlinksWithMatches++;
            downlinksMask |= (1L << (i - 1));
          }

          // TODO(hbs): How do we keep track of the PLR for each link? We would need to issue a GTS
          // for each valid P2P link
          //
          // TODO(hbs): emit GTS value for the downlink (latency of packets received at
          // downlinkTimestamp).
          //

          indices[i]++;

        } while (true);

        if (this.downlinkLatencyMax) {
          if (Long.MIN_VALUE == downlink_maxLatency) {
            GTSHelper.setValue(downlinkLatencyMaxGTS.get(i - 1), uplinkTimestamp, -1L);
          } else {
            GTSHelper.setValue(
                downlinkLatencyMaxGTS.get(i - 1), uplinkTimestamp, downlink_maxLatency);
          }
        }
        if (this.downlinkLatencyMin) {
          if (Long.MAX_VALUE == downlink_minLatency) {
            GTSHelper.setValue(downlinkLatencyMinGTS.get(i - 1), uplinkTimestamp, -1L);
          } else {
            GTSHelper.setValue(
                downlinkLatencyMinGTS.get(i - 1), uplinkTimestamp, downlink_minLatency);
          }
        }
        if (this.downlinkMatches) {
          GTSHelper.setValue(downlinkMatchesGTS.get(i - 1), uplinkTimestamp, downlinkMatches);
        }
      }

      if (this.downlinksBitset) {
        GTSHelper.setValue(downlinksBitsetGTS, uplinkTimestamp, downlinksMask);
      }

      if (this.downlinksTotalMatches) {
        GTSHelper.setValue(downlinksTotalMatchesGTS, uplinkTimestamp, matchingDownlinkHashes);
      }

      if (this.downlinksWithMatches) {
        GTSHelper.setValue(downlinksWithMatchesGTS, uplinkTimestamp, downlinksWithMatches);
      }

      if (this.uplinkLatencyMin) {
        if (Long.MAX_VALUE == minLatency) {
          GTSHelper.setValue(uplinkLatencyMinGTS, uplinkTimestamp, -1L);
        } else {
          GTSHelper.setValue(uplinkLatencyMinGTS, uplinkTimestamp, minLatency);
        }
      }

      if (this.uplinkLatencyMax) {
        if (Long.MIN_VALUE == maxLatency) {
          GTSHelper.setValue(uplinkLatencyMax, uplinkTimestamp, -1L);
        } else {
          GTSHelper.setValue(uplinkLatencyMax, uplinkTimestamp, maxLatency);
        }
      }
      indices[0]++;
    }

    return result;
  }