class IndividualsDetector implements Detector {

    @Override
    public DetectorResult detect(MetricData metricData) {
        notNull(metricData, "metricData can't be null");

        val params = getParams();
        val trusted = isTrusted();

        val observed = metricData.getValue();
        val stdDev = sqrt(this.variance);
//        val weakDelta = params.getWeakSigmas() * stdDev;
        val strongDelta = params.getStrongSigmas() * stdDev;

        val currentRange = Math.abs(prevValue - observed);

        // TODO Modify this to use AnomalyClassifier.detect() so we can get tail checks. [WLW]

        // Looks like currently this detector supports only a single anomaly level (strong).
        val thresholds = new AnomalyThresholds(
                this.mean + strongDelta,
                this.mean + strongDelta,
                this.mean - strongDelta,
                this.mean - strongDelta);

        AnomalyLevel level;

        if (totalDataPoints > params.getWarmUpPeriod()) {
            level = NORMAL;
            if (currentRange > upperControlLimit_R) {
                level = STRONG;
            } else {
                if (observed > upperControlLimit_X || observed < lowerControlLimit_X) {
                    level = WEAK;
                }
            }
        } else {
            level = MODEL_WARMUP;
        }

        if (level == NORMAL || level == MODEL_WARMUP) {
            this.movingRangeSum += abs(currentRange);
            this.mean = getRunningMean(observed);
            this.totalDataPoints++;
        }

        if (((totalDataPoints - params.getWarmUpPeriod()) % RECOMPUTE_LIMITS_PERIOD) == 0) {
            double averageMovingRange = getAverageMovingRange();
            double multiplier = params.getStrongSigmas() / R_CONTROL_CHART_CONSTANT_D2;
            this.target = this.mean;

            upperControlLimit_R = R_CONTROL_CHART_CONSTANT_D4 * averageMovingRange;
            upperControlLimit_X = this.target + multiplier * averageMovingRange;
            lowerControlLimit_X = this.target - multiplier * averageMovingRange;
        }
        this.prevValue = observed;

        final OutlierDetectorResult result = new OutlierDetectorResult(level);
        result.setPredicted(this.mean);
        result.setThresholds(thresholds);
        result.setTrusted(trusted);
        return result;
    }

    public  IndividualsDetector(UUID uuid, IndividualsDetectorParams params, boolean trusted, String name);

}