class CusumDetector extends AbstractOutlierDetector {

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

        val params = getParams();
        val trusted = isTrusted();
        val observed = metricData.getValue();

        this.movingRange += Math.abs(this.prevValue - observed);

        val stdDev = avgMovingRange() / STD_DEV_DIVISOR;
        val slack = params.getSlackParam() * stdDev;
        val weakDelta = params.getWeakSigmas() * stdDev;
        val strongDelta = params.getStrongSigmas() * stdDev;

        this.sumHigh = Math.max(0, this.sumHigh + observed - (params.getTargetValue() + slack));
        this.sumLow = Math.min(0, this.sumLow + observed - (params.getTargetValue() - slack));

        this.prevValue = observed;

        // FIXME This eventually overflows. Realistically it won't happen, but would be nice to fix it anyway. [WLW]
        this.totalDataPoints++;

        Double upperStrong;
        Double upperWeak;
        Double lowerStrong;
        Double lowerWeak;
        AnomalyLevel level;

        if (totalDataPoints <= params.getWarmUpPeriod()) {
            level = MODEL_WARMUP;
        } else {
            level = NORMAL;

            upperWeak = weakDelta;
            upperStrong = strongDelta;
            lowerWeak = -weakDelta;
            lowerStrong = -strongDelta;

            // Below we use strict inequalities for anomaly checks instead of nonstrict.
            // The reason is that if a metric runs at a constant value, then the stdev is
            // 0, and another incoming metric data with the same constant value should
            // come out as NORMAL, not STRONG. [WLW]

            switch (params.getType()) {
                case LEFT_TAILED:
                    lowerWeak = -weakDelta;
                    lowerStrong = -strongDelta;
                    if (this.sumLow < lowerStrong) {
                        level = STRONG;
                        // TODO Check whether this is really what we are supposed to do here. [WLW]
                        resetSums();
                    } else if (this.sumLow < lowerWeak) {
                        level = WEAK;
                    }
                    break;
                case RIGHT_TAILED:
                    if (this.sumHigh > upperStrong) {
                        level = STRONG;
                        // TODO Check whether this is really what we are supposed to do here. [WLW]
                        resetSums();
                    } else if (this.sumHigh > upperWeak) {
                        level = WEAK;
                    }
                    break;
                case TWO_TAILED:
                    if (this.sumHigh > upperStrong || this.sumLow < lowerStrong) {
                        level = STRONG;
                        // TODO Check whether this is really what we are supposed to do here. [WLW]
                        resetSums();
                    } else if (this.sumHigh > upperWeak || this.sumLow < lowerWeak) {
                        level = WEAK;
                    }
                    break;
                default:
                    throw new IllegalStateException("Illegal type: " + params.getType());
            }
        }

        return new OutlierDetectorResult(level, trusted);
    }

    public  CusumDetector(UUID uuid, CusumDetectorParams params, boolean trusted);

}