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

class Node {
	protected Node child;
	protected Node sibling;
	protected final static int DEPTH_CHOKE = 50;
	protected final static int BREADTH_CHOKE = 10;
	protected Container container;
	int x;
	int y;

	public Node() {}

	protected Dimension format() {
		int[] geometry = format(0, 0);
		return new Dimension (geometry[1], geometry[0]);
	}

	protected int[] format(int level, int offset) {
		int[] childGeometry =  (
				getChild() == null
			?
				new int[] { level, 1 }
			:
				getChild().format(level + 1, offset)
			);
		int[] siblingGeometry = (
				getSibling() == null
			?
				new int[] { level, 0 }
			:
				getSibling().format(level, offset + childGeometry[1])
			);
			
			x = (int) Math.ceil(childGeometry[1] / 2 + offset);
			y = level;
		
		return new int[] { Math.max(childGeometry[0], siblingGeometry[0]), childGeometry[1] + siblingGeometry[1] };
	}

	protected void generateTree(double depthProbability, double depthSkew, double breadthProbability, double breadthSkew, int depthChoke, int breadthChoke) {
		if (depthChoke > 0 && Math.random() < depthProbability + depthSkew * depthChoke) {
			setChild(new Node());
			getChild().generateTree(depthProbability, depthSkew, breadthProbability, depthSkew, depthChoke - 1, BREADTH_CHOKE);
		} else {
			setChild(null);
		}

		if (breadthChoke > 0 && Math.random() < breadthProbability + breadthSkew * breadthChoke) {
			setSibling(new Node());
			getSibling().generateTree(depthProbability, depthSkew, breadthProbability, breadthSkew, depthChoke, breadthChoke - 1);
		} else {
			setSibling(null);
		}
	}

	protected void setSibling(Node sibling) {
		this.sibling = sibling;
	}

	protected Node getSibling() {
		return sibling;
	}

	protected void setChild(Node child) {
		this.child = child;
	}

	protected Node getChild() {
		return child;

	}
}

class Tree extends JComponent {
	protected Node root = new Node();
	protected static RenderingHints hints;
	
	protected int WIDTH = 10;
	protected int ROUNDING = 3;
	protected int MIDPOINT = WIDTH / 2;

	public void paint(Graphics g) {
		Graphics2D g2D = (Graphics2D) g;
		g2D.setRenderingHints(hints);
		paintNode(root, g2D);
	}
	
	public Tree() {
		super();
		if (hints == null) {
			hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, (Object) RenderingHints.VALUE_ANTIALIAS_ON);
			hints.put(RenderingHints.KEY_FRACTIONALMETRICS, (Object)  RenderingHints.VALUE_FRACTIONALMETRICS_ON);
		}
	}

	protected void paintNode(Node n, Graphics2D g) {
		g.setColor(Color.blue);
		g.fillRoundRect((int) (n.x * WIDTH * 1.2), (int) (n.y * WIDTH * 1.5), WIDTH, WIDTH, ROUNDING, ROUNDING);
		
		int midX = (int) (n.x * WIDTH * 1.2) + MIDPOINT;
		int midY = (int) (n.y * WIDTH * 1.5) + WIDTH;
		g.setColor(Color.black);
		
		Node child = n.getChild();
		while (child != null) {
			g.drawLine(
				midX,
				midY,
				(int) (child.x * WIDTH * 1.2) + MIDPOINT,
				(int) (child.y * WIDTH * 1.5));
			paintNode(child, g);
			child = child.getSibling();
		}

	}

	public void generateTree(double depthProbabilityInitial, double depthProbabilityFinal, double breadthProbabilityInitial, double breadthProbabilityFinal) {
		root.setSibling(null);
		root.setChild(new Node());
		root.getChild().generateTree(depthProbabilityInitial, (depthProbabilityFinal - depthProbabilityInitial) / Node.DEPTH_CHOKE, breadthProbabilityInitial, (breadthProbabilityFinal - breadthProbabilityInitial) / Node.BREADTH_CHOKE, Node.DEPTH_CHOKE, Node.BREADTH_CHOKE);
		Dimension d = root.format();
		Dimension D = new Dimension((int) (WIDTH * 1.2 * d.getWidth()), (int) (WIDTH * 1.5 * d.getHeight()));
		setMaximumSize(D);
		setMinimumSize(D);
		setPreferredSize(D);
	}
}

public class Treeulator extends JFrame implements ActionListener, WindowListener {

	private JButton build = new JButton("Build");
	private JButton defaults = new JButton("Reset");
	private Tree tree = new Tree();
	private JSlider breadthInitial = new JSlider(JSlider.HORIZONTAL, 0, 99, 50);
	private JSlider breadthFinal = new JSlider(JSlider.HORIZONTAL, 0, 99, 50);
	private JSlider depthInitial = new JSlider(JSlider.HORIZONTAL, 0, 99, 50);
	private JSlider depthFinal = new JSlider(JSlider.HORIZONTAL, 0, 99, 50);

	public static void main(String[] args) {
		JFrame treeulator = new Treeulator();
	}
	public Treeulator() {
		super("Treeulator");
		this.addWindowListener(this);
		JPanel panel = new JPanel();
		this.getContentPane().setLayout(new BorderLayout());
		panel.setLayout(new GridLayout(5, 2));
		panel.add(new JLabel("Breadth (Left):"));
		panel.add(breadthInitial);
		panel.add(new JLabel("Breadth (Right):"));
		panel.add(breadthFinal);
		panel.add(new JLabel("Depth (Top):"));
		panel.add(depthInitial);
		panel.add(new JLabel("Depth (Bottom):"));
		panel.add(depthFinal);
		panel.add(build);
		panel.add(defaults);
		this.getContentPane().add(panel, BorderLayout.SOUTH);
		this.getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
		build.addActionListener(this);
		defaults.addActionListener(this);
		tree.setPreferredSize(new Dimension(300, 300));
		tree.setLayout(null);
		this.setVisible(true);
		this.pack();
		this.repaint();
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == build) {
			tree.generateTree(depthInitial.getValue() / 100.0, depthFinal.getValue() / 100.0, breadthInitial.getValue() / 100.0, breadthFinal.getValue() / 100.0);
			this.repaint();
		} else if (e.getSource() == defaults) {
			depthInitial.setValue(50);
			breadthInitial.setValue(50);
			depthFinal.setValue(50);
			breadthFinal.setValue(50);
		}
	}
	public void windowOpened(WindowEvent e) {}
	public void windowClosing(WindowEvent e) {
		System.exit(0);
	}
	public void windowClosed(WindowEvent e) {}
	public void windowIconified(WindowEvent e) {}
	public void windowDeiconified(WindowEvent e) {}
	public void windowActivated(WindowEvent e) {}
	public void windowDeactivated(WindowEvent e) {}
}

