import java.awt.*;
import javax.swing.*;

    /** This class is designed to provide for the display of a BarChart.
     *  The (inclusive) min and max values of the range are settable
     *  and the number of units per bar.
     *  Values are added to the chart via the addValue method.
     * 
     *  This class is not intended for production usage but to provide a
     *  vehicle for teaching about if-statements. There are no checks
     *  to ensure that min <= max or that unitsPerBar <= (max-min+1).
     *  There is also no check to ensure that min <= value <= max when
     *  adding a new value to the chart. Values less than min are added
     *  to the first bar and values greater than max cause an array
     *  bounds exception.
     *  A controller class should provide checks to stop irregular
     *  conditions being set.
     *  All non-public methods are private to limit access from
     *  simple programs.
     */
class BarChart extends JComponent {
    /** The default minimum.
     */
    public static final int DEFAULTMIN = 1;
    /** The default minimum.
     */
    public static final int DEFAULTMAX = 100;
    /** The default units per bar.
     */
    public static final int DEFAULTUNITSPERBAR = 10;

        /** Construct with default minimum, maximum and units per bar.
         */
    public BarChart(){
        this(DEFAULTMIN,DEFAULTMAX,DEFAULTUNITSPERBAR);
    }

        /** Construct with the given minimum, maximum and units per bar.
         */
    public BarChart(int min,int max,int unitsPerBar){

        this.min = min;
        this.max = max;
        this.unitsPerBar = unitsPerBar;

        // Set up counts for the right number of bars.
        final int range = max-min+1;
        int numBars = range/unitsPerBar;
        if((range % unitsPerBar) > 0){
            numBars++;
        }
        setCounts(new int[numBars]);

        int width = 300, height = 250;
        setPreferredSize(new Dimension(width,height));
        ComponentFrame frame = new ComponentFrame("Bar Chart");
        frame.addComponent(this);
        frame.pack();
    }

        /** Add a value to the chart.
         */
    public void addValue(int value){
        final int barNumber = (value-min)/unitsPerBar;
        getCounts()[barNumber]++;
        repaint();
    }


        /** Redraw the chart.
         */
    public void paintComponent(Graphics g){
        Image i = getImage();
        // Check for resizing.
        final Dimension size = getSize();
        if((i == null) || (i.getWidth(this) != size.width) ||
                            (i.getHeight(this) != size.height)){
            // Redraw it.
            drawBars();
            i = getImage();
        }
        if(i != null){
            g.drawImage(getImage(),0,0,this);
        }
    }


        /** Refresh the chart image.
         */
    public void repaint(){
        drawBars();
        paintComponent(getGraphics());
    }

        /** Get the current minimum.
         */
    public int getMin(){
        return min;
    }

        /** Get the current maximum.
         */
    public int getMax(){
        return max;
    }

        /** Get the current units per bar.
         */
    public int getUnitsPerBar(){
        return unitsPerBar;
    }

        /** Draw all the bars on the graphics context of an offscreen
         * image. Make sure the current image is the right size.
         */
    protected void drawBars(){
        int[] data = getCounts();
        if(data != null){
            drawBarsFromData(data);
        }
    }

    protected void drawBarsFromData(int[] data){
        // Make sure it is sensible to be drawing anything.
        final Dimension size = getSize();
        if((size.width == 0) || (size.height == 0)){
           return ;
        }
        // Check for resizing.
        Image i = getImage();
        if((i == null) || (i.getWidth(this) != size.width) ||
                        (i.getHeight(this) != size.height)){
            i = createImage(size.width,size.height);
            setImage(i);
        }
        // Where to draw to.
        final Graphics g = i.getGraphics();
        // Clear any old drawing.
        g.setColor(Color.white);
        g.fillRect(0,0,size.width,size.height);
        // Find largest count.
        final int maxCount = findMax(data);
        if(maxCount != 0){
            final int numBars = data.length;
            // Offset at the left (and right) edge, above and below the chart.
            final int leftEdge = 5, topEdge = 25, bottomEdge = 30;
            // The width of each bar.
            final int barWidth = (size.width-2*leftEdge)/numBars;
            // Remaining height for the whole chart.
            final int availableHeight = size.height-(topEdge+bottomEdge);
            // We need to calculate string widths in the current font.
            final FontMetrics metrics = getFontMetrics(getFont());
            for(int barNumber = 0; barNumber < numBars; barNumber++){
                final int value = data[barNumber];
                // Determine the bar height based upon the current maximum.
                final int barHeight = availableHeight*value/maxCount;
                final int x = leftEdge+barNumber*barWidth,
                                    y = size.height-(barHeight+bottomEdge);
                g.setColor(Color.black);
                g.drawRect(x,y,barWidth,barHeight);
                g.setColor(Color.yellow);
                g.fillRect(x+1,y+1,barWidth-1,barHeight-1);

                // Show the count just above the bar.
                final String howMany = ""+value;
                int stringWidth = metrics.stringWidth(howMany);
                g.setColor(Color.black);
                g.drawString(howMany,x+(barWidth-stringWidth)/2,
                                size.height-(barHeight+bottomEdge));

                // Draw a label under the bar.
                String label = ""+(min+barNumber*unitsPerBar);
                stringWidth = metrics.stringWidth(label);
                g.drawString(label,x+(barWidth-stringWidth)/2,
                                size.height-bottomEdge+metrics.getHeight()+1);
            }
        }
    }

        /** Find the value, for scaling.
         */
    protected int findMax(int[] counts){
        if(counts != null){
            final int numBars = counts.length;
            int max = counts[0];
            for(int i = 1; i < numBars; i++){
                if(counts[i] > max){
                    max = counts[i];
                }
            }
            return max;
        }
        else{
            return 0;
        }
    }

    private int[] getCounts(){
        return counts;
    }

    private void setCounts(int[] c){
        if(c != null){
            counts = c;
        }
    }

    private Image getImage(){
        return image;
    }

    private void setImage(Image i){
        image = i;
    }

    // Limits of the values to be included.
    private final int min, max;
    // How many units per par.
    private final int unitsPerBar;
    // How many in each bar so far.
    private int[] counts;
    // The offscreen image for double-buffering.
    private Image image;
}
