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

class SwingBarChart extends JComponent {
    public SwingBarChart(RangeCounts data){
        setData(data);
    }

    public Dimension getPreferredSize(){
        return new Dimension(450,250);
    }

    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);
        }
    }


    public void update(Graphics g){
        drawBars();
        paint(getGraphics());
    }

    protected void drawBars(){
        // Draw the bars directly from the counts.
        drawBarsFromData(getData().getCounts());
    }

        // Draw all the bars on the graphics context of an offscreen
        // image. Make sure the current image is the right size.
    protected void drawBarsFromData(int[] dataValues){
        Image i = getImage();
        // Check for resizing.
        final Dimension size = getSize();
        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.cyan);
        g.fillRect(0,0,size.width,size.height);

        RangeCounts data = getData();
        // Find the largest bar to be drawn.
        final int maxCount = findMax(dataValues);
        if(maxCount != 0){
            final int numBars = dataValues.length;
            final String[] labels = getBarLabels();

            // 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());
            final int min = data.getMin(), unitsPerBar = data.getUnitsPerBar();

            for(int barNumber = 0; barNumber < numBars; barNumber++){
                final int value = dataValues[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.
                final String label = labels[barNumber];
                stringWidth = metrics.stringWidth(label);
                g.drawString(label,x+(barWidth-stringWidth)/2,
                                size.height-bottomEdge+metrics.getHeight()+1);
            }
        }
    }

    public void setBarLabels(String[] l){
        barLabels = l;
    }

        // Return the bar labels. If there are none, make some up,
        // based upon the lowest value of each bar.
    protected String[] getBarLabels(){
        if(barLabels == null){
            setBarLabels(getDefaultBarLabels());
        }
        return barLabels;
    }

        // Return a default set of labels for the bars.
        // These are based on the min value for each bar.
    protected String[] getDefaultBarLabels(){
        RangeCounts data = getData();
        String[] labels = null;
        if(data != null){
            final int unitsPerBar = data.getUnitsPerBar();
            final int numBars = data.numCounts();
            // The low point of a bar range.
            // Use to build a label.
            int low = data.getMin();
            labels = new String[numBars];
            for(int barNumber = 0; barNumber < numBars; barNumber++){
                labels[barNumber] = low+".."+(low+unitsPerBar-1);
                low += unitsPerBar;
            }
            setBarLabels(labels);
        }
        return labels;
    }

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

    protected Image getImage(){
        return image;
    }

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

    protected RangeCounts getData(){
        return data;
    }

    protected void setData(RangeCounts d){
        data = d;
    }

    // The offscreen image for double-buffering.
    private Image image;
    private RangeCounts data;
    // User settable labels for the bars.
    private String[] barLabels;
}
