/*
 * @(#)AggregatedRange.java 6/9/2011
 *
 * Copyright 2002 - 2011 JIDE Software Inc. All rights reserved.
 */

package com.jidesoft.range;

import java.beans.PropertyChangeListener;
import java.util.*;

/**
 * A range class formed from a collection of Positionable instances.
 * The class can be used to derive the minimum and maximum values for the collection of Positionables,
 * as well as providing other useful information such as the sum of all the positive values and the sum
 * of all the negative values. (These are used in the preparation of a stacked bar chart.)
 */
public class AggregatedRange implements Range<Double> {
    private Double positiveSum;
    private int positiveCount;
    private Double negativeSum;
    private int negativeCount;
    private List<Positionable> positions;

    /**
     * Create an empty range
     */
    public AggregatedRange() {
        this(null);
    }

    /**
     * Create a range from the supplied Positionable instances
     * @param positions the instances of the Positionable interface
     */
    public AggregatedRange(Collection<Positionable> positions) {
        this.positions = positions == null ? new ArrayList<Positionable>() : new ArrayList<Positionable>(positions);
        Collections.sort(this.positions);
    }

    /**
     * The lower value of the range; for this class it is the same as minimum()
     * @return the lower value of the range
     */
    public Double lower() {
        return minimum();
    }

    /**
     * The upper value of the range; for this class it is the same as maximum()
     * @return the upper value of the range
     */
    public Double upper() {
        return maximum();
    }

    /**
     * The number of points being combined in this range
     * @return the number of points contributing to the range
     */
    public int getCount() {
        return positions.size();
    }

    /**
     * Computes the sum of all the positive Positionables
     * @return the sum of all the positive Positionables
     */
    public double getPositiveSum() {
        if (positiveSum == null) {
            updatePositives();
        }
        return positiveSum;
    }

    public int getPositiveCount() {
        if (positiveSum == null) {
            updatePositives();
        }
        return positiveCount;
    }

    private void updatePositives() {
        // Use a double to avoid autoboxing
            double sum = 0.0;
            positiveCount = 0;
            if (positions != null) {
                for (Positionable pos : positions) {
                    if (pos.position() >= 0) {
                        sum += pos.position();
                        positiveCount++;
                    }
                }
            }
            positiveSum = sum;
    }

    /**
     * Computes the sum of all the negative Positionables
     * @return the sum of all the negative Positionables
     */
    public double getNegativeSum() {
        if (negativeSum == null) {
            updateNegatives();
        }
        return negativeSum;
    }

    public int getNegativeCount() {
        if (negativeSum == null) {
            updateNegatives();
        }
        return negativeCount;
    }


    private void updateNegatives() {
        // Use a double to avoid autoboxing
            double sum = 0.0;
            negativeCount = 0;
            if (positions != null) {
                for (Positionable pos : positions) {
                    if (pos.position() < 0) {
                        sum += pos.position();
                        negativeCount++;
                    }
                }
            }
            negativeSum = sum;
    }

    /**
     * Returns the minimum (numeric) value in the range
     * @return the minimum value in the range
     */
    public double minimum() {
        if (positions == null || positions.size() == 0) {
            return Double.NEGATIVE_INFINITY;
        }
        Positionable first = positions.get(0);
        return first.position();
    }

    /**
     * Returns the maximum (numeric) value in the range
     * @return the maximum value in the range
     */
    public double maximum() {
        if (positions == null || positions.size() == 0) {
            return Double.POSITIVE_INFINITY;
        }
        Positionable last = positions.get(positions.size()-1);
        return last.position();
    }

    /**
     * The size of the range, computed as the difference between the maximum and the minimum
     * @return the size of the range (i.e., max - min)
     */
    public double size() {
        return maximum() - minimum();
    }

    /**
     * Returns a boolean to indicate whether the supplied Double lies within this range
     */
    public boolean contains(Double item) {
        return item != null && item >= minimum() && item <= maximum();
    }

    /**
     * Not supported in this class
     */
    public void adjust(Double lower, Double upper) {
        throw new UnsupportedOperationException("An aggregated range is immutable");
    }

    /**
     * Not supported in this class
     */
    public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
        throw new UnsupportedOperationException("An aggregated range is immutable");
    }

    /**
     * Not supported in this class
     */
    public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
        throw new UnsupportedOperationException("An aggregated range is immutable");
    }
}
