/*
 * NonLinearProgressBar.java
 *
 * Created on March 22, 2008, 7:15 PM
 *
 * By: Jason Ederle
 * http://www.jasonederle.com
 *
 */

package jblackout.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.Vector;
import javax.swing.*;
import javax.swing.border.LineBorder;

/**
 * This class displays a non-linear progress bar on a JPanel that can be resized to any dimension.
 * For example say you have 5 events that need to be waited on but may happen in any order.
 * The initial progress bar will look like this (the real progress bar isn't text based.. it looks nice) :
 * [ ][ ][ ][ ][ ]
 * 
 * Say the 3rd event finishes first:
 * [ ][ ][x][ ][ ] 
 *
 * then the 5th:
 * [ ][ ][x][ ][x]
 *
 * You get the idea.
 * The progess nodes are numbered like arrays, the first node is at index 0 and last node is numberOfNodes-1.

 *
 * @author Jason Ederle
 */
public class NonLinearProgressBar extends JPanel {
    
    private Color bgColor = Color.BLACK;
    private Color nodeColor = Color.DARK_GRAY;
    private Color borderColor = Color.WHITE;
    private Color loadedColor = Color.GREEN;
  
    private int spacerSize = 2;
    private int borderThickness = 1;
    private int numberOfNodes = 1;
    private Vector<Color> nodeState;
    
    
    /** Creates a new instance of NonLinearProgressBar */
    public NonLinearProgressBar() {
        super();
        this.nodeState = new Vector<Color>();
        this.setBackground(bgColor);
        this.resetProgressBar(numberOfNodes);
        this.setBorder( new LineBorder(borderColor, borderThickness));
        this.setVisible(true);
    }
    
    /** Creates a new instance of NonLinearProgressBar 
     * with as many nodes specified by newNodwCount.
     */
    public NonLinearProgressBar(int newNodeCount) {
        super();
        this.nodeState = new Vector<Color>();
        this.setBackground(bgColor);
        this.resetProgressBar(newNodeCount);
        this.setBorder( new LineBorder(borderColor, borderThickness));
        this.setVisible(true);
    }
    
    
    /**
     * Sets the number of notches in the progress bar and resets itself.
     */
    public void resetProgressBar(int numProgressNodes){
        this.numberOfNodes = numProgressNodes;
        
        nodeState.clear();
        for(int i=0; i<numberOfNodes; i++)
            nodeState.add(nodeColor);
        
        this.repaint();
    }
    
    
    /**
     * Colors a notch in the progress bar to indicate it's been loaded.
     */
    public void setNodeLoaded(int nodeNumber) throws Exception{
        if(nodeNumber < numberOfNodes){
            nodeState.set(nodeNumber, loadedColor);
            repaint();
            this.paintAll(this.getGraphics());
        }else{
            throw new Exception("Can't paint progress for node " + nodeNumber + " the index is out of bounds.");
        }
    }
    
    
    /**
     * Colors a notch in the progress bar to indicate that it has not been loaded.
     * Not yet implemented.
     */
    public void setNodeAsNotLoaded(int nodeNumber) throws Exception{
        if(nodeNumber < numberOfNodes){
            nodeState.set(nodeNumber, nodeColor);
            repaint();
                        this.paintAll(this.getGraphics());
        }else{
            throw new Exception("Can't paint progress for node " + nodeNumber + " the index is out of bounds.");
        }
    }
        
    
    
    /**
     * Paints the progress bar onto a jpanel.
     */
    public void paint(Graphics g){
        if(g == null)
            return;
        
        super.paint(g);    
        
        //adjust frame size to show size without border thickness
        Dimension frameSize = getSize();
        frameSize.width = frameSize.width - ( 2 * borderThickness );
        frameSize.height = frameSize.height - ( 2 * borderThickness );
        
        //figure out size of spacers and node dimensions
        int numSpacers = numberOfNodes + 1;
        int totalSpacerSize = (numSpacers * spacerSize); 
        int nodeWidth = (int)(frameSize.width - totalSpacerSize) / numberOfNodes;
        int nodeHeight = frameSize.height - (spacerSize * 2); 
        
        //start in top left corner, but offset for spacers and borders.
        int x = spacerSize + borderThickness;
        int y = spacerSize + borderThickness;
       
        // because of the way the division works you end up with a clump of partial pixels that adds up to be
        // a big gap. So I take this extra padding, and spread it across as many nodes as possible in the for loop.
        int totalPadding = frameSize.width - (totalSpacerSize + nodeWidth * numberOfNodes);
        
        //a node may strectch 1px to make the nodes appear evenly spread ( due to division problem )
        int currNodeWidth = 0;
        
        int i=0;
        for(i=0; i<numberOfNodes; i++){
            
            //node state stores colors representing the state of the node
            g.setColor( nodeState.elementAt(i) );          
            
            //distribute the padding across as many nodes as possible
            if(totalPadding > 0){
                totalPadding--;
                currNodeWidth = nodeWidth + 1;
            }else{
                currNodeWidth = nodeWidth;
            }
                    
            //draw the node
            g.fillRect(x, y, currNodeWidth, nodeHeight);
            
            //setup the new x cordinate for next node          
            x += spacerSize + currNodeWidth;
        }
        
    }
    
    
    
    /**
     * Demonstrates what that the progress bar can do.
     */
    public void demoProgressBar(){
        Thread t = new Thread(){
            public void run(){
                Thread t = Thread.currentThread();
                int pauseTime = 500;
                int maxIterations = 50;
                for(int i=1;i<maxIterations; i++ ){
                    try{
                        
                        resetProgressBar(i+1);
                        t.sleep(pauseTime);
                        for(int x=0; x<=i; x++){
                            //paint each node 1 at a time.
                            
                            setNodeLoaded(x);
                            t.sleep(pauseTime);
                        }
                        
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                    
                }
                
            }
        };

        
        t.start();
    }
    
    
    /**
     *  Demos the progress bar
     */
    public static void main(String[] args) throws Exception{
        JFrame frame = new JFrame("Test Non-Linear Progress Bar");
        
        
        
        final NonLinearProgressBar bar = new NonLinearProgressBar();
        Dimension barSize = new Dimension(350,10);
        bar.setPreferredSize(barSize);
        bar.demoProgressBar();
        
        frame.add(bar);       
        frame.pack();
        frame.setVisible(true);        
    }
    
    
}
